Compare commits
13 Commits
0.6.6
...
charlie/zs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3baec49df | ||
|
|
e6fde89e26 | ||
|
|
7579a792c7 | ||
|
|
0bbc138037 | ||
|
|
ff11db61b4 | ||
|
|
2823487bf8 | ||
|
|
910fac781d | ||
|
|
149fb2090e | ||
|
|
40c65dcfa7 | ||
|
|
03f3a4e855 | ||
|
|
531ebf6dff | ||
|
|
7c2011599f | ||
|
|
17e90823da |
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# This file was autogenerated by cargo-dist: https://opensource.axo.dev/cargo-dist/
|
||||
#
|
||||
# Copyright 2022-2024, axodotdev
|
||||
# SPDX-License-Identifier: MIT or Apache-2.0
|
||||
#
|
||||
@@ -64,7 +66,7 @@ jobs:
|
||||
# we specify bash to get pipefail; it guards against the `curl` command
|
||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.18.0/cargo-dist-installer.sh | sh"
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.22.1/cargo-dist-installer.sh | sh"
|
||||
- name: Cache cargo-dist
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -233,9 +233,9 @@ inherits = "release"
|
||||
# Config for 'cargo dist'
|
||||
[workspace.metadata.dist]
|
||||
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
|
||||
cargo-dist-version = "0.18.0"
|
||||
cargo-dist-version = "0.22.1"
|
||||
# CI backends to support
|
||||
ci = ["github"]
|
||||
ci = "github"
|
||||
# The installers to generate for each app
|
||||
installers = ["shell", "powershell"]
|
||||
# The archive format to use for windows builds (defaults .zip)
|
||||
@@ -266,11 +266,11 @@ targets = [
|
||||
auto-includes = false
|
||||
# Whether cargo-dist should create a GitHub Release or use an existing draft
|
||||
create-release = true
|
||||
# Publish jobs to run in CI
|
||||
# Which actions to run on pull requests
|
||||
pr-run-mode = "skip"
|
||||
# Whether CI should trigger releases with dispatches instead of tag pushes
|
||||
dispatch-releases = true
|
||||
# The stage during which the GitHub Release should be created
|
||||
# Which phase cargo-dist should use to create the GitHub release
|
||||
github-release = "announce"
|
||||
# Whether CI should include auto-generated code to build local artifacts
|
||||
build-local-artifacts = false
|
||||
@@ -278,9 +278,11 @@ build-local-artifacts = false
|
||||
local-artifacts-jobs = ["./build-binaries", "./build-docker"]
|
||||
# Publish jobs to run in CI
|
||||
publish-jobs = ["./publish-pypi", "./publish-wasm"]
|
||||
# Announcement jobs to run in CI
|
||||
# Post-announce jobs to run in CI
|
||||
post-announce-jobs = ["./notify-dependents", "./publish-docs", "./publish-playground"]
|
||||
# Custom permissions for GitHub Jobs
|
||||
github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read" }, "publish-wasm" = { contents = "read", id-token = "write", packages = "write" } }
|
||||
# Whether to install an updater program
|
||||
install-updater = false
|
||||
# Path that installers should place binaries in
|
||||
install-path = "CARGO_HOME"
|
||||
|
||||
@@ -38,7 +38,12 @@ test-case = { workspace = true }
|
||||
[build-dependencies]
|
||||
path-slash = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
zip = { workspace = true, features = ["zstd", "deflate"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "powerpc64"))'.build-dependencies]
|
||||
zip = { workspace = true, features = ["deflate", "zstd"] }
|
||||
|
||||
[target.'cfg(target_arch = "powerpc64")'.build-dependencies]
|
||||
zip = { workspace = true, features = ["deflate"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_db = { workspace = true, features = ["os", "testing"] }
|
||||
|
||||
@@ -30,10 +30,17 @@ fn zip_dir(directory_path: &str, writer: File) -> ZipResult<File> {
|
||||
// We can't use `#[cfg(...)]` here because the target-arch in a build script is the
|
||||
// architecture of the system running the build script and not the architecture of the build-target.
|
||||
// That's why we use the `TARGET` environment variable here.
|
||||
let method = if std::env::var("TARGET").unwrap().contains("wasm32") {
|
||||
CompressionMethod::Deflated
|
||||
} else {
|
||||
CompressionMethod::Zstd
|
||||
#[cfg(target_arch = "powerpc64")]
|
||||
let method = CompressionMethod::Deflated;
|
||||
|
||||
#[cfg(not(target_arch = "powerpc64"))]
|
||||
let method = {
|
||||
let target = std::env::var("TARGET").unwrap();
|
||||
if target.contains("wasm32") || target.contains("powerpc64") {
|
||||
CompressionMethod::Deflated
|
||||
} else {
|
||||
CompressionMethod::Zstd
|
||||
}
|
||||
};
|
||||
|
||||
let options = FileOptions::default()
|
||||
|
||||
@@ -54,6 +54,13 @@ impl TryFrom<(&str, &str)> for PythonVersion {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u8, u8)> for PythonVersion {
|
||||
fn from(value: (u8, u8)) -> Self {
|
||||
let (major, minor) = value;
|
||||
Self { major, minor }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PythonVersion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let PythonVersion { major, minor } = self;
|
||||
|
||||
@@ -362,7 +362,7 @@ impl<'db> Type<'db> {
|
||||
pub fn may_be_unbound(&self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
Type::Unbound => true,
|
||||
Type::Union(union) => union.contains(db, Type::Unbound),
|
||||
Type::Union(union) => union.elements(db).contains(&Type::Unbound),
|
||||
// Unbound can't appear in an intersection, because an intersection with Unbound
|
||||
// simplifies to just Unbound.
|
||||
_ => false,
|
||||
@@ -422,6 +422,8 @@ impl<'db> Type<'db> {
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|&elem_ty| ty.is_subtype_of(db, elem_ty)),
|
||||
(_, Type::Instance(class)) if class.is_stdlib_symbol(db, "builtins", "object") => true,
|
||||
(Type::Instance(class), _) if class.is_stdlib_symbol(db, "builtins", "object") => false,
|
||||
// TODO
|
||||
_ => false,
|
||||
}
|
||||
@@ -817,8 +819,16 @@ impl<'db> CallOutcome<'db> {
|
||||
node,
|
||||
"call-non-callable",
|
||||
format_args!(
|
||||
"Union element '{}' of type '{}' is not callable.",
|
||||
"Object of type '{}' is not callable (due to union element '{}').",
|
||||
called_ty.display(db),
|
||||
elem.display(db),
|
||||
),
|
||||
),
|
||||
_ if not_callable.len() == outcomes.len() => builder.add_diagnostic(
|
||||
node,
|
||||
"call-non-callable",
|
||||
format_args!(
|
||||
"Object of type '{}' is not callable.",
|
||||
called_ty.display(db)
|
||||
),
|
||||
),
|
||||
@@ -826,9 +836,9 @@ impl<'db> CallOutcome<'db> {
|
||||
node,
|
||||
"call-non-callable",
|
||||
format_args!(
|
||||
"Union elements {} of type '{}' are not callable.",
|
||||
"Object of type '{}' is not callable (due to union elements {}).",
|
||||
called_ty.display(db),
|
||||
not_callable.display(db),
|
||||
called_ty.display(db)
|
||||
),
|
||||
),
|
||||
}
|
||||
@@ -994,16 +1004,16 @@ impl<'db> ClassType<'db> {
|
||||
pub struct UnionType<'db> {
|
||||
/// The union type includes values in any of these types.
|
||||
#[return_ref]
|
||||
elements: FxOrderSet<Type<'db>>,
|
||||
elements_boxed: Box<[Type<'db>]>,
|
||||
}
|
||||
|
||||
impl<'db> UnionType<'db> {
|
||||
pub fn contains(&self, db: &'db dyn Db, ty: Type<'db>) -> bool {
|
||||
self.elements(db).contains(&ty)
|
||||
fn elements(self, db: &'db dyn Db) -> &'db [Type<'db>] {
|
||||
self.elements_boxed(db)
|
||||
}
|
||||
|
||||
/// Create a union from a list of elements
|
||||
/// (which may be eagerly simplified into a different variant of [`Type`] altogether)
|
||||
/// (which may be eagerly simplified into a different variant of [`Type`] altogether).
|
||||
pub fn from_elements<T: Into<Type<'db>>>(
|
||||
db: &'db dyn Db,
|
||||
elements: impl IntoIterator<Item = T>,
|
||||
@@ -1017,13 +1027,13 @@ impl<'db> UnionType<'db> {
|
||||
}
|
||||
|
||||
/// Apply a transformation function to all elements of the union,
|
||||
/// and create a new union from the resulting set of types
|
||||
/// and create a new union from the resulting set of types.
|
||||
pub fn map(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
transform_fn: impl Fn(&Type<'db>) -> Type<'db>,
|
||||
) -> Type<'db> {
|
||||
Self::from_elements(db, self.elements(db).into_iter().map(transform_fn))
|
||||
Self::from_elements(db, self.elements(db).iter().map(transform_fn))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1127,6 +1137,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case(Ty::BuiltinInstance("str"), Ty::BuiltinInstance("object"))]
|
||||
#[test_case(Ty::BuiltinInstance("int"), Ty::BuiltinInstance("object"))]
|
||||
#[test_case(Ty::Unknown, Ty::IntLiteral(1))]
|
||||
#[test_case(Ty::Any, Ty::IntLiteral(1))]
|
||||
#[test_case(Ty::Never, Ty::IntLiteral(1))]
|
||||
@@ -1144,6 +1156,7 @@ mod tests {
|
||||
assert!(from.into_type(&db).is_assignable_to(&db, to.into_type(&db)));
|
||||
}
|
||||
|
||||
#[test_case(Ty::BuiltinInstance("object"), Ty::BuiltinInstance("int"))]
|
||||
#[test_case(Ty::IntLiteral(1), Ty::BuiltinInstance("str"))]
|
||||
#[test_case(Ty::BuiltinInstance("int"), Ty::BuiltinInstance("str"))]
|
||||
#[test_case(Ty::BuiltinInstance("int"), Ty::IntLiteral(1))]
|
||||
@@ -1152,6 +1165,8 @@ mod tests {
|
||||
assert!(!from.into_type(&db).is_assignable_to(&db, to.into_type(&db)));
|
||||
}
|
||||
|
||||
#[test_case(Ty::BuiltinInstance("str"), Ty::BuiltinInstance("object"))]
|
||||
#[test_case(Ty::BuiltinInstance("int"), Ty::BuiltinInstance("object"))]
|
||||
#[test_case(Ty::Never, Ty::IntLiteral(1))]
|
||||
#[test_case(Ty::IntLiteral(1), Ty::BuiltinInstance("int"))]
|
||||
#[test_case(Ty::StringLiteral("foo"), Ty::BuiltinInstance("str"))]
|
||||
@@ -1164,6 +1179,7 @@ mod tests {
|
||||
assert!(from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
||||
}
|
||||
|
||||
#[test_case(Ty::BuiltinInstance("object"), Ty::BuiltinInstance("int"))]
|
||||
#[test_case(Ty::Unknown, Ty::IntLiteral(1))]
|
||||
#[test_case(Ty::Any, Ty::IntLiteral(1))]
|
||||
#[test_case(Ty::IntLiteral(1), Ty::Unknown)]
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
//! * An intersection containing two non-overlapping types should simplify to [`Type::Never`].
|
||||
use crate::types::{builtins_symbol_ty, IntersectionType, Type, UnionType};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use ordermap::set::MutableValues;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub(crate) struct UnionBuilder<'db> {
|
||||
elements: FxOrderSet<Type<'db>>,
|
||||
elements: Vec<Type<'db>>,
|
||||
db: &'db dyn Db,
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ impl<'db> UnionBuilder<'db> {
|
||||
pub(crate) fn new(db: &'db dyn Db) -> Self {
|
||||
Self {
|
||||
db,
|
||||
elements: FxOrderSet::default(),
|
||||
elements: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,60 +46,70 @@ impl<'db> UnionBuilder<'db> {
|
||||
pub(crate) fn add(mut self, ty: Type<'db>) -> Self {
|
||||
match ty {
|
||||
Type::Union(union) => {
|
||||
for element in union.elements(self.db) {
|
||||
let new_elements = union.elements(self.db);
|
||||
self.elements.reserve(new_elements.len());
|
||||
for element in new_elements {
|
||||
self = self.add(*element);
|
||||
}
|
||||
}
|
||||
Type::Never => {}
|
||||
_ => {
|
||||
let mut remove = vec![];
|
||||
for element in &self.elements {
|
||||
let bool_pair = if let Type::BooleanLiteral(b) = ty {
|
||||
Some(Type::BooleanLiteral(!b))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut to_add = ty;
|
||||
let mut to_remove = SmallVec::<[usize; 2]>::new();
|
||||
for (index, element) in self.elements.iter().enumerate() {
|
||||
if Some(*element) == bool_pair {
|
||||
to_add = builtins_symbol_ty(self.db, "bool");
|
||||
to_remove.push(index);
|
||||
// The type we are adding is a BooleanLiteral, which doesn't have any
|
||||
// subtypes. And we just found that the union already contained our
|
||||
// mirror-image BooleanLiteral, so it can't also contain bool or any
|
||||
// supertype of bool. Therefore, we are done.
|
||||
break;
|
||||
}
|
||||
if ty.is_subtype_of(self.db, *element) {
|
||||
return self;
|
||||
} else if element.is_subtype_of(self.db, ty) {
|
||||
remove.push(*element);
|
||||
to_remove.push(index);
|
||||
}
|
||||
}
|
||||
for element in remove {
|
||||
self.elements.remove(&element);
|
||||
|
||||
match to_remove[..] {
|
||||
[] => self.elements.push(to_add),
|
||||
[index] => self.elements[index] = to_add,
|
||||
_ => {
|
||||
let mut current_index = 0;
|
||||
let mut to_remove = to_remove.into_iter();
|
||||
let mut next_to_remove_index = to_remove.next();
|
||||
self.elements.retain(|_| {
|
||||
let retain = if Some(current_index) == next_to_remove_index {
|
||||
next_to_remove_index = to_remove.next();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
current_index += 1;
|
||||
retain
|
||||
});
|
||||
self.elements.push(to_add);
|
||||
}
|
||||
}
|
||||
self.elements.insert(ty);
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Performs the following normalizations:
|
||||
/// - Replaces `Literal[True,False]` with `bool`.
|
||||
/// - TODO For enums `E` with members `X1`,...,`Xn`, replaces
|
||||
/// `Literal[E.X1,...,E.Xn]` with `E`.
|
||||
fn simplify(&mut self) {
|
||||
if let Some(true_index) = self.elements.get_index_of(&Type::BooleanLiteral(true)) {
|
||||
if self.elements.contains(&Type::BooleanLiteral(false)) {
|
||||
*self.elements.get_index_mut2(true_index).unwrap() =
|
||||
builtins_symbol_ty(self.db, "bool");
|
||||
self.elements.remove(&Type::BooleanLiteral(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(mut self) -> Type<'db> {
|
||||
pub(crate) fn build(self) -> Type<'db> {
|
||||
match self.elements.len() {
|
||||
0 => Type::Never,
|
||||
1 => self.elements[0],
|
||||
_ => {
|
||||
self.simplify();
|
||||
|
||||
match self.elements.len() {
|
||||
0 => Type::Never,
|
||||
1 => self.elements[0],
|
||||
_ => {
|
||||
self.elements.shrink_to_fit();
|
||||
Type::Union(UnionType::new(self.db, self.elements))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Type::Union(UnionType::new(self.db, self.elements.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,12 +303,6 @@ mod tests {
|
||||
use crate::ProgramSettings;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
|
||||
impl<'db> UnionType<'db> {
|
||||
fn elements_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
|
||||
self.elements(db).into_iter().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
@@ -326,7 +330,7 @@ mod tests {
|
||||
let t1 = Type::IntLiteral(1);
|
||||
let union = UnionType::from_elements(&db, [t0, t1]).expect_union();
|
||||
|
||||
assert_eq!(union.elements_vec(&db), &[t0, t1]);
|
||||
assert_eq!(union.elements(&db), &[t0, t1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -363,10 +367,10 @@ mod tests {
|
||||
let t3 = Type::IntLiteral(17);
|
||||
|
||||
let union = UnionType::from_elements(&db, [t0, t1, t3]).expect_union();
|
||||
assert_eq!(union.elements_vec(&db), &[t0, t3]);
|
||||
assert_eq!(union.elements(&db), &[t0, t3]);
|
||||
|
||||
let union = UnionType::from_elements(&db, [t0, t1, t2, t3]).expect_union();
|
||||
assert_eq!(union.elements_vec(&db), &[bool_ty, t3]);
|
||||
assert_eq!(union.elements(&db), &[bool_ty, t3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -378,7 +382,7 @@ mod tests {
|
||||
let u1 = UnionType::from_elements(&db, [t0, t1]);
|
||||
let union = UnionType::from_elements(&db, [u1, t2]).expect_union();
|
||||
|
||||
assert_eq!(union.elements_vec(&db), &[t0, t1, t2]);
|
||||
assert_eq!(union.elements(&db), &[t0, t1, t2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -386,18 +390,37 @@ mod tests {
|
||||
let db = setup_db();
|
||||
let t0 = builtins_symbol_ty(&db, "str").to_instance(&db);
|
||||
let t1 = Type::LiteralString;
|
||||
let t2 = Type::Unknown;
|
||||
let u0 = UnionType::from_elements(&db, [t0, t1]);
|
||||
let u1 = UnionType::from_elements(&db, [t1, t0]);
|
||||
let u2 = UnionType::from_elements(&db, [t0, t1, t2]);
|
||||
|
||||
assert_eq!(u0, t0);
|
||||
assert_eq!(u1, t0);
|
||||
assert_eq!(u2.expect_union().elements_vec(&db), &[t0, t2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_union_no_simplify_any() {}
|
||||
fn build_union_no_simplify_unknown() {
|
||||
let db = setup_db();
|
||||
let t0 = builtins_symbol_ty(&db, "str").to_instance(&db);
|
||||
let t1 = Type::Unknown;
|
||||
let u0 = UnionType::from_elements(&db, [t0, t1]);
|
||||
let u1 = UnionType::from_elements(&db, [t1, t0]);
|
||||
|
||||
assert_eq!(u0.expect_union().elements(&db), &[t0, t1]);
|
||||
assert_eq!(u1.expect_union().elements(&db), &[t1, t0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_union_subsume_multiple() {
|
||||
let db = setup_db();
|
||||
let str_ty = builtins_symbol_ty(&db, "str").to_instance(&db);
|
||||
let int_ty = builtins_symbol_ty(&db, "int").to_instance(&db);
|
||||
let object_ty = builtins_symbol_ty(&db, "object").to_instance(&db);
|
||||
let unknown_ty = Type::Unknown;
|
||||
|
||||
let u0 = UnionType::from_elements(&db, [str_ty, unknown_ty, int_ty, object_ty]);
|
||||
|
||||
assert_eq!(u0.expect_union().elements(&db), &[unknown_ty, object_ty]);
|
||||
}
|
||||
|
||||
impl<'db> IntersectionType<'db> {
|
||||
fn pos_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
|
||||
@@ -477,7 +500,7 @@ mod tests {
|
||||
.add_positive(u0)
|
||||
.build()
|
||||
.expect_union();
|
||||
let [Type::Intersection(i0), Type::Intersection(i1)] = union.elements_vec(&db)[..] else {
|
||||
let [Type::Intersection(i0), Type::Intersection(i1)] = union.elements(&db)[..] else {
|
||||
panic!("expected a union of two intersections");
|
||||
};
|
||||
assert_eq!(i0.pos_vec(&db), &[ta, t0]);
|
||||
|
||||
@@ -3486,7 +3486,7 @@ mod tests {
|
||||
assert_file_diagnostics(
|
||||
&db,
|
||||
"src/a.py",
|
||||
&["Union element 'Literal[1]' of type 'Literal[1] | Literal[f]' is not callable."],
|
||||
&["Object of type 'Literal[1] | Literal[f]' is not callable (due to union element 'Literal[1]')."],
|
||||
);
|
||||
assert_public_ty(&db, "src/a.py", "x", "Unknown | int");
|
||||
|
||||
@@ -3515,7 +3515,7 @@ mod tests {
|
||||
&db,
|
||||
"src/a.py",
|
||||
&[
|
||||
r#"Union elements Literal[1], Literal["foo"] of type 'Literal[1] | Literal["foo"] | Literal[f]' are not callable."#,
|
||||
r#"Object of type 'Literal[1] | Literal["foo"] | Literal[f]' is not callable (due to union elements Literal[1], Literal["foo"])."#,
|
||||
],
|
||||
);
|
||||
assert_public_ty(&db, "src/a.py", "x", "Unknown | int");
|
||||
@@ -3523,6 +3523,31 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_union_with_all_not_callable() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
if flag:
|
||||
f = 1
|
||||
else:
|
||||
f = 'foo'
|
||||
x = f()
|
||||
",
|
||||
)?;
|
||||
|
||||
assert_file_diagnostics(
|
||||
&db,
|
||||
"src/a.py",
|
||||
&[r#"Object of type 'Literal[1] | Literal["foo"]' is not callable."#],
|
||||
);
|
||||
assert_public_ty(&db, "src/a.py", "x", "Unknown");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_callable() {
|
||||
let mut db = setup_db();
|
||||
|
||||
@@ -114,22 +114,19 @@ fn lint_maybe_undefined(context: &SemanticLintContext, name: &ast::ExprName) {
|
||||
return;
|
||||
}
|
||||
let semantic = &context.semantic;
|
||||
match name.ty(semantic) {
|
||||
Type::Unbound => {
|
||||
context.push_diagnostic(format_diagnostic(
|
||||
context,
|
||||
&format!("Name '{}' used when not defined.", &name.id),
|
||||
name.start(),
|
||||
));
|
||||
}
|
||||
Type::Union(union) if union.contains(semantic.db(), Type::Unbound) => {
|
||||
context.push_diagnostic(format_diagnostic(
|
||||
context,
|
||||
&format!("Name '{}' used when possibly not defined.", &name.id),
|
||||
name.start(),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
let ty = name.ty(semantic);
|
||||
if ty.is_unbound() {
|
||||
context.push_diagnostic(format_diagnostic(
|
||||
context,
|
||||
&format!("Name '{}' used when not defined.", &name.id),
|
||||
name.start(),
|
||||
));
|
||||
} else if ty.may_be_unbound(semantic.db()) {
|
||||
context.push_diagnostic(format_diagnostic(
|
||||
context,
|
||||
&format!("Name '{}' used when possibly not defined.", &name.id),
|
||||
name.start(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,20 +152,23 @@ pub enum AnalyzeCommand {
|
||||
pub struct AnalyzeGraphCommand {
|
||||
/// List of files or directories to include.
|
||||
#[clap(help = "List of files or directories to include [default: .]")]
|
||||
pub files: Vec<PathBuf>,
|
||||
files: Vec<PathBuf>,
|
||||
/// The direction of the import map. By default, generates a dependency map, i.e., a map from
|
||||
/// file to files that it depends on. Use `--direction dependents` to generate a map from file
|
||||
/// to files that depend on it.
|
||||
#[clap(long, value_enum, default_value_t)]
|
||||
pub direction: Direction,
|
||||
direction: Direction,
|
||||
/// Attempt to detect imports from string literals.
|
||||
#[clap(long)]
|
||||
pub detect_string_imports: bool,
|
||||
detect_string_imports: bool,
|
||||
/// Enable preview mode. Use `--no-preview` to disable.
|
||||
#[arg(long, overrides_with("no_preview"))]
|
||||
preview: bool,
|
||||
#[clap(long, overrides_with("preview"), hide = true)]
|
||||
no_preview: bool,
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long, value_enum)]
|
||||
target_version: Option<PythonVersion>,
|
||||
}
|
||||
|
||||
// The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient
|
||||
@@ -789,6 +792,7 @@ impl AnalyzeGraphCommand {
|
||||
None
|
||||
},
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
target_version: self.target_version,
|
||||
..ExplicitConfigOverrides::default()
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ use ruff_linter::linter::add_noqa_to_path;
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
use ruff_workspace::resolver::{
|
||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile,
|
||||
};
|
||||
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
@@ -57,6 +59,15 @@ pub(crate) fn add_noqa(
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
let settings = resolver.resolve(path);
|
||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||
&& match_exclusion(
|
||||
resolved_file.path(),
|
||||
resolved_file.file_name(),
|
||||
&settings.linter.exclude,
|
||||
)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let source_kind = match SourceKind::from_path(path, source_type) {
|
||||
Ok(Some(source_kind)) => source_kind,
|
||||
Ok(None) => return None,
|
||||
|
||||
@@ -8,7 +8,7 @@ use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
|
||||
use ruff_linter::{warn_user, warn_user_once};
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{python_files_in_path, ResolvedFile};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -59,6 +59,12 @@ pub(crate) fn analyze_graph(
|
||||
.filter_map(|package| package.parent())
|
||||
.map(Path::to_path_buf)
|
||||
.filter_map(|path| SystemPathBuf::from_path_buf(path).ok()),
|
||||
pyproject_config
|
||||
.settings
|
||||
.analyze
|
||||
.target_version
|
||||
.as_tuple()
|
||||
.into(),
|
||||
)?;
|
||||
|
||||
// Create a cache for resolved globs.
|
||||
@@ -74,19 +80,30 @@ pub(crate) fn analyze_graph(
|
||||
continue;
|
||||
};
|
||||
|
||||
let path = resolved_file.into_path();
|
||||
let path = resolved_file.path();
|
||||
let package = path
|
||||
.parent()
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(Clone::clone);
|
||||
|
||||
// Resolve the per-file settings.
|
||||
let settings = resolver.resolve(&path);
|
||||
let settings = resolver.resolve(path);
|
||||
let string_imports = settings.analyze.detect_string_imports;
|
||||
let include_dependencies = settings.analyze.include_dependencies.get(&path).cloned();
|
||||
let include_dependencies = settings.analyze.include_dependencies.get(path).cloned();
|
||||
|
||||
// Skip excluded files.
|
||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||
&& match_exclusion(
|
||||
resolved_file.path(),
|
||||
resolved_file.file_name(),
|
||||
&settings.analyze.exclude,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore non-Python files.
|
||||
let source_type = match settings.analyze.extension.get(&path) {
|
||||
let source_type = match settings.analyze.extension.get(path) {
|
||||
None => match SourceType::from(&path) {
|
||||
SourceType::Python(source_type) => source_type,
|
||||
SourceType::Toml(_) => {
|
||||
@@ -106,19 +123,19 @@ pub(crate) fn analyze_graph(
|
||||
warn!("Failed to convert package to system path");
|
||||
continue;
|
||||
};
|
||||
let Ok(path) = SystemPathBuf::from_path_buf(path) else {
|
||||
let Ok(path) = SystemPathBuf::from_path_buf(resolved_file.into_path()) else {
|
||||
warn!("Failed to convert path to system path");
|
||||
continue;
|
||||
};
|
||||
|
||||
let db = db.snapshot();
|
||||
let glob_resolver = glob_resolver.clone();
|
||||
let root = root.clone();
|
||||
let result = inner_result.clone();
|
||||
let glob_resolver = glob_resolver.clone();
|
||||
scope.spawn(move |_| {
|
||||
// Identify any imports via static analysis.
|
||||
let mut imports =
|
||||
ruff_graph::generate(&path, package.as_deref(), string_imports, &db)
|
||||
ModuleImports::detect(&db, &path, package.as_deref(), string_imports)
|
||||
.unwrap_or_else(|err| {
|
||||
warn!("Failed to generate import map for {path}: {err}");
|
||||
ModuleImports::default()
|
||||
|
||||
@@ -226,13 +226,14 @@ fn globs() -> Result<()> {
|
||||
|
||||
root.child("ruff.toml").write_str(indoc::indoc! {r#"
|
||||
[analyze]
|
||||
include-dependencies = { "ruff/a.py" = ["ruff/b.py"], "ruff/b.py" = ["ruff/*.py"] }
|
||||
include-dependencies = { "ruff/a.py" = ["ruff/b.py"], "ruff/b.py" = ["ruff/*.py"], "ruff/c.py" = ["*.json"] }
|
||||
"#})?;
|
||||
|
||||
root.child("ruff").child("__init__.py").write_str("")?;
|
||||
root.child("ruff").child("a.py").write_str("")?;
|
||||
root.child("ruff").child("b.py").write_str("")?;
|
||||
root.child("ruff").child("c.py").write_str("")?;
|
||||
root.child("ruff").child("d.json").write_str("")?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec(),
|
||||
@@ -252,7 +253,50 @@ fn globs() -> Result<()> {
|
||||
"ruff/b.py",
|
||||
"ruff/c.py"
|
||||
],
|
||||
"ruff/c.py": []
|
||||
"ruff/c.py": [
|
||||
"ruff/d.json"
|
||||
]
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let root = ChildPath::new(tempdir.path());
|
||||
|
||||
root.child("ruff.toml").write_str(indoc::indoc! {r#"
|
||||
[analyze]
|
||||
exclude = ["ruff/c.py"]
|
||||
"#})?;
|
||||
|
||||
root.child("ruff").child("__init__.py").write_str("")?;
|
||||
root.child("ruff")
|
||||
.child("a.py")
|
||||
.write_str(indoc::indoc! {r#"
|
||||
import ruff.b
|
||||
"#})?;
|
||||
root.child("ruff").child("b.py").write_str("")?;
|
||||
root.child("ruff").child("c.py").write_str("")?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec(),
|
||||
}, {
|
||||
assert_cmd_snapshot!(command().current_dir(&root), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": []
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -1619,6 +1619,58 @@ print(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_exclude() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
exclude = ["excluded.py"]
|
||||
select = ["RUF015"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let test_path = tempdir.path().join("noqa.py");
|
||||
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
def first_square():
|
||||
return [x * x for x in range(20)][0]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let exclude_path = tempdir.path().join("excluded.py");
|
||||
|
||||
fs::write(
|
||||
&exclude_path,
|
||||
r#"
|
||||
def first_square():
|
||||
return [x * x for x in range(20)][0]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--add-noqa"]), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Added 1 noqa directive.
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Infer `3.11` from `requires-python` in `pyproject.toml`.
|
||||
#[test]
|
||||
fn requires_python() -> Result<()> {
|
||||
|
||||
@@ -389,7 +389,9 @@ formatter.docstring_code_format = disabled
|
||||
formatter.docstring_code_line_width = dynamic
|
||||
|
||||
# Analyze Settings
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = Py37
|
||||
analyze.detect_string_imports = false
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
||||
@@ -34,13 +34,15 @@ tracing-subscriber = { workspace = true, optional = true }
|
||||
tracing-tree = { workspace = true, optional = true }
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
|
||||
[target.'cfg(not(any(target_arch = "wasm32", target_arch = "powerpc64")))'.dependencies]
|
||||
zip = { workspace = true, features = ["zstd"] }
|
||||
|
||||
[target.'cfg(target_arch="wasm32")'.dependencies]
|
||||
web-time = { version = "1.1.0" }
|
||||
[target.'cfg(any(target_arch = "wasm32", target_arch = "powerpc64"))'.dependencies]
|
||||
zip = { workspace = true, features = ["deflate"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
web-time = { version = "1.1.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -194,6 +194,10 @@ pub(crate) struct Args {
|
||||
/// Format the files. Without this flag, the python files are not modified
|
||||
#[arg(long)]
|
||||
pub(crate) write: bool,
|
||||
|
||||
#[arg(long)]
|
||||
pub(crate) preview: bool,
|
||||
|
||||
/// Control the verbosity of the output
|
||||
#[arg(long, default_value_t, value_enum)]
|
||||
pub(crate) format: Format,
|
||||
@@ -235,7 +239,8 @@ pub(crate) fn main(args: &Args) -> anyhow::Result<ExitCode> {
|
||||
let all_success = if args.multi_project {
|
||||
format_dev_multi_project(args, error_file)?
|
||||
} else {
|
||||
let result = format_dev_project(&args.files, args.stability_check, args.write)?;
|
||||
let result =
|
||||
format_dev_project(&args.files, args.stability_check, args.write, args.preview)?;
|
||||
let error_count = result.error_count();
|
||||
|
||||
if result.error_count() > 0 {
|
||||
@@ -344,7 +349,12 @@ fn format_dev_multi_project(
|
||||
for project_path in project_paths {
|
||||
debug!(parent: None, "Starting {}", project_path.display());
|
||||
|
||||
match format_dev_project(&[project_path.clone()], args.stability_check, args.write) {
|
||||
match format_dev_project(
|
||||
&[project_path.clone()],
|
||||
args.stability_check,
|
||||
args.write,
|
||||
args.preview,
|
||||
) {
|
||||
Ok(result) => {
|
||||
total_errors += result.error_count();
|
||||
total_files += result.file_count;
|
||||
@@ -442,6 +452,7 @@ fn format_dev_project(
|
||||
files: &[PathBuf],
|
||||
stability_check: bool,
|
||||
write: bool,
|
||||
preview: bool,
|
||||
) -> anyhow::Result<CheckRepoResult> {
|
||||
let start = Instant::now();
|
||||
|
||||
@@ -477,7 +488,14 @@ fn format_dev_project(
|
||||
#[cfg(feature = "singlethreaded")]
|
||||
let iter = { paths.into_iter() };
|
||||
iter.map(|path| {
|
||||
let result = format_dir_entry(path, stability_check, write, &black_options, &resolver);
|
||||
let result = format_dir_entry(
|
||||
path,
|
||||
stability_check,
|
||||
write,
|
||||
preview,
|
||||
&black_options,
|
||||
&resolver,
|
||||
);
|
||||
pb_span.pb_inc(1);
|
||||
result
|
||||
})
|
||||
@@ -532,6 +550,7 @@ fn format_dir_entry(
|
||||
resolved_file: Result<ResolvedFile, ignore::Error>,
|
||||
stability_check: bool,
|
||||
write: bool,
|
||||
preview: bool,
|
||||
options: &BlackOptions,
|
||||
resolver: &Resolver,
|
||||
) -> anyhow::Result<(Result<Statistics, CheckFileError>, PathBuf), Error> {
|
||||
@@ -544,6 +563,10 @@ fn format_dir_entry(
|
||||
let path = resolved_file.into_path();
|
||||
let mut options = options.to_py_format_options(&path);
|
||||
|
||||
if preview {
|
||||
options = options.with_preview(PreviewMode::Enabled);
|
||||
}
|
||||
|
||||
let settings = resolver.resolve(&path);
|
||||
// That's a bad way of doing this but it's not worth doing something better for format_dev
|
||||
if settings.formatter.line_width != LineWidth::default() {
|
||||
@@ -551,9 +574,8 @@ fn format_dir_entry(
|
||||
}
|
||||
|
||||
// Handle panics (mostly in `debug_assert!`)
|
||||
let result = match catch_unwind(|| format_dev_file(&path, stability_check, write, options)) {
|
||||
Ok(result) => result,
|
||||
Err(panic) => {
|
||||
let result = catch_unwind(|| format_dev_file(&path, stability_check, write, options))
|
||||
.unwrap_or_else(|panic| {
|
||||
if let Some(message) = panic.downcast_ref::<String>() {
|
||||
Err(CheckFileError::Panic {
|
||||
message: message.clone(),
|
||||
@@ -568,8 +590,7 @@ fn format_dir_entry(
|
||||
message: "(Panic didn't set a string message)".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
Ok((result, path))
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,10 @@ pub struct ModuleDb {
|
||||
|
||||
impl ModuleDb {
|
||||
/// Initialize a [`ModuleDb`] from the given source root.
|
||||
pub fn from_src_roots(mut src_roots: impl Iterator<Item = SystemPathBuf>) -> Result<Self> {
|
||||
pub fn from_src_roots(
|
||||
mut src_roots: impl Iterator<Item = SystemPathBuf>,
|
||||
target_version: PythonVersion,
|
||||
) -> Result<Self> {
|
||||
let search_paths = {
|
||||
// Use the first source root.
|
||||
let src_root = src_roots
|
||||
@@ -37,7 +40,7 @@ impl ModuleDb {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
&ProgramSettings {
|
||||
target_version: PythonVersion::default(),
|
||||
target_version,
|
||||
search_paths,
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -21,6 +21,39 @@ mod settings;
|
||||
pub struct ModuleImports(BTreeSet<SystemPathBuf>);
|
||||
|
||||
impl ModuleImports {
|
||||
/// Detect the [`ModuleImports`] for a given Python file.
|
||||
pub fn detect(
|
||||
db: &ModuleDb,
|
||||
path: &SystemPath,
|
||||
package: Option<&SystemPath>,
|
||||
string_imports: bool,
|
||||
) -> Result<Self> {
|
||||
// Read and parse the source code.
|
||||
let file = system_path_to_file(db, path)?;
|
||||
let parsed = parsed_module(db, file);
|
||||
let module_path =
|
||||
package.and_then(|package| to_module_path(package.as_std_path(), path.as_std_path()));
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
// Collect the imports.
|
||||
let imports =
|
||||
Collector::new(module_path.as_deref(), string_imports).collect(parsed.syntax());
|
||||
|
||||
// Resolve the imports.
|
||||
let mut resolved_imports = ModuleImports::default();
|
||||
for import in imports {
|
||||
let Some(resolved) = Resolver::new(&model).resolve(import) else {
|
||||
continue;
|
||||
};
|
||||
let Some(path) = resolved.as_system_path() else {
|
||||
continue;
|
||||
};
|
||||
resolved_imports.insert(path.to_path_buf());
|
||||
}
|
||||
|
||||
Ok(resolved_imports)
|
||||
}
|
||||
|
||||
/// Insert a file path into the module imports.
|
||||
pub fn insert(&mut self, path: SystemPathBuf) {
|
||||
self.0.insert(path);
|
||||
@@ -91,35 +124,3 @@ impl FromIterator<(SystemPathBuf, ModuleImports)> for ImportMap {
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the module imports for a given Python file.
|
||||
pub fn generate(
|
||||
path: &SystemPath,
|
||||
package: Option<&SystemPath>,
|
||||
string_imports: bool,
|
||||
db: &ModuleDb,
|
||||
) -> Result<ModuleImports> {
|
||||
// Read and parse the source code.
|
||||
let file = system_path_to_file(db, path)?;
|
||||
let parsed = parsed_module(db, file);
|
||||
let module_path =
|
||||
package.and_then(|package| to_module_path(package.as_std_path(), path.as_std_path()));
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
// Collect the imports.
|
||||
let imports = Collector::new(module_path.as_deref(), string_imports).collect(parsed.syntax());
|
||||
|
||||
// Resolve the imports.
|
||||
let mut resolved_imports = ModuleImports::default();
|
||||
for import in imports {
|
||||
let Some(resolved) = Resolver::new(&model).resolve(import) else {
|
||||
continue;
|
||||
};
|
||||
let Some(path) = resolved.as_system_path() else {
|
||||
continue;
|
||||
};
|
||||
resolved_imports.insert(path.to_path_buf());
|
||||
}
|
||||
|
||||
Ok(resolved_imports)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_linter::display_settings;
|
||||
use ruff_linter::settings::types::{ExtensionMapping, PreviewMode};
|
||||
use ruff_linter::settings::types::{ExtensionMapping, FilePatternSet, PreviewMode, PythonVersion};
|
||||
use ruff_macros::CacheKey;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
@@ -7,7 +7,9 @@ use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Default, Clone, CacheKey)]
|
||||
pub struct AnalyzeSettings {
|
||||
pub exclude: FilePatternSet,
|
||||
pub preview: PreviewMode,
|
||||
pub target_version: PythonVersion,
|
||||
pub detect_string_imports: bool,
|
||||
pub include_dependencies: BTreeMap<PathBuf, (PathBuf, Vec<String>)>,
|
||||
pub extension: ExtensionMapping,
|
||||
@@ -20,7 +22,9 @@ impl fmt::Display for AnalyzeSettings {
|
||||
formatter = f,
|
||||
namespace = "analyze",
|
||||
fields = [
|
||||
self.exclude,
|
||||
self.preview,
|
||||
self.target_version | debug,
|
||||
self.detect_string_imports,
|
||||
self.extension | debug,
|
||||
self.include_dependencies | debug,
|
||||
|
||||
20
crates/ruff_linter/resources/test/fixtures/pydocstyle/D400_415.py
vendored
Normal file
20
crates/ruff_linter/resources/test/fixtures/pydocstyle/D400_415.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
def f():
|
||||
"Here's a line ending in a question mark?"
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
"""Here's a line ending in an exclamation mark!"""
|
||||
...
|
||||
|
||||
def f():
|
||||
"""Here's a line ending in a colon:"""
|
||||
...
|
||||
|
||||
def f():
|
||||
"""Here's a line ending in a semi colon;"""
|
||||
...
|
||||
|
||||
def f():
|
||||
"""Here's a line ending with a whitespace """
|
||||
...
|
||||
@@ -30,7 +30,7 @@ use crate::registry::Rule;
|
||||
/// ```console
|
||||
/// Traceback (most recent call last):
|
||||
/// File "tmp.py", line 2, in <module>
|
||||
/// raise RuntimeError("Some value is incorrect")
|
||||
/// raise RuntimeError("'Some value' is incorrect")
|
||||
/// RuntimeError: 'Some value' is incorrect
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -29,7 +29,9 @@ mod tests {
|
||||
#[test_case(Rule::UndocumentedParam, Path::new("sections.py"))]
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D.py"))]
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D400.py"))]
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D400_415.py"))]
|
||||
#[test_case(Rule::EndsInPunctuation, Path::new("D.py"))]
|
||||
#[test_case(Rule::EndsInPunctuation, Path::new("D400_415.py"))]
|
||||
#[test_case(Rule::FirstLineCapitalized, Path::new("D.py"))]
|
||||
#[test_case(Rule::FirstLineCapitalized, Path::new("D403.py"))]
|
||||
#[test_case(Rule::FitsOnOneLine, Path::new("D.py"))]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_text_size::TextLen;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines};
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -47,14 +47,18 @@ use crate::rules::pydocstyle::helpers::logical_line;
|
||||
#[violation]
|
||||
pub struct EndsInPeriod;
|
||||
|
||||
impl AlwaysFixableViolation for EndsInPeriod {
|
||||
impl Violation for EndsInPeriod {
|
||||
/// `None` in the case a fix is never available or otherwise Some
|
||||
/// [`FixAvailability`] describing the available fix.
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("First line should end with a period")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Add period".to_string()
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Add period".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +108,7 @@ pub(crate) fn ends_with_period(checker: &mut Checker, docstring: &Docstring) {
|
||||
if !trimmed.ends_with('.') {
|
||||
let mut diagnostic = Diagnostic::new(EndsInPeriod, docstring.range());
|
||||
// Best-effort fix: avoid adding a period after other punctuation marks.
|
||||
if !trimmed.ends_with([':', ';']) {
|
||||
if !trimmed.ends_with([':', ';', '?', '!']) {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
||||
".".to_string(),
|
||||
line.start() + trimmed.text_len(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_text_size::TextLen;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines};
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -46,14 +46,18 @@ use crate::rules::pydocstyle::helpers::logical_line;
|
||||
#[violation]
|
||||
pub struct EndsInPunctuation;
|
||||
|
||||
impl AlwaysFixableViolation for EndsInPunctuation {
|
||||
impl Violation for EndsInPunctuation {
|
||||
/// `None` in the case a fix is never available or otherwise Some
|
||||
/// [`FixAvailability`] describing the available fix.
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("First line should end with a period, question mark, or exclamation point")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Add closing punctuation".to_string()
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Add closing punctuation".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ D.py:487:5: D400 [*] First line should end with a period
|
||||
489 489 |
|
||||
490 490 |
|
||||
|
||||
D.py:514:5: D400 [*] First line should end with a period
|
||||
D.py:514:5: D400 First line should end with a period
|
||||
|
|
||||
513 | def valid_google_string(): # noqa: D400
|
||||
514 | """Test a valid something!"""
|
||||
@@ -202,16 +202,6 @@ D.py:514:5: D400 [*] First line should end with a period
|
||||
|
|
||||
= help: Add period
|
||||
|
||||
ℹ Unsafe fix
|
||||
511 511 |
|
||||
512 512 |
|
||||
513 513 | def valid_google_string(): # noqa: D400
|
||||
514 |- """Test a valid something!"""
|
||||
514 |+ """Test a valid something!."""
|
||||
515 515 |
|
||||
516 516 |
|
||||
517 517 | @expect("D415: First line should end with a period, question mark, "
|
||||
|
||||
D.py:520:5: D400 [*] First line should end with a period
|
||||
|
|
||||
518 | "or exclamation point (not 'g')")
|
||||
@@ -328,6 +318,4 @@ D.py:664:5: D400 [*] First line should end with a period
|
||||
665 |+ but continuations shouldn't be considered multi-line."
|
||||
666 666 |
|
||||
667 667 |
|
||||
668 668 |
|
||||
|
||||
|
||||
668 668 |
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D400_415.py:2:5: D400 First line should end with a period
|
||||
|
|
||||
1 | def f():
|
||||
2 | "Here's a line ending in a question mark?"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
||||
3 | ...
|
||||
|
|
||||
= help: Add period
|
||||
|
||||
D400_415.py:7:5: D400 First line should end with a period
|
||||
|
|
||||
6 | def f():
|
||||
7 | """Here's a line ending in an exclamation mark!"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
||||
8 | ...
|
||||
|
|
||||
= help: Add period
|
||||
|
||||
D400_415.py:11:5: D400 First line should end with a period
|
||||
|
|
||||
10 | def f():
|
||||
11 | """Here's a line ending in a colon:"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
||||
12 | ...
|
||||
|
|
||||
= help: Add period
|
||||
|
||||
D400_415.py:15:5: D400 First line should end with a period
|
||||
|
|
||||
14 | def f():
|
||||
15 | """Here's a line ending in a semi colon;"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
||||
16 | ...
|
||||
|
|
||||
= help: Add period
|
||||
|
||||
D400_415.py:19:5: D400 [*] First line should end with a period
|
||||
|
|
||||
18 | def f():
|
||||
19 | """Here's a line ending with a whitespace """
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
||||
20 | ...
|
||||
|
|
||||
= help: Add period
|
||||
|
||||
ℹ Unsafe fix
|
||||
16 16 | ...
|
||||
17 17 |
|
||||
18 18 | def f():
|
||||
19 |- """Here's a line ending with a whitespace """
|
||||
19 |+ """Here's a line ending with a whitespace. """
|
||||
20 20 | ...
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D400_415.py:11:5: D415 First line should end with a period, question mark, or exclamation point
|
||||
|
|
||||
10 | def f():
|
||||
11 | """Here's a line ending in a colon:"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
||||
12 | ...
|
||||
|
|
||||
= help: Add closing punctuation
|
||||
|
||||
D400_415.py:15:5: D415 First line should end with a period, question mark, or exclamation point
|
||||
|
|
||||
14 | def f():
|
||||
15 | """Here's a line ending in a semi colon;"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
||||
16 | ...
|
||||
|
|
||||
= help: Add closing punctuation
|
||||
|
||||
D400_415.py:19:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
||||
|
|
||||
18 | def f():
|
||||
19 | """Here's a line ending with a whitespace """
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
||||
20 | ...
|
||||
|
|
||||
= help: Add closing punctuation
|
||||
|
||||
ℹ Unsafe fix
|
||||
16 16 | ...
|
||||
17 17 |
|
||||
18 18 | def f():
|
||||
19 |- """Here's a line ending with a whitespace """
|
||||
19 |+ """Here's a line ending with a whitespace. """
|
||||
20 20 | ...
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D415.py:11:5: D415 First line should end with a period, question mark, or exclamation point
|
||||
|
|
||||
10 | def f():
|
||||
11 | """Here's a line ending in a colon:"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
||||
12 | ...
|
||||
|
|
||||
= help: Add closing punctuation
|
||||
|
||||
D415.py:15:5: D415 First line should end with a period, question mark, or exclamation point
|
||||
|
|
||||
14 | def f():
|
||||
15 | """Here's a line ending in a semi colon;"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
||||
16 | ...
|
||||
|
|
||||
= help: Add closing punctuation
|
||||
|
||||
D415.py:19:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
||||
|
|
||||
18 | def f():
|
||||
19 | """Here's a line ending with a whitespace """
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
||||
20 | ...
|
||||
|
|
||||
= help: Add closing punctuation
|
||||
|
||||
ℹ Unsafe fix
|
||||
16 16 | ...
|
||||
17 17 |
|
||||
18 18 | def f():
|
||||
19 |- """Here's a line ending with a whitespace """
|
||||
19 |+ """Here's a line ending with a whitespace. """
|
||||
20 20 | ...
|
||||
@@ -82,30 +82,6 @@ func([1, 2, 3,], bar)
|
||||
|
||||
func([(x, y,) for (x, y) in z], bar)
|
||||
|
||||
# Ensure that return type annotations (which use `parenthesize_if_expands`) are also hugged.
|
||||
def func() -> [1, 2, 3,]:
|
||||
pass
|
||||
|
||||
def func() -> ([1, 2, 3,]):
|
||||
pass
|
||||
|
||||
def func() -> ([1, 2, 3,]):
|
||||
pass
|
||||
|
||||
def func() -> ( # comment
|
||||
[1, 2, 3,]):
|
||||
pass
|
||||
|
||||
def func() -> (
|
||||
[1, 2, 3,] # comment
|
||||
):
|
||||
pass
|
||||
|
||||
def func() -> (
|
||||
[1, 2, 3,]
|
||||
# comment
|
||||
):
|
||||
pass
|
||||
|
||||
# Ensure that nested lists are hugged.
|
||||
func([
|
||||
|
||||
176
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/return_type_no_parameters.py
vendored
Normal file
176
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/return_type_no_parameters.py
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
# Tests for functions without parameters or a dangling comment
|
||||
# Black's overall behavior is to:
|
||||
# 1. Print the return type on the same line as the function header if it fits
|
||||
# 2. Parenthesize the return type if it doesn't fit.
|
||||
# The exception to this are subscripts, see below
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Return types that use NeedsParantheses::BestFit layout with the exception of subscript
|
||||
#########################################################################################
|
||||
# String return type that fits on the same line
|
||||
def no_parameters_string_return_type() -> "ALongIdentifierButDoesntGetParenthesized":
|
||||
pass
|
||||
|
||||
|
||||
# String return type that exceeds the line length
|
||||
def no_parameters_overlong_string_return_type() -> (
|
||||
"ALongIdentifierButDoesntGetParenthesized"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that fits on the same line as the function header
|
||||
def no_parameters_name_return_type() -> ALongIdentifierButDoesntGetParenthesized:
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that exceeds the configured line width
|
||||
def no_parameters_overlong_name_return_type() -> (
|
||||
ALongIdentifierButDoesntGetParenthesized
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Unions
|
||||
#########################################################################################
|
||||
|
||||
def test_return_overlong_union() -> (
|
||||
A | B | C | DDDDDDDDDDDDDDDDDDDDDDDD | EEEEEEEEEEEEEEEEEEEEEE
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def test_return_union_with_elements_exceeding_length() -> (
|
||||
A
|
||||
| B
|
||||
| Ccccccccccccccccccccccccccccccccc
|
||||
| DDDDDDDDDDDDDDDDDDDDDDDD
|
||||
| EEEEEEEEEEEEEEEEEEEEEE
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Multiline strings (NeedsParentheses::Never)
|
||||
#########################################################################################
|
||||
|
||||
def test_return_multiline_string_type_annotation() -> """str
|
||||
| list[str]
|
||||
""":
|
||||
pass
|
||||
|
||||
|
||||
def test_return_multiline_string_binary_expression_return_type_annotation() -> """str
|
||||
| list[str]
|
||||
""" + "b":
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Implicit concatenated strings (NeedsParentheses::Multiline)
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_implicit_concatenated_string_return_type() -> "str" "bbbbbbbbbbbbbbbb":
|
||||
pass
|
||||
|
||||
|
||||
def test_overlong_implicit_concatenated_string_return_type() -> (
|
||||
"liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def test_extralong_implicit_concatenated_string_return_type() -> (
|
||||
"liiiiiiiiiiiisssssst[str]"
|
||||
"bbbbbbbbbbbbbbbbbbbb"
|
||||
"cccccccccccccccccccccccccccccccccccccc"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Subscript
|
||||
#########################################################################################
|
||||
def no_parameters_subscript_return_type() -> list[str]:
|
||||
pass
|
||||
|
||||
|
||||
# 1. Black tries to keep the list flat by parenthesizing the list as shown below even when the `list` identifier
|
||||
# fits on the header line. IMO, this adds unnecessary parentheses that can be avoided
|
||||
# and supporting it requires extra complexity (best_fitting! layout)
|
||||
def no_parameters_overlong_subscript_return_type_with_single_element() -> (
|
||||
list[xxxxxxxxxxxxxxxxxxxxx]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# 2. Black: Removes the parentheses when the subscript fits after breaking individual elements.
|
||||
# This is somewhat wasteful because the below list actually fits on a single line when splitting after
|
||||
# `list[`. It is also inconsistent with how subscripts are normally formatted where it first tries to fit the entire subscript,
|
||||
# then splits after `list[` but keeps all elements on a single line, and finally, splits after each element.
|
||||
# IMO: Splitting after the `list[` and trying to keep the elements together when possible seems more consistent.
|
||||
def no_parameters_subscript_return_type_multiple_elements() -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
# Black removes the parentheses even the elements exceed the configured line width.
|
||||
# So does Ruff.
|
||||
def no_parameters_subscript_return_type_multiple_overlong_elements() -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
# Black parenthesizes the subscript if its name doesn't fit on the header line.
|
||||
# So does Ruff
|
||||
def no_parameters_subscriptreturn_type_with_overlong_value_() -> (
|
||||
liiiiiiiiiiiiiiiiiiiiist[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black: It removes the parentheses when the subscript contains multiple elements as
|
||||
# `no_parameters_subscript_return_type_multiple_overlong_elements` shows. However, it doesn't
|
||||
# when the subscript contains a single element. Black then keeps the parentheses.
|
||||
# Ruff removes the parentheses in this case for consistency.
|
||||
def no_parameters_overlong_subscript_return_type_with_overlong_single_element() -> (
|
||||
list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# can_omit_optional_parentheses_layout
|
||||
#########################################################################################
|
||||
|
||||
def test_binary_expression_return_type_annotation() -> aaaaaaaaaaaaaaaaaaaaaaaaaa > [
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbb,
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Other
|
||||
#########################################################################################
|
||||
|
||||
# Don't paranthesize lists
|
||||
def f() -> [
|
||||
a,
|
||||
b,
|
||||
]: pass
|
||||
195
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/return_type_parameters.py
vendored
Normal file
195
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/return_type_parameters.py
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
# Tests for functions with parameters.
|
||||
# The main difference to functions without parameters is that the return type never gets
|
||||
# parenthesized for values that can't be split (NeedsParentheses::BestFit).
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Return types that use NeedsParantheses::BestFit layout with the exception of subscript
|
||||
#########################################################################################
|
||||
# String return type that fits on the same line
|
||||
def parameters_string_return_type(a) -> "ALongIdentifierButDoesntGetParenthesized":
|
||||
pass
|
||||
|
||||
|
||||
# String return type that exceeds the line length
|
||||
def parameters_overlong_string_return_type(
|
||||
a,
|
||||
) -> "ALongIdentifierButDoesntGetParenthesized":
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that fits on the same line as the function header
|
||||
def parameters_name_return_type(a) -> ALongIdentifierButDoesntGetParenthesized:
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that exceeds the configured line width
|
||||
def parameters_overlong_name_return_type(
|
||||
a,
|
||||
) -> ALongIdentifierButDoesntGetParenthesized:
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Unions
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_return_overlong_union(
|
||||
a,
|
||||
) -> A | B | C | DDDDDDDDDDDDDDDDDDDDDDDD | EEEEEEEEEEEEEEEEEEEEEE:
|
||||
pass
|
||||
|
||||
|
||||
def test_return_union_with_elements_exceeding_length(
|
||||
a,
|
||||
) -> (
|
||||
A
|
||||
| B
|
||||
| Ccccccccccccccccccccccccccccccccc
|
||||
| DDDDDDDDDDDDDDDDDDDDDDDD
|
||||
| EEEEEEEEEEEEEEEEEEEEEE
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Multiline stirngs (NeedsParentheses::Never)
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_return_multiline_string_type_annotation(a) -> """str
|
||||
| list[str]
|
||||
""":
|
||||
pass
|
||||
|
||||
|
||||
def test_return_multiline_string_binary_expression_return_type_annotation(a) -> """str
|
||||
| list[str]
|
||||
""" + "b":
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Implicit concatenated strings (NeedsParentheses::Multiline)
|
||||
#########################################################################################
|
||||
|
||||
def test_implicit_concatenated_string_return_type(a) -> "str" "bbbbbbbbbbbbbbbb":
|
||||
pass
|
||||
|
||||
|
||||
def test_overlong_implicit_concatenated_string_return_type(
|
||||
a,
|
||||
) -> "liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb":
|
||||
pass
|
||||
|
||||
|
||||
def test_extralong_implicit_concatenated_string_return_type(
|
||||
a,
|
||||
) -> (
|
||||
"liiiiiiiiiiiisssssst[str]"
|
||||
"bbbbbbbbbbbbbbbbbbbb"
|
||||
"cccccccccccccccccccccccccccccccccccccc"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Subscript
|
||||
#########################################################################################
|
||||
def parameters_subscript_return_type(a) -> list[str]:
|
||||
pass
|
||||
|
||||
|
||||
# Unlike with no-parameters, the return type gets never parenthesized.
|
||||
def parameters_overlong_subscript_return_type_with_single_element(
|
||||
a
|
||||
) -> list[xxxxxxxxxxxxxxxxxxxxx]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_subscript_return_type_multiple_elements(a) -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_subscript_return_type_multiple_overlong_elements(a) -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_subscriptreturn_type_with_overlong_value_(
|
||||
a
|
||||
) -> liiiiiiiiiiiiiiiiiiiiist[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_overlong_subscript_return_type_with_overlong_single_element(
|
||||
a
|
||||
) -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
# Not even in this very ridiculous case
|
||||
def a():
|
||||
def b():
|
||||
def c():
|
||||
def d():
|
||||
def e():
|
||||
def f():
|
||||
def g():
|
||||
def h():
|
||||
def i():
|
||||
def j():
|
||||
def k():
|
||||
def l():
|
||||
def m():
|
||||
def n():
|
||||
def o():
|
||||
def p():
|
||||
def q():
|
||||
def r():
|
||||
def s():
|
||||
def t():
|
||||
def u():
|
||||
def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeedooooong(
|
||||
a,
|
||||
) -> list[
|
||||
int,
|
||||
float
|
||||
]: ...
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Magic comma in return type
|
||||
#########################################################################################
|
||||
|
||||
# Black only splits the return type. Ruff also breaks the parameters. This is probably a bug.
|
||||
def parameters_subscriptreturn_type_with_overlong_value_(a) -> liiiiiiiiiiiiiiiiiiiiist[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# can_omit_optional_parentheses_layout
|
||||
#########################################################################################
|
||||
|
||||
def test_return_multiline_string_binary_expression_return_type_annotation(
|
||||
a,
|
||||
) -> aaaaaaaaaaaaaaaaaaaaaaaaaa > [
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbb,
|
||||
]:
|
||||
pass
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::expression::parentheses::{
|
||||
};
|
||||
use crate::expression::CallChainLayout;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprSubscript {
|
||||
@@ -103,19 +104,25 @@ impl NeedsParentheses for ExprSubscript {
|
||||
} else {
|
||||
match self.value.needs_parentheses(self.into(), context) {
|
||||
OptionalParentheses::BestFit => {
|
||||
if parent.as_stmt_function_def().is_some_and(|function_def| {
|
||||
function_def
|
||||
.returns
|
||||
.as_deref()
|
||||
.and_then(Expr::as_subscript_expr)
|
||||
== Some(self)
|
||||
}) {
|
||||
// Don't use the best fitting layout for return type annotation because it results in the
|
||||
// return type expanding before the parameters.
|
||||
OptionalParentheses::Never
|
||||
} else {
|
||||
OptionalParentheses::BestFit
|
||||
if let Some(function) = parent.as_stmt_function_def() {
|
||||
if function.returns.as_deref().is_some_and(|returns| {
|
||||
AnyNodeRef::ptr_eq(returns.into(), self.into())
|
||||
}) {
|
||||
if is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(context) &&
|
||||
function.parameters.is_empty() && !context.comments().has(&*function.parameters) {
|
||||
// Apply the `optional_parentheses` layout when the subscript
|
||||
// is in a return type position of a function without parameters.
|
||||
// This ensures the subscript is parenthesized if it has a very
|
||||
// long name that goes over the line length limit.
|
||||
return OptionalParentheses::Multiline
|
||||
}
|
||||
|
||||
// Don't use the best fitting layout for return type annotation because it results in the
|
||||
// return type expanding before the parameters.
|
||||
return OptionalParentheses::Never;
|
||||
}
|
||||
}
|
||||
OptionalParentheses::BestFit
|
||||
}
|
||||
parentheses => parentheses,
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ use crate::expression::parentheses::{
|
||||
OptionalParentheses, Parentheses, Parenthesize,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_hug_parens_with_braces_and_square_brackets_enabled;
|
||||
use crate::preview::{
|
||||
is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled,
|
||||
is_hug_parens_with_braces_and_square_brackets_enabled,
|
||||
};
|
||||
|
||||
mod binary_like;
|
||||
pub(crate) mod expr_attribute;
|
||||
@@ -324,7 +327,7 @@ fn format_with_parentheses_comments(
|
||||
)
|
||||
}
|
||||
|
||||
/// Wraps an expression in an optional parentheses except if its [`NeedsParentheses::needs_parentheses`] implementation
|
||||
/// Wraps an expression in optional parentheses except if its [`NeedsParentheses::needs_parentheses`] implementation
|
||||
/// indicates that it is okay to omit the parentheses. For example, parentheses can always be omitted for lists,
|
||||
/// because they already bring their own parentheses.
|
||||
pub(crate) fn maybe_parenthesize_expression<'a, T>(
|
||||
@@ -382,23 +385,38 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
|
||||
OptionalParentheses::Always => OptionalParentheses::Always,
|
||||
// The reason to add parentheses is to avoid a syntax error when breaking an expression over multiple lines.
|
||||
// Therefore, it is unnecessary to add an additional pair of parentheses if an outer expression
|
||||
// is parenthesized.
|
||||
_ if f.context().node_level().is_parenthesized() => OptionalParentheses::Never,
|
||||
// is parenthesized. Unless, it's the `Parenthesize::IfBreaksParenthesizedNested` layout
|
||||
// where parenthesizing nested `maybe_parenthesized_expression` is explicitly desired.
|
||||
_ if f.context().node_level().is_parenthesized() => {
|
||||
if !is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(
|
||||
f.context(),
|
||||
) {
|
||||
OptionalParentheses::Never
|
||||
} else if matches!(parenthesize, Parenthesize::IfBreaksParenthesizedNested) {
|
||||
return parenthesize_if_expands(
|
||||
&expression.format().with_options(Parentheses::Never),
|
||||
)
|
||||
.with_indent(!is_expression_huggable(expression, f.context()))
|
||||
.fmt(f);
|
||||
} else {
|
||||
return expression.format().with_options(Parentheses::Never).fmt(f);
|
||||
}
|
||||
}
|
||||
needs_parentheses => needs_parentheses,
|
||||
};
|
||||
|
||||
match needs_parentheses {
|
||||
OptionalParentheses::Multiline => match parenthesize {
|
||||
Parenthesize::IfBreaksOrIfRequired => {
|
||||
|
||||
Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested if !is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(f.context()) => {
|
||||
parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
|
||||
.fmt(f)
|
||||
}
|
||||
|
||||
Parenthesize::IfRequired => {
|
||||
expression.format().with_options(Parentheses::Never).fmt(f)
|
||||
}
|
||||
|
||||
Parenthesize::Optional | Parenthesize::IfBreaks => {
|
||||
Parenthesize::Optional | Parenthesize::IfBreaks | Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested => {
|
||||
if can_omit_optional_parentheses(expression, f.context()) {
|
||||
optional_parentheses(&expression.format().with_options(Parentheses::Never))
|
||||
.fmt(f)
|
||||
@@ -411,7 +429,7 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
|
||||
}
|
||||
},
|
||||
OptionalParentheses::BestFit => match parenthesize {
|
||||
Parenthesize::IfBreaksOrIfRequired => {
|
||||
Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested => {
|
||||
parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
|
||||
.fmt(f)
|
||||
}
|
||||
@@ -435,13 +453,13 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
|
||||
}
|
||||
},
|
||||
OptionalParentheses::Never => match parenthesize {
|
||||
Parenthesize::IfBreaksOrIfRequired => {
|
||||
Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested if !is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(f.context()) => {
|
||||
parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
|
||||
.with_indent(!is_expression_huggable(expression, f.context()))
|
||||
.fmt(f)
|
||||
}
|
||||
|
||||
Parenthesize::Optional | Parenthesize::IfBreaks | Parenthesize::IfRequired => {
|
||||
Parenthesize::Optional | Parenthesize::IfBreaks | Parenthesize::IfRequired | Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested => {
|
||||
expression.format().with_options(Parentheses::Never).fmt(f)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -56,10 +56,15 @@ pub(crate) enum Parenthesize {
|
||||
/// Adding parentheses is desired to prevent the comments from wandering.
|
||||
IfRequired,
|
||||
|
||||
/// Parenthesizes the expression if the group doesn't fit on a line (e.g., even name expressions are parenthesized), or if
|
||||
/// the expression doesn't break, but _does_ reports that it always requires parentheses in this position (e.g., walrus
|
||||
/// operators in function return annotations).
|
||||
IfBreaksOrIfRequired,
|
||||
/// Same as [`Self::IfBreaks`] except that it uses [`parenthesize_if_expands`] for expressions
|
||||
/// with the layout [`NeedsParentheses::BestFit`] which is used by non-splittable
|
||||
/// expressions like literals, name, and strings.
|
||||
IfBreaksParenthesized,
|
||||
|
||||
/// Same as [`Self::IfBreaksParenthesized`] but uses [`parenthesize_if_expands`] for nested
|
||||
/// [`maybe_parenthesized_expression`] calls unlike other layouts that always omit parentheses
|
||||
/// when outer parentheses are present.
|
||||
IfBreaksParenthesizedNested,
|
||||
}
|
||||
|
||||
impl Parenthesize {
|
||||
@@ -416,27 +421,25 @@ impl Format<PyFormatContext<'_>> for FormatEmptyParenthesized<'_> {
|
||||
debug_assert!(self.comments[end_of_line_split..]
|
||||
.iter()
|
||||
.all(|comment| comment.line_position().is_own_line()));
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
token(self.left),
|
||||
// end-of-line comments
|
||||
trailing_comments(&self.comments[..end_of_line_split]),
|
||||
// Avoid unstable formatting with
|
||||
// ```python
|
||||
// x = () - (#
|
||||
// )
|
||||
// ```
|
||||
// Without this the comment would go after the empty tuple first, but still expand
|
||||
// the bin op. In the second formatting pass they are trailing bin op comments
|
||||
// so the bin op collapse. Suboptimally we keep parentheses around the bin op in
|
||||
// either case.
|
||||
(!self.comments[..end_of_line_split].is_empty()).then_some(hard_line_break()),
|
||||
// own line comments, which need to be indented
|
||||
soft_block_indent(&dangling_comments(&self.comments[end_of_line_split..])),
|
||||
token(self.right)
|
||||
])]
|
||||
)
|
||||
group(&format_args![
|
||||
token(self.left),
|
||||
// end-of-line comments
|
||||
trailing_comments(&self.comments[..end_of_line_split]),
|
||||
// Avoid unstable formatting with
|
||||
// ```python
|
||||
// x = () - (#
|
||||
// )
|
||||
// ```
|
||||
// Without this the comment would go after the empty tuple first, but still expand
|
||||
// the bin op. In the second formatting pass they are trailing bin op comments
|
||||
// so the bin op collapse. Suboptimally we keep parentheses around the bin op in
|
||||
// either case.
|
||||
(!self.comments[..end_of_line_split].is_empty()).then_some(hard_line_break()),
|
||||
// own line comments, which need to be indented
|
||||
soft_block_indent(&dangling_comments(&self.comments[end_of_line_split..])),
|
||||
token(self.right)
|
||||
])
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ impl FormatNodeRule<WithItem> for FormatWithItem {
|
||||
maybe_parenthesize_expression(
|
||||
context_expr,
|
||||
item,
|
||||
Parenthesize::IfBreaksOrIfRequired,
|
||||
Parenthesize::IfBreaksParenthesizedNested,
|
||||
)
|
||||
.fmt(f)?;
|
||||
} else {
|
||||
|
||||
@@ -29,3 +29,10 @@ pub(crate) fn is_comprehension_leading_expression_comments_same_line_enabled(
|
||||
) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// See [#9447](https://github.com/astral-sh/ruff/issues/9447)
|
||||
pub(crate) fn is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::{NodeKind, StmtFunctionDef};
|
||||
|
||||
use crate::comments::format::{
|
||||
empty_lines_after_leading_comments, empty_lines_before_trailing_comments,
|
||||
};
|
||||
@@ -10,6 +7,8 @@ use crate::prelude::*;
|
||||
use crate::statement::clause::{clause_body, clause_header, ClauseHeader};
|
||||
use crate::statement::stmt_class_def::FormatDecorators;
|
||||
use crate::statement::suite::SuiteKind;
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::{NodeKind, StmtFunctionDef};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtFunctionDef;
|
||||
@@ -112,23 +111,23 @@ fn format_function_header(f: &mut PyFormatter, item: &StmtFunctionDef) -> Format
|
||||
write!(f, [token("def"), space(), name.format()])?;
|
||||
|
||||
if let Some(type_params) = type_params.as_ref() {
|
||||
write!(f, [type_params.format()])?;
|
||||
type_params.format().fmt(f)?;
|
||||
}
|
||||
|
||||
let format_inner = format_with(|f: &mut PyFormatter| {
|
||||
write!(f, [parameters.format()])?;
|
||||
parameters.format().fmt(f)?;
|
||||
|
||||
if let Some(return_annotation) = returns.as_ref() {
|
||||
if let Some(return_annotation) = returns.as_deref() {
|
||||
write!(f, [space(), token("->"), space()])?;
|
||||
|
||||
if return_annotation.is_tuple_expr() {
|
||||
let parentheses = if comments.has_leading(return_annotation.as_ref()) {
|
||||
let parentheses = if comments.has_leading(return_annotation) {
|
||||
Parentheses::Always
|
||||
} else {
|
||||
Parentheses::Never
|
||||
};
|
||||
write!(f, [return_annotation.format().with_options(parentheses)])?;
|
||||
} else if comments.has_trailing(return_annotation.as_ref()) {
|
||||
return_annotation.format().with_options(parentheses).fmt(f)
|
||||
} else if comments.has_trailing(return_annotation) {
|
||||
// Intentionally parenthesize any return annotations with trailing comments.
|
||||
// This avoids an instability in cases like:
|
||||
// ```python
|
||||
@@ -156,15 +155,17 @@ fn format_function_header(f: &mut PyFormatter, item: &StmtFunctionDef) -> Format
|
||||
// requires that the parent be aware of how the child is formatted, which
|
||||
// is challenging. As a compromise, we break those expressions to avoid an
|
||||
// instability.
|
||||
write!(
|
||||
f,
|
||||
[return_annotation.format().with_options(Parentheses::Always)]
|
||||
)?;
|
||||
|
||||
return_annotation
|
||||
.format()
|
||||
.with_options(Parentheses::Always)
|
||||
.fmt(f)
|
||||
} else {
|
||||
let parenthesize = if parameters.is_empty() && !comments.has(parameters.as_ref()) {
|
||||
// If the parameters are empty, add parentheses if the return annotation
|
||||
// breaks at all.
|
||||
Parenthesize::IfBreaksOrIfRequired
|
||||
// If the parameters are empty, add parentheses around literal expressions
|
||||
// (any non splitable expression) but avoid parenthesizing subscripts and
|
||||
// other parenthesized expressions unless necessary.
|
||||
Parenthesize::IfBreaksParenthesized
|
||||
} else {
|
||||
// Otherwise, use our normal rules for parentheses, which allows us to break
|
||||
// like:
|
||||
@@ -179,17 +180,11 @@ fn format_function_header(f: &mut PyFormatter, item: &StmtFunctionDef) -> Format
|
||||
// ```
|
||||
Parenthesize::IfBreaks
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
[maybe_parenthesize_expression(
|
||||
return_annotation,
|
||||
item,
|
||||
parenthesize
|
||||
)]
|
||||
)?;
|
||||
maybe_parenthesize_expression(return_annotation, item, parenthesize).fmt(f)
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
group(&format_inner).fmt(f)
|
||||
|
||||
@@ -155,20 +155,7 @@ def SimplePyFn(
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -29,14 +29,18 @@
|
||||
|
||||
|
||||
# magic trailing comma in return type, no params
|
||||
-def a() -> tuple[
|
||||
- a,
|
||||
- b,
|
||||
-]: ...
|
||||
+def a() -> (
|
||||
+ tuple[
|
||||
+ a,
|
||||
+ b,
|
||||
+ ]
|
||||
+): ...
|
||||
@@ -36,7 +36,9 @@
|
||||
|
||||
|
||||
# magic trailing comma in return type, params
|
||||
@@ -179,26 +166,7 @@ def SimplePyFn(
|
||||
p,
|
||||
q,
|
||||
]:
|
||||
@@ -68,11 +72,13 @@
|
||||
|
||||
|
||||
# long return type, no param list
|
||||
-def foo() -> list[
|
||||
- Loooooooooooooooooooooooooooooooooooong,
|
||||
- Loooooooooooooooooooong,
|
||||
- Looooooooooooong,
|
||||
-]: ...
|
||||
+def foo() -> (
|
||||
+ list[
|
||||
+ Loooooooooooooooooooooooooooooooooooong,
|
||||
+ Loooooooooooooooooooong,
|
||||
+ Looooooooooooong,
|
||||
+ ]
|
||||
+): ...
|
||||
|
||||
|
||||
# long function name, no param list, no return value
|
||||
@@ -93,7 +99,11 @@
|
||||
@@ -93,7 +95,11 @@
|
||||
|
||||
|
||||
# unskippable type hint (??)
|
||||
@@ -211,7 +179,7 @@ def SimplePyFn(
|
||||
pass
|
||||
|
||||
|
||||
@@ -112,7 +122,13 @@
|
||||
@@ -112,7 +118,13 @@
|
||||
|
||||
|
||||
# don't lose any comments (no magic)
|
||||
@@ -226,7 +194,7 @@ def SimplePyFn(
|
||||
... # 6
|
||||
|
||||
|
||||
@@ -120,12 +136,18 @@
|
||||
@@ -120,12 +132,18 @@
|
||||
def foo( # 1
|
||||
a, # 2
|
||||
b,
|
||||
@@ -283,12 +251,10 @@ def foo(
|
||||
|
||||
|
||||
# magic trailing comma in return type, no params
|
||||
def a() -> (
|
||||
tuple[
|
||||
a,
|
||||
b,
|
||||
]
|
||||
): ...
|
||||
def a() -> tuple[
|
||||
a,
|
||||
b,
|
||||
]: ...
|
||||
|
||||
|
||||
# magic trailing comma in return type, params
|
||||
@@ -326,13 +292,11 @@ def aaaaaaaaaaaaaaaaa(
|
||||
|
||||
|
||||
# long return type, no param list
|
||||
def foo() -> (
|
||||
list[
|
||||
Loooooooooooooooooooooooooooooooooooong,
|
||||
Loooooooooooooooooooong,
|
||||
Looooooooooooong,
|
||||
]
|
||||
): ...
|
||||
def foo() -> list[
|
||||
Loooooooooooooooooooooooooooooooooooong,
|
||||
Loooooooooooooooooooong,
|
||||
Looooooooooooong,
|
||||
]: ...
|
||||
|
||||
|
||||
# long function name, no param list, no return value
|
||||
@@ -592,5 +556,3 @@ def SimplePyFn(
|
||||
Buffer[UInt8, 2],
|
||||
]: ...
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -88,30 +88,6 @@ func([1, 2, 3,], bar)
|
||||
|
||||
func([(x, y,) for (x, y) in z], bar)
|
||||
|
||||
# Ensure that return type annotations (which use `parenthesize_if_expands`) are also hugged.
|
||||
def func() -> [1, 2, 3,]:
|
||||
pass
|
||||
|
||||
def func() -> ([1, 2, 3,]):
|
||||
pass
|
||||
|
||||
def func() -> ([1, 2, 3,]):
|
||||
pass
|
||||
|
||||
def func() -> ( # comment
|
||||
[1, 2, 3,]):
|
||||
pass
|
||||
|
||||
def func() -> (
|
||||
[1, 2, 3,] # comment
|
||||
):
|
||||
pass
|
||||
|
||||
def func() -> (
|
||||
[1, 2, 3,]
|
||||
# comment
|
||||
):
|
||||
pass
|
||||
|
||||
# Ensure that nested lists are hugged.
|
||||
func([
|
||||
@@ -329,68 +305,6 @@ func(
|
||||
)
|
||||
|
||||
|
||||
# Ensure that return type annotations (which use `parenthesize_if_expands`) are also hugged.
|
||||
def func() -> (
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func() -> (
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func() -> (
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func() -> ( # comment
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func() -> (
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
] # comment
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func() -> (
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
# comment
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Ensure that nested lists are hugged.
|
||||
func(
|
||||
[
|
||||
@@ -611,56 +525,7 @@ func(
|
||||
|
||||
foo(
|
||||
# comment
|
||||
@@ -167,33 +145,27 @@
|
||||
|
||||
|
||||
# Ensure that return type annotations (which use `parenthesize_if_expands`) are also hugged.
|
||||
-def func() -> (
|
||||
- [
|
||||
- 1,
|
||||
- 2,
|
||||
- 3,
|
||||
- ]
|
||||
-):
|
||||
+def func() -> ([
|
||||
+ 1,
|
||||
+ 2,
|
||||
+ 3,
|
||||
+]):
|
||||
pass
|
||||
|
||||
|
||||
-def func() -> (
|
||||
- [
|
||||
- 1,
|
||||
- 2,
|
||||
- 3,
|
||||
- ]
|
||||
-):
|
||||
+def func() -> ([
|
||||
+ 1,
|
||||
+ 2,
|
||||
+ 3,
|
||||
+]):
|
||||
pass
|
||||
|
||||
|
||||
-def func() -> (
|
||||
- [
|
||||
- 1,
|
||||
- 2,
|
||||
- 3,
|
||||
- ]
|
||||
-):
|
||||
+def func() -> ([
|
||||
+ 1,
|
||||
+ 2,
|
||||
+ 3,
|
||||
+]):
|
||||
pass
|
||||
|
||||
|
||||
@@ -229,56 +201,46 @@
|
||||
@@ -167,56 +145,46 @@
|
||||
|
||||
|
||||
# Ensure that nested lists are hugged.
|
||||
@@ -747,6 +612,3 @@ func(
|
||||
-)
|
||||
+])
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -521,4 +521,67 @@ def process_board_action(
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -131,32 +131,24 @@
|
||||
|
||||
# Breaking return type annotations. Black adds parentheses if the parameters are
|
||||
# empty; otherwise, it leverages the expressions own parentheses if possible.
|
||||
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
- Set[
|
||||
- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
- ]
|
||||
-): ...
|
||||
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
|
||||
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
+]: ...
|
||||
|
||||
|
||||
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
- Set[
|
||||
- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
- ]
|
||||
-): ...
|
||||
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
|
||||
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
+]: ...
|
||||
|
||||
|
||||
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
- Set[
|
||||
- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
- ]
|
||||
-): ...
|
||||
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
|
||||
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
+]: ...
|
||||
|
||||
|
||||
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
- Set[
|
||||
- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
- ]
|
||||
-): ...
|
||||
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
|
||||
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
+]: ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
@@ -257,11 +249,8 @@
|
||||
): ...
|
||||
|
||||
|
||||
-def double() -> (
|
||||
- first_item
|
||||
- and foo.bar.baz().bop(
|
||||
- 1,
|
||||
- )
|
||||
+def double() -> first_item and foo.bar.baz().bop(
|
||||
+ 1,
|
||||
):
|
||||
return 2 * a
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,492 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/return_type_no_parameters.py
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
# Tests for functions without parameters or a dangling comment
|
||||
# Black's overall behavior is to:
|
||||
# 1. Print the return type on the same line as the function header if it fits
|
||||
# 2. Parenthesize the return type if it doesn't fit.
|
||||
# The exception to this are subscripts, see below
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Return types that use NeedsParantheses::BestFit layout with the exception of subscript
|
||||
#########################################################################################
|
||||
# String return type that fits on the same line
|
||||
def no_parameters_string_return_type() -> "ALongIdentifierButDoesntGetParenthesized":
|
||||
pass
|
||||
|
||||
|
||||
# String return type that exceeds the line length
|
||||
def no_parameters_overlong_string_return_type() -> (
|
||||
"ALongIdentifierButDoesntGetParenthesized"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that fits on the same line as the function header
|
||||
def no_parameters_name_return_type() -> ALongIdentifierButDoesntGetParenthesized:
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that exceeds the configured line width
|
||||
def no_parameters_overlong_name_return_type() -> (
|
||||
ALongIdentifierButDoesntGetParenthesized
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Unions
|
||||
#########################################################################################
|
||||
|
||||
def test_return_overlong_union() -> (
|
||||
A | B | C | DDDDDDDDDDDDDDDDDDDDDDDD | EEEEEEEEEEEEEEEEEEEEEE
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def test_return_union_with_elements_exceeding_length() -> (
|
||||
A
|
||||
| B
|
||||
| Ccccccccccccccccccccccccccccccccc
|
||||
| DDDDDDDDDDDDDDDDDDDDDDDD
|
||||
| EEEEEEEEEEEEEEEEEEEEEE
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Multiline strings (NeedsParentheses::Never)
|
||||
#########################################################################################
|
||||
|
||||
def test_return_multiline_string_type_annotation() -> """str
|
||||
| list[str]
|
||||
""":
|
||||
pass
|
||||
|
||||
|
||||
def test_return_multiline_string_binary_expression_return_type_annotation() -> """str
|
||||
| list[str]
|
||||
""" + "b":
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Implicit concatenated strings (NeedsParentheses::Multiline)
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_implicit_concatenated_string_return_type() -> "str" "bbbbbbbbbbbbbbbb":
|
||||
pass
|
||||
|
||||
|
||||
def test_overlong_implicit_concatenated_string_return_type() -> (
|
||||
"liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def test_extralong_implicit_concatenated_string_return_type() -> (
|
||||
"liiiiiiiiiiiisssssst[str]"
|
||||
"bbbbbbbbbbbbbbbbbbbb"
|
||||
"cccccccccccccccccccccccccccccccccccccc"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Subscript
|
||||
#########################################################################################
|
||||
def no_parameters_subscript_return_type() -> list[str]:
|
||||
pass
|
||||
|
||||
|
||||
# 1. Black tries to keep the list flat by parenthesizing the list as shown below even when the `list` identifier
|
||||
# fits on the header line. IMO, this adds unnecessary parentheses that can be avoided
|
||||
# and supporting it requires extra complexity (best_fitting! layout)
|
||||
def no_parameters_overlong_subscript_return_type_with_single_element() -> (
|
||||
list[xxxxxxxxxxxxxxxxxxxxx]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# 2. Black: Removes the parentheses when the subscript fits after breaking individual elements.
|
||||
# This is somewhat wasteful because the below list actually fits on a single line when splitting after
|
||||
# `list[`. It is also inconsistent with how subscripts are normally formatted where it first tries to fit the entire subscript,
|
||||
# then splits after `list[` but keeps all elements on a single line, and finally, splits after each element.
|
||||
# IMO: Splitting after the `list[` and trying to keep the elements together when possible seems more consistent.
|
||||
def no_parameters_subscript_return_type_multiple_elements() -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
# Black removes the parentheses even the elements exceed the configured line width.
|
||||
# So does Ruff.
|
||||
def no_parameters_subscript_return_type_multiple_overlong_elements() -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
# Black parenthesizes the subscript if its name doesn't fit on the header line.
|
||||
# So does Ruff
|
||||
def no_parameters_subscriptreturn_type_with_overlong_value_() -> (
|
||||
liiiiiiiiiiiiiiiiiiiiist[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black: It removes the parentheses when the subscript contains multiple elements as
|
||||
# `no_parameters_subscript_return_type_multiple_overlong_elements` shows. However, it doesn't
|
||||
# when the subscript contains a single element. Black then keeps the parentheses.
|
||||
# Ruff removes the parentheses in this case for consistency.
|
||||
def no_parameters_overlong_subscript_return_type_with_overlong_single_element() -> (
|
||||
list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# can_omit_optional_parentheses_layout
|
||||
#########################################################################################
|
||||
|
||||
def test_binary_expression_return_type_annotation() -> aaaaaaaaaaaaaaaaaaaaaaaaaa > [
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbb,
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Other
|
||||
#########################################################################################
|
||||
|
||||
# Don't paranthesize lists
|
||||
def f() -> [
|
||||
a,
|
||||
b,
|
||||
]: pass
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
# Tests for functions without parameters or a dangling comment
|
||||
# Black's overall behavior is to:
|
||||
# 1. Print the return type on the same line as the function header if it fits
|
||||
# 2. Parenthesize the return type if it doesn't fit.
|
||||
# The exception to this are subscripts, see below
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Return types that use NeedsParantheses::BestFit layout with the exception of subscript
|
||||
#########################################################################################
|
||||
# String return type that fits on the same line
|
||||
def no_parameters_string_return_type() -> "ALongIdentifierButDoesntGetParenthesized":
|
||||
pass
|
||||
|
||||
|
||||
# String return type that exceeds the line length
|
||||
def no_parameters_overlong_string_return_type() -> (
|
||||
"ALongIdentifierButDoesntGetParenthesized"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that fits on the same line as the function header
|
||||
def no_parameters_name_return_type() -> ALongIdentifierButDoesntGetParenthesized:
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that exceeds the configured line width
|
||||
def no_parameters_overlong_name_return_type() -> (
|
||||
ALongIdentifierButDoesntGetParenthesized
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Unions
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_return_overlong_union() -> (
|
||||
A | B | C | DDDDDDDDDDDDDDDDDDDDDDDD | EEEEEEEEEEEEEEEEEEEEEE
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def test_return_union_with_elements_exceeding_length() -> (
|
||||
A
|
||||
| B
|
||||
| Ccccccccccccccccccccccccccccccccc
|
||||
| DDDDDDDDDDDDDDDDDDDDDDDD
|
||||
| EEEEEEEEEEEEEEEEEEEEEE
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Multiline strings (NeedsParentheses::Never)
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_return_multiline_string_type_annotation() -> (
|
||||
"""str
|
||||
| list[str]
|
||||
"""
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def test_return_multiline_string_binary_expression_return_type_annotation() -> (
|
||||
"""str
|
||||
| list[str]
|
||||
"""
|
||||
+ "b"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Implicit concatenated strings (NeedsParentheses::Multiline)
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_implicit_concatenated_string_return_type() -> "str" "bbbbbbbbbbbbbbbb":
|
||||
pass
|
||||
|
||||
|
||||
def test_overlong_implicit_concatenated_string_return_type() -> (
|
||||
"liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def test_extralong_implicit_concatenated_string_return_type() -> (
|
||||
"liiiiiiiiiiiisssssst[str]"
|
||||
"bbbbbbbbbbbbbbbbbbbb"
|
||||
"cccccccccccccccccccccccccccccccccccccc"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Subscript
|
||||
#########################################################################################
|
||||
def no_parameters_subscript_return_type() -> list[str]:
|
||||
pass
|
||||
|
||||
|
||||
# 1. Black tries to keep the list flat by parenthesizing the list as shown below even when the `list` identifier
|
||||
# fits on the header line. IMO, this adds unnecessary parentheses that can be avoided
|
||||
# and supporting it requires extra complexity (best_fitting! layout)
|
||||
def no_parameters_overlong_subscript_return_type_with_single_element() -> (
|
||||
list[xxxxxxxxxxxxxxxxxxxxx]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# 2. Black: Removes the parentheses when the subscript fits after breaking individual elements.
|
||||
# This is somewhat wasteful because the below list actually fits on a single line when splitting after
|
||||
# `list[`. It is also inconsistent with how subscripts are normally formatted where it first tries to fit the entire subscript,
|
||||
# then splits after `list[` but keeps all elements on a single line, and finally, splits after each element.
|
||||
# IMO: Splitting after the `list[` and trying to keep the elements together when possible seems more consistent.
|
||||
def no_parameters_subscript_return_type_multiple_elements() -> (
|
||||
list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black removes the parentheses even the elements exceed the configured line width.
|
||||
# So does Ruff.
|
||||
def no_parameters_subscript_return_type_multiple_overlong_elements() -> (
|
||||
list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black parenthesizes the subscript if its name doesn't fit on the header line.
|
||||
# So does Ruff
|
||||
def no_parameters_subscriptreturn_type_with_overlong_value_() -> (
|
||||
liiiiiiiiiiiiiiiiiiiiist[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black: It removes the parentheses when the subscript contains multiple elements as
|
||||
# `no_parameters_subscript_return_type_multiple_overlong_elements` shows. However, it doesn't
|
||||
# when the subscript contains a single element. Black then keeps the parentheses.
|
||||
# Ruff removes the parentheses in this case for consistency.
|
||||
def no_parameters_overlong_subscript_return_type_with_overlong_single_element() -> (
|
||||
list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# can_omit_optional_parentheses_layout
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_binary_expression_return_type_annotation() -> (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
> [
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbb,
|
||||
]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Other
|
||||
#########################################################################################
|
||||
|
||||
|
||||
# Don't paranthesize lists
|
||||
def f() -> (
|
||||
[
|
||||
a,
|
||||
b,
|
||||
]
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -58,11 +58,9 @@
|
||||
#########################################################################################
|
||||
|
||||
|
||||
-def test_return_multiline_string_type_annotation() -> (
|
||||
- """str
|
||||
+def test_return_multiline_string_type_annotation() -> """str
|
||||
| list[str]
|
||||
-"""
|
||||
-):
|
||||
+""":
|
||||
pass
|
||||
|
||||
|
||||
@@ -108,9 +106,9 @@
|
||||
# 1. Black tries to keep the list flat by parenthesizing the list as shown below even when the `list` identifier
|
||||
# fits on the header line. IMO, this adds unnecessary parentheses that can be avoided
|
||||
# and supporting it requires extra complexity (best_fitting! layout)
|
||||
-def no_parameters_overlong_subscript_return_type_with_single_element() -> (
|
||||
- list[xxxxxxxxxxxxxxxxxxxxx]
|
||||
-):
|
||||
+def no_parameters_overlong_subscript_return_type_with_single_element() -> list[
|
||||
+ xxxxxxxxxxxxxxxxxxxxx
|
||||
+]:
|
||||
pass
|
||||
|
||||
|
||||
@@ -119,23 +117,18 @@
|
||||
# `list[`. It is also inconsistent with how subscripts are normally formatted where it first tries to fit the entire subscript,
|
||||
# then splits after `list[` but keeps all elements on a single line, and finally, splits after each element.
|
||||
# IMO: Splitting after the `list[` and trying to keep the elements together when possible seems more consistent.
|
||||
-def no_parameters_subscript_return_type_multiple_elements() -> (
|
||||
- list[
|
||||
- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
- ]
|
||||
-):
|
||||
+def no_parameters_subscript_return_type_multiple_elements() -> list[
|
||||
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
+]:
|
||||
pass
|
||||
|
||||
|
||||
# Black removes the parentheses even the elements exceed the configured line width.
|
||||
# So does Ruff.
|
||||
-def no_parameters_subscript_return_type_multiple_overlong_elements() -> (
|
||||
- list[
|
||||
- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
- ]
|
||||
-):
|
||||
+def no_parameters_subscript_return_type_multiple_overlong_elements() -> list[
|
||||
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
+]:
|
||||
pass
|
||||
|
||||
|
||||
@@ -154,11 +147,9 @@
|
||||
# `no_parameters_subscript_return_type_multiple_overlong_elements` shows. However, it doesn't
|
||||
# when the subscript contains a single element. Black then keeps the parentheses.
|
||||
# Ruff removes the parentheses in this case for consistency.
|
||||
-def no_parameters_overlong_subscript_return_type_with_overlong_single_element() -> (
|
||||
- list[
|
||||
- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
- ]
|
||||
-):
|
||||
+def no_parameters_overlong_subscript_return_type_with_overlong_single_element() -> list[
|
||||
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
+]:
|
||||
pass
|
||||
|
||||
|
||||
@@ -167,13 +158,10 @@
|
||||
#########################################################################################
|
||||
|
||||
|
||||
-def test_binary_expression_return_type_annotation() -> (
|
||||
- aaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
- > [
|
||||
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
- bbbbbbbbbbbbbbbbbbbbbbbbb,
|
||||
- ]
|
||||
-):
|
||||
+def test_binary_expression_return_type_annotation() -> aaaaaaaaaaaaaaaaaaaaaaaaaa > [
|
||||
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
+ bbbbbbbbbbbbbbbbbbbbbbbbb,
|
||||
+]:
|
||||
pass
|
||||
|
||||
|
||||
@@ -183,10 +171,8 @@
|
||||
|
||||
|
||||
# Don't paranthesize lists
|
||||
-def f() -> (
|
||||
- [
|
||||
- a,
|
||||
- b,
|
||||
- ]
|
||||
-):
|
||||
+def f() -> [
|
||||
+ a,
|
||||
+ b,
|
||||
+]:
|
||||
pass
|
||||
```
|
||||
@@ -0,0 +1,414 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/return_type_parameters.py
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
# Tests for functions with parameters.
|
||||
# The main difference to functions without parameters is that the return type never gets
|
||||
# parenthesized for values that can't be split (NeedsParentheses::BestFit).
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Return types that use NeedsParantheses::BestFit layout with the exception of subscript
|
||||
#########################################################################################
|
||||
# String return type that fits on the same line
|
||||
def parameters_string_return_type(a) -> "ALongIdentifierButDoesntGetParenthesized":
|
||||
pass
|
||||
|
||||
|
||||
# String return type that exceeds the line length
|
||||
def parameters_overlong_string_return_type(
|
||||
a,
|
||||
) -> "ALongIdentifierButDoesntGetParenthesized":
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that fits on the same line as the function header
|
||||
def parameters_name_return_type(a) -> ALongIdentifierButDoesntGetParenthesized:
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that exceeds the configured line width
|
||||
def parameters_overlong_name_return_type(
|
||||
a,
|
||||
) -> ALongIdentifierButDoesntGetParenthesized:
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Unions
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_return_overlong_union(
|
||||
a,
|
||||
) -> A | B | C | DDDDDDDDDDDDDDDDDDDDDDDD | EEEEEEEEEEEEEEEEEEEEEE:
|
||||
pass
|
||||
|
||||
|
||||
def test_return_union_with_elements_exceeding_length(
|
||||
a,
|
||||
) -> (
|
||||
A
|
||||
| B
|
||||
| Ccccccccccccccccccccccccccccccccc
|
||||
| DDDDDDDDDDDDDDDDDDDDDDDD
|
||||
| EEEEEEEEEEEEEEEEEEEEEE
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Multiline stirngs (NeedsParentheses::Never)
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_return_multiline_string_type_annotation(a) -> """str
|
||||
| list[str]
|
||||
""":
|
||||
pass
|
||||
|
||||
|
||||
def test_return_multiline_string_binary_expression_return_type_annotation(a) -> """str
|
||||
| list[str]
|
||||
""" + "b":
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Implicit concatenated strings (NeedsParentheses::Multiline)
|
||||
#########################################################################################
|
||||
|
||||
def test_implicit_concatenated_string_return_type(a) -> "str" "bbbbbbbbbbbbbbbb":
|
||||
pass
|
||||
|
||||
|
||||
def test_overlong_implicit_concatenated_string_return_type(
|
||||
a,
|
||||
) -> "liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb":
|
||||
pass
|
||||
|
||||
|
||||
def test_extralong_implicit_concatenated_string_return_type(
|
||||
a,
|
||||
) -> (
|
||||
"liiiiiiiiiiiisssssst[str]"
|
||||
"bbbbbbbbbbbbbbbbbbbb"
|
||||
"cccccccccccccccccccccccccccccccccccccc"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Subscript
|
||||
#########################################################################################
|
||||
def parameters_subscript_return_type(a) -> list[str]:
|
||||
pass
|
||||
|
||||
|
||||
# Unlike with no-parameters, the return type gets never parenthesized.
|
||||
def parameters_overlong_subscript_return_type_with_single_element(
|
||||
a
|
||||
) -> list[xxxxxxxxxxxxxxxxxxxxx]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_subscript_return_type_multiple_elements(a) -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_subscript_return_type_multiple_overlong_elements(a) -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_subscriptreturn_type_with_overlong_value_(
|
||||
a
|
||||
) -> liiiiiiiiiiiiiiiiiiiiist[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_overlong_subscript_return_type_with_overlong_single_element(
|
||||
a
|
||||
) -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
# Not even in this very ridiculous case
|
||||
def a():
|
||||
def b():
|
||||
def c():
|
||||
def d():
|
||||
def e():
|
||||
def f():
|
||||
def g():
|
||||
def h():
|
||||
def i():
|
||||
def j():
|
||||
def k():
|
||||
def l():
|
||||
def m():
|
||||
def n():
|
||||
def o():
|
||||
def p():
|
||||
def q():
|
||||
def r():
|
||||
def s():
|
||||
def t():
|
||||
def u():
|
||||
def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeedooooong(
|
||||
a,
|
||||
) -> list[
|
||||
int,
|
||||
float
|
||||
]: ...
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Magic comma in return type
|
||||
#########################################################################################
|
||||
|
||||
# Black only splits the return type. Ruff also breaks the parameters. This is probably a bug.
|
||||
def parameters_subscriptreturn_type_with_overlong_value_(a) -> liiiiiiiiiiiiiiiiiiiiist[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# can_omit_optional_parentheses_layout
|
||||
#########################################################################################
|
||||
|
||||
def test_return_multiline_string_binary_expression_return_type_annotation(
|
||||
a,
|
||||
) -> aaaaaaaaaaaaaaaaaaaaaaaaaa > [
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbb,
|
||||
]:
|
||||
pass
|
||||
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
# Tests for functions with parameters.
|
||||
# The main difference to functions without parameters is that the return type never gets
|
||||
# parenthesized for values that can't be split (NeedsParentheses::BestFit).
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Return types that use NeedsParantheses::BestFit layout with the exception of subscript
|
||||
#########################################################################################
|
||||
# String return type that fits on the same line
|
||||
def parameters_string_return_type(a) -> "ALongIdentifierButDoesntGetParenthesized":
|
||||
pass
|
||||
|
||||
|
||||
# String return type that exceeds the line length
|
||||
def parameters_overlong_string_return_type(
|
||||
a,
|
||||
) -> "ALongIdentifierButDoesntGetParenthesized":
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that fits on the same line as the function header
|
||||
def parameters_name_return_type(a) -> ALongIdentifierButDoesntGetParenthesized:
|
||||
pass
|
||||
|
||||
|
||||
# Name return type that exceeds the configured line width
|
||||
def parameters_overlong_name_return_type(
|
||||
a,
|
||||
) -> ALongIdentifierButDoesntGetParenthesized:
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Unions
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_return_overlong_union(
|
||||
a,
|
||||
) -> A | B | C | DDDDDDDDDDDDDDDDDDDDDDDD | EEEEEEEEEEEEEEEEEEEEEE:
|
||||
pass
|
||||
|
||||
|
||||
def test_return_union_with_elements_exceeding_length(
|
||||
a,
|
||||
) -> (
|
||||
A
|
||||
| B
|
||||
| Ccccccccccccccccccccccccccccccccc
|
||||
| DDDDDDDDDDDDDDDDDDDDDDDD
|
||||
| EEEEEEEEEEEEEEEEEEEEEE
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Multiline stirngs (NeedsParentheses::Never)
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_return_multiline_string_type_annotation(
|
||||
a,
|
||||
) -> """str
|
||||
| list[str]
|
||||
""":
|
||||
pass
|
||||
|
||||
|
||||
def test_return_multiline_string_binary_expression_return_type_annotation(
|
||||
a,
|
||||
) -> (
|
||||
"""str
|
||||
| list[str]
|
||||
"""
|
||||
+ "b"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Implicit concatenated strings (NeedsParentheses::Multiline)
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_implicit_concatenated_string_return_type(a) -> "str" "bbbbbbbbbbbbbbbb":
|
||||
pass
|
||||
|
||||
|
||||
def test_overlong_implicit_concatenated_string_return_type(
|
||||
a,
|
||||
) -> "liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb":
|
||||
pass
|
||||
|
||||
|
||||
def test_extralong_implicit_concatenated_string_return_type(
|
||||
a,
|
||||
) -> (
|
||||
"liiiiiiiiiiiisssssst[str]"
|
||||
"bbbbbbbbbbbbbbbbbbbb"
|
||||
"cccccccccccccccccccccccccccccccccccccc"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Subscript
|
||||
#########################################################################################
|
||||
def parameters_subscript_return_type(a) -> list[str]:
|
||||
pass
|
||||
|
||||
|
||||
# Unlike with no-parameters, the return type gets never parenthesized.
|
||||
def parameters_overlong_subscript_return_type_with_single_element(
|
||||
a,
|
||||
) -> list[xxxxxxxxxxxxxxxxxxxxx]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_subscript_return_type_multiple_elements(
|
||||
a,
|
||||
) -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_subscript_return_type_multiple_overlong_elements(
|
||||
a,
|
||||
) -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_subscriptreturn_type_with_overlong_value_(
|
||||
a,
|
||||
) -> liiiiiiiiiiiiiiiiiiiiist[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def parameters_overlong_subscript_return_type_with_overlong_single_element(
|
||||
a,
|
||||
) -> list[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
# Not even in this very ridiculous case
|
||||
def a():
|
||||
def b():
|
||||
def c():
|
||||
def d():
|
||||
def e():
|
||||
def f():
|
||||
def g():
|
||||
def h():
|
||||
def i():
|
||||
def j():
|
||||
def k():
|
||||
def l():
|
||||
def m():
|
||||
def n():
|
||||
def o():
|
||||
def p():
|
||||
def q():
|
||||
def r():
|
||||
def s():
|
||||
def t():
|
||||
def u():
|
||||
def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeedooooong(
|
||||
a,
|
||||
) -> list[
|
||||
int,
|
||||
float,
|
||||
]: ...
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# Magic comma in return type
|
||||
#########################################################################################
|
||||
|
||||
|
||||
# Black only splits the return type. Ruff also breaks the parameters. This is probably a bug.
|
||||
def parameters_subscriptreturn_type_with_overlong_value_(
|
||||
a,
|
||||
) -> liiiiiiiiiiiiiiiiiiiiist[
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# can_omit_optional_parentheses_layout
|
||||
#########################################################################################
|
||||
|
||||
|
||||
def test_return_multiline_string_binary_expression_return_type_annotation(
|
||||
a,
|
||||
) -> aaaaaaaaaaaaaaaaaaaaaaaaaa > [
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbb,
|
||||
]:
|
||||
pass
|
||||
```
|
||||
@@ -215,7 +215,9 @@ impl Configuration {
|
||||
let analyze_defaults = AnalyzeSettings::default();
|
||||
|
||||
let analyze = AnalyzeSettings {
|
||||
exclude: FilePatternSet::try_from_iter(analyze.exclude.unwrap_or_default())?,
|
||||
preview: analyze_preview,
|
||||
target_version,
|
||||
extension: self.extension.clone().unwrap_or_default(),
|
||||
detect_string_imports: analyze
|
||||
.detect_string_imports
|
||||
@@ -1218,7 +1220,9 @@ impl FormatConfiguration {
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AnalyzeConfiguration {
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
pub preview: Option<PreviewMode>,
|
||||
|
||||
pub direction: Option<Direction>,
|
||||
pub detect_string_imports: Option<bool>,
|
||||
pub include_dependencies: Option<BTreeMap<PathBuf, (PathBuf, Vec<String>)>>,
|
||||
@@ -1228,6 +1232,15 @@ impl AnalyzeConfiguration {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn from_options(options: AnalyzeOptions, project_root: &Path) -> Result<Self> {
|
||||
Ok(Self {
|
||||
exclude: options.exclude.map(|paths| {
|
||||
paths
|
||||
.into_iter()
|
||||
.map(|pattern| {
|
||||
let absolute = fs::normalize_path_to(&pattern, project_root);
|
||||
FilePattern::User(pattern, absolute)
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
preview: options.preview.map(PreviewMode::from),
|
||||
direction: options.direction,
|
||||
detect_string_imports: options.detect_string_imports,
|
||||
@@ -1246,6 +1259,7 @@ impl AnalyzeConfiguration {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn combine(self, config: Self) -> Self {
|
||||
Self {
|
||||
exclude: self.exclude.or(config.exclude),
|
||||
preview: self.preview.or(config.preview),
|
||||
direction: self.direction.or(config.direction),
|
||||
detect_string_imports: self.detect_string_imports.or(config.detect_string_imports),
|
||||
|
||||
@@ -3320,6 +3320,27 @@ pub struct FormatOptions {
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct AnalyzeOptions {
|
||||
/// A list of file patterns to exclude from analysis in addition to the files excluded globally (see [`exclude`](#exclude), and [`extend-exclude`](#extend-exclude)).
|
||||
///
|
||||
/// Exclusions are based on globs, and can be either:
|
||||
///
|
||||
/// - Single-path patterns, like `.mypy_cache` (to exclude any directory
|
||||
/// named `.mypy_cache` in the tree), `foo.py` (to exclude any file named
|
||||
/// `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ).
|
||||
/// - Relative patterns, like `directory/foo.py` (to exclude that specific
|
||||
/// file) or `directory/*.py` (to exclude any Python files in
|
||||
/// `directory`). Note that these paths are relative to the project root
|
||||
/// (e.g., the directory containing your `pyproject.toml`).
|
||||
///
|
||||
/// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "list[str]",
|
||||
example = r#"
|
||||
exclude = ["generated"]
|
||||
"#
|
||||
)]
|
||||
pub exclude: Option<Vec<String>>,
|
||||
/// Whether to enable preview mode. When preview mode is enabled, Ruff will expose unstable
|
||||
/// commands.
|
||||
#[option(
|
||||
|
||||
10
ruff.schema.json
generated
10
ruff.schema.json
generated
@@ -779,6 +779,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"exclude": {
|
||||
"description": "A list of file patterns to exclude from analysis in addition to the files excluded globally (see [`exclude`](#exclude), and [`extend-exclude`](#extend-exclude)).\n\nExclusions are based on globs, and can be either:\n\n- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). - Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py` (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"include-dependencies": {
|
||||
"description": "A map from file path to the list of file paths or globs that should be considered dependencies of that file, regardless of whether relevant imports are detected.",
|
||||
"type": [
|
||||
|
||||
Reference in New Issue
Block a user