diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3380958b0ef88..dbcd71e426708 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -59,7 +59,6 @@ jobs: - "!crates/ruff_python_formatter/**" - "!crates/ruff_formatter/**" - "!crates/ruff_dev/**" - - "!crates/ruff_shrinking/**" - scripts/* - python/** - .github/workflows/ci.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index d31fa46a143cb..74cf334405db3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,53 @@ # Changelog +## 0.4.3 + +### Enhancements + +- Add support for PEP 696 syntax ([#11120](https://github.com/astral-sh/ruff/pull/11120)) + +### Preview features + +- \[`refurb`\] Use function range for `reimplemented-operator` diagnostics ([#11271](https://github.com/astral-sh/ruff/pull/11271)) +- \[`refurb`\] Ignore methods in `reimplemented-operator` (`FURB118`) ([#11270](https://github.com/astral-sh/ruff/pull/11270)) +- \[`refurb`\] Implement `fstring-number-format` (`FURB116`) ([#10921](https://github.com/astral-sh/ruff/pull/10921)) +- \[`ruff`\] Implement `redirected-noqa` (`RUF101`) ([#11052](https://github.com/astral-sh/ruff/pull/11052)) +- \[`pyflakes`\] Distinguish between first-party and third-party imports for fix suggestions ([#11168](https://github.com/astral-sh/ruff/pull/11168)) + +### Rule changes + +- \[`flake8-bugbear`\] Ignore non-abstract class attributes when enforcing `B024` ([#11210](https://github.com/astral-sh/ruff/pull/11210)) +- \[`flake8-logging`\] Include inline instantiations when detecting loggers ([#11154](https://github.com/astral-sh/ruff/pull/11154)) +- \[`pylint`\] Also emit `PLR0206` for properties with variadic parameters ([#11200](https://github.com/astral-sh/ruff/pull/11200)) +- \[`ruff`\] Detect duplicate codes as part of `unused-noqa` (`RUF100`) ([#10850](https://github.com/astral-sh/ruff/pull/10850)) + +### Formatter + +- Avoid multiline expression if format specifier is present ([#11123](https://github.com/astral-sh/ruff/pull/11123)) + +### LSP + +- Write `ruff server` setup guide for Helix ([#11183](https://github.com/astral-sh/ruff/pull/11183)) +- `ruff server` no longer hangs after shutdown ([#11222](https://github.com/astral-sh/ruff/pull/11222)) +- `ruff server` reads from a configuration TOML file in the user configuration directory if no local configuration exists ([#11225](https://github.com/astral-sh/ruff/pull/11225)) +- `ruff server` respects `per-file-ignores` configuration ([#11224](https://github.com/astral-sh/ruff/pull/11224)) +- `ruff server`: Support a custom TOML configuration file ([#11140](https://github.com/astral-sh/ruff/pull/11140)) +- `ruff server`: Support setting to prioritize project configuration over editor configuration ([#11086](https://github.com/astral-sh/ruff/pull/11086)) + +### Bug fixes + +- Avoid debug assertion around NFKC renames ([#11249](https://github.com/astral-sh/ruff/pull/11249)) +- \[`pyflakes`\] Prioritize `redefined-while-unused` over `unused-import` ([#11173](https://github.com/astral-sh/ruff/pull/11173)) +- \[`ruff`\] Respect `async` expressions in comprehension bodies ([#11219](https://github.com/astral-sh/ruff/pull/11219)) +- \[`pygrep_hooks`\] Fix `blanket-noqa` panic when last line has noqa with no newline (`PGH004`) ([#11108](https://github.com/astral-sh/ruff/pull/11108)) +- \[`perflint`\] Ignore list-copy recommendations for async `for` loops ([#11250](https://github.com/astral-sh/ruff/pull/11250)) +- \[`pyflakes`\] Improve `invalid-print-syntax` documentation ([#11171](https://github.com/astral-sh/ruff/pull/11171)) + +### Performance + +- Avoid allocations for isort module names ([#11251](https://github.com/astral-sh/ruff/pull/11251)) +- Build a separate ARM wheel for macOS ([#11149](https://github.com/astral-sh/ruff/pull/11149)) + ## 0.4.2 ### Rule changes diff --git a/Cargo.lock b/Cargo.lock index 134ccad44a7fd..0a94b85a3f5d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1943,7 +1943,7 @@ dependencies = [ [[package]] name = "ruff" -version = "0.4.2" +version = "0.4.3" dependencies = [ "anyhow", "argfile", @@ -2104,7 +2104,7 @@ dependencies = [ [[package]] name = "ruff_linter" -version = "0.4.2" +version = "0.4.3" dependencies = [ "aho-corasick", "annotate-snippets 0.9.2", @@ -2373,6 +2373,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", + "shellexpand", "tracing", "walkdir", ] diff --git a/README.md b/README.md index 46bfed4e710f4..446aaeae47c84 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.4.2 + rev: v0.4.3 hooks: # Run the linter. - id: ruff diff --git a/crates/red_knot/src/symbols.rs b/crates/red_knot/src/symbols.rs index 182bb5f79cf9d..2406b414ed356 100644 --- a/crates/red_knot/src/symbols.rs +++ b/crates/red_knot/src/symbols.rs @@ -68,7 +68,7 @@ pub(crate) struct Scope { name: Name, kind: ScopeKind, child_scopes: Vec, - // symbol IDs, hashed by symbol name + /// symbol IDs, hashed by symbol name symbols_by_name: Map, } @@ -107,6 +107,7 @@ bitflags! { pub(crate) struct Symbol { name: Name, flags: SymbolFlags, + scope_id: ScopeId, // kind: Kind, } @@ -141,7 +142,7 @@ pub(crate) enum Definition { // the small amount of information we need from the AST. Import(ImportDefinition), ImportFrom(ImportFromDefinition), - ClassDef(TypedNodeKey), + ClassDef(ClassDefinition), FunctionDef(TypedNodeKey), Assignment(TypedNodeKey), AnnotatedAssignment(TypedNodeKey), @@ -174,6 +175,12 @@ impl ImportFromDefinition { } } +#[derive(Clone, Debug)] +pub(crate) struct ClassDefinition { + pub(crate) node_key: TypedNodeKey, + pub(crate) scope_id: ScopeId, +} + #[derive(Debug, Clone)] pub enum Dependency { Module(ModuleName), @@ -332,7 +339,11 @@ impl SymbolTable { *entry.key() } RawEntryMut::Vacant(entry) => { - let id = self.symbols_by_id.push(Symbol { name, flags }); + let id = self.symbols_by_id.push(Symbol { + name, + flags, + scope_id, + }); entry.insert_with_hasher(hash, id, (), |_| hash); id } @@ -459,8 +470,8 @@ impl SymbolTableBuilder { symbol_id } - fn push_scope(&mut self, child_of: ScopeId, name: &str, kind: ScopeKind) -> ScopeId { - let scope_id = self.table.add_child_scope(child_of, name, kind); + fn push_scope(&mut self, name: &str, kind: ScopeKind) -> ScopeId { + let scope_id = self.table.add_child_scope(self.cur_scope(), name, kind); self.scopes.push(scope_id); scope_id } @@ -482,10 +493,10 @@ impl SymbolTableBuilder { &mut self, name: &str, params: &Option>, - nested: impl FnOnce(&mut Self), - ) { + nested: impl FnOnce(&mut Self) -> ScopeId, + ) -> ScopeId { if let Some(type_params) = params { - self.push_scope(self.cur_scope(), name, ScopeKind::Annotation); + self.push_scope(name, ScopeKind::Annotation); for type_param in &type_params.type_params { let name = match type_param { ast::TypeParam::TypeVar(ast::TypeParamTypeVar { name, .. }) => name, @@ -495,10 +506,11 @@ impl SymbolTableBuilder { self.add_or_update_symbol(name, SymbolFlags::IS_DEFINED); } } - nested(self); + let scope_id = nested(self); if params.is_some() { self.pop_scope(); } + scope_id } } @@ -525,21 +537,26 @@ impl PreorderVisitor<'_> for SymbolTableBuilder { // TODO need to capture more definition statements here match stmt { ast::Stmt::ClassDef(node) => { - let def = Definition::ClassDef(TypedNodeKey::from_node(node)); - self.add_or_update_symbol_with_def(&node.name, def); - self.with_type_params(&node.name, &node.type_params, |builder| { - builder.push_scope(builder.cur_scope(), &node.name, ScopeKind::Class); + let scope_id = self.with_type_params(&node.name, &node.type_params, |builder| { + let scope_id = builder.push_scope(&node.name, ScopeKind::Class); ast::visitor::preorder::walk_stmt(builder, stmt); builder.pop_scope(); + scope_id }); + let def = Definition::ClassDef(ClassDefinition { + node_key: TypedNodeKey::from_node(node), + scope_id, + }); + self.add_or_update_symbol_with_def(&node.name, def); } ast::Stmt::FunctionDef(node) => { let def = Definition::FunctionDef(TypedNodeKey::from_node(node)); self.add_or_update_symbol_with_def(&node.name, def); self.with_type_params(&node.name, &node.type_params, |builder| { - builder.push_scope(builder.cur_scope(), &node.name, ScopeKind::Function); + let scope_id = builder.push_scope(&node.name, ScopeKind::Function); ast::visitor::preorder::walk_stmt(builder, stmt); builder.pop_scope(); + scope_id }); } ast::Stmt::Import(ast::StmtImport { names, .. }) => { diff --git a/crates/red_knot/src/types.rs b/crates/red_knot/src/types.rs index bce8eff114148..871c43092bb4c 100644 --- a/crates/red_knot/src/types.rs +++ b/crates/red_knot/src/types.rs @@ -1,7 +1,8 @@ #![allow(dead_code)] use crate::ast_ids::NodeKey; +use crate::db::{HasJar, QueryResult, SemanticDb, SemanticJar}; use crate::files::FileId; -use crate::symbols::SymbolId; +use crate::symbols::{ScopeId, SymbolId}; use crate::{FxDashMap, FxIndexSet, Name}; use ruff_index::{newtype_index, IndexVec}; use rustc_hash::FxHashMap; @@ -124,8 +125,15 @@ impl TypeStore { .add_function(name, decorators) } - fn add_class(&self, file_id: FileId, name: &str, bases: Vec) -> ClassTypeId { - self.add_or_get_module(file_id).add_class(name, bases) + fn add_class( + &self, + file_id: FileId, + name: &str, + scope_id: ScopeId, + bases: Vec, + ) -> ClassTypeId { + self.add_or_get_module(file_id) + .add_class(name, scope_id, bases) } fn add_union(&mut self, file_id: FileId, elems: &[Type]) -> UnionTypeId { @@ -253,6 +261,24 @@ pub struct ClassTypeId { class_id: ModuleClassTypeId, } +impl ClassTypeId { + fn get_own_class_member(self, db: &Db, name: &Name) -> QueryResult> + where + Db: SemanticDb + HasJar, + { + // TODO: this should distinguish instance-only members (e.g. `x: int`) and not return them + let ClassType { scope_id, .. } = *db.jar()?.type_store.get_class(self); + let table = db.symbol_table(self.file_id)?; + if let Some(symbol_id) = table.symbol_id_by_name(scope_id, name) { + Ok(Some(db.infer_symbol_type(self.file_id, symbol_id)?)) + } else { + Ok(None) + } + } + + // TODO: get_own_instance_member, get_class_member, get_instance_member +} + #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] pub struct UnionTypeId { file_id: FileId, @@ -318,9 +344,10 @@ impl ModuleTypeStore { } } - fn add_class(&mut self, name: &str, bases: Vec) -> ClassTypeId { + fn add_class(&mut self, name: &str, scope_id: ScopeId, bases: Vec) -> ClassTypeId { let class_id = self.classes.push(ClassType { name: Name::new(name), + scope_id, // TODO: if no bases are given, that should imply [object] bases, }); @@ -405,7 +432,11 @@ impl std::fmt::Display for DisplayType<'_> { #[derive(Debug)] pub(crate) struct ClassType { + /// Name of the class at definition name: Name, + /// `ScopeId` of the class body + pub(crate) scope_id: ScopeId, + /// Types of all class bases bases: Vec, } @@ -496,6 +527,7 @@ impl IntersectionType { #[cfg(test)] mod tests { use crate::files::Files; + use crate::symbols::SymbolTable; use crate::types::{Type, TypeStore}; use crate::FxIndexSet; use std::path::Path; @@ -505,7 +537,7 @@ mod tests { let store = TypeStore::default(); let files = Files::default(); let file_id = files.intern(Path::new("/foo")); - let id = store.add_class(file_id, "C", Vec::new()); + let id = store.add_class(file_id, "C", SymbolTable::root_scope_id(), Vec::new()); assert_eq!(store.get_class(id).name(), "C"); let inst = Type::Instance(id); assert_eq!(format!("{}", inst.display(&store)), "C"); @@ -528,8 +560,8 @@ mod tests { let mut store = TypeStore::default(); let files = Files::default(); let file_id = files.intern(Path::new("/foo")); - let c1 = store.add_class(file_id, "C1", Vec::new()); - let c2 = store.add_class(file_id, "C2", Vec::new()); + let c1 = store.add_class(file_id, "C1", SymbolTable::root_scope_id(), Vec::new()); + let c2 = store.add_class(file_id, "C2", SymbolTable::root_scope_id(), Vec::new()); let elems = vec![Type::Instance(c1), Type::Instance(c2)]; let id = store.add_union(file_id, &elems); assert_eq!( @@ -545,9 +577,9 @@ mod tests { let mut store = TypeStore::default(); let files = Files::default(); let file_id = files.intern(Path::new("/foo")); - let c1 = store.add_class(file_id, "C1", Vec::new()); - let c2 = store.add_class(file_id, "C2", Vec::new()); - let c3 = store.add_class(file_id, "C3", Vec::new()); + let c1 = store.add_class(file_id, "C1", SymbolTable::root_scope_id(), Vec::new()); + let c2 = store.add_class(file_id, "C2", SymbolTable::root_scope_id(), Vec::new()); + let c3 = store.add_class(file_id, "C3", SymbolTable::root_scope_id(), Vec::new()); let pos = vec![Type::Instance(c1), Type::Instance(c2)]; let neg = vec![Type::Instance(c3)]; let id = store.add_intersection(file_id, &pos, &neg); diff --git a/crates/red_knot/src/types/infer.rs b/crates/red_knot/src/types/infer.rs index f7e890fbb1eb3..efdd3a484cb6a 100644 --- a/crates/red_knot/src/types/infer.rs +++ b/crates/red_knot/src/types/infer.rs @@ -4,7 +4,7 @@ use ruff_python_ast::AstNode; use crate::db::{HasJar, QueryResult, SemanticDb, SemanticJar}; use crate::module::ModuleName; -use crate::symbols::{Definition, ImportFromDefinition, SymbolId}; +use crate::symbols::{ClassDefinition, Definition, ImportFromDefinition, SymbolId}; use crate::types::Type; use crate::FileId; use ruff_python_ast as ast; @@ -51,7 +51,7 @@ where Type::Unknown } } - Definition::ClassDef(node_key) => { + Definition::ClassDef(ClassDefinition { node_key, scope_id }) => { if let Some(ty) = type_store.get_cached_node_type(file_id, node_key.erased()) { ty } else { @@ -65,7 +65,8 @@ where bases.push(infer_expr_type(db, file_id, base)?); } - let ty = Type::Class(type_store.add_class(file_id, &node.name.id, bases)); + let ty = + Type::Class(type_store.add_class(file_id, &node.name.id, *scope_id, bases)); type_store.cache_node_type(file_id, *node_key.erased(), ty); ty } @@ -133,6 +134,7 @@ mod tests { use crate::db::{HasJar, SemanticDb, SemanticJar}; use crate::module::{ModuleName, ModuleSearchPath, ModuleSearchPathKind}; use crate::types::Type; + use crate::Name; // TODO with virtual filesystem we shouldn't have to write files to disk for these // tests @@ -222,4 +224,42 @@ mod tests { Ok(()) } + + #[test] + fn resolve_method() -> anyhow::Result<()> { + let case = create_test()?; + let db = &case.db; + + let path = case.src.path().join("mod.py"); + std::fs::write(path, "class C:\n def f(self): pass")?; + let file = db + .resolve_module(ModuleName::new("mod"))? + .expect("module should be found") + .path(db)? + .file(); + let syms = db.symbol_table(file)?; + let sym = syms + .root_symbol_id_by_name("C") + .expect("C symbol should be found"); + + let ty = db.infer_symbol_type(file, sym)?; + + let Type::Class(class_id) = ty else { + panic!("C is not a Class"); + }; + + let member_ty = class_id + .get_own_class_member(db, &Name::new("f")) + .expect("C.f to resolve"); + + let Some(Type::Function(func_id)) = member_ty else { + panic!("C.f is not a Function"); + }; + + let jar = HasJar::::jar(db)?; + let function = jar.type_store.get_function(func_id); + assert_eq!(function.name(), "f"); + + Ok(()) + } } diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index 8f41e629674c5..c8e793bfadd5b 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff" -version = "0.4.2" +version = "0.4.3" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff/tests/lint.rs b/crates/ruff/tests/lint.rs index 44a4ce82b61ec..735d730f7f0ef 100644 --- a/crates/ruff/tests/lint.rs +++ b/crates/ruff/tests/lint.rs @@ -1330,3 +1330,226 @@ def function(): Ok(()) } + +#[test] +fn add_noqa() -> Result<()> { + let tempdir = TempDir::new()?; + let ruff_toml = tempdir.path().join("ruff.toml"); + fs::write( + &ruff_toml, + r#" +[lint] +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] +"#, + )?; + + 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(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) + .arg(&test_path) + .args(["--add-noqa"]) + .arg("-") + .pass_stdin(r#" + +"#), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Added 1 noqa directive. + "###); + }); + + let test_code = std::fs::read_to_string(&test_path).expect("should read test file"); + + insta::assert_snapshot!(test_code, @r###" + def first_square(): + return [x * x for x in range(20)][0] # noqa: RUF015 + "###); + + Ok(()) +} + +#[test] +fn add_noqa_multiple_codes() -> Result<()> { + let tempdir = TempDir::new()?; + let ruff_toml = tempdir.path().join("ruff.toml"); + fs::write( + &ruff_toml, + r#" +[lint] +select = ["ANN001", "ANN201", "ARG001", "D103"] +"#, + )?; + + let test_path = tempdir.path().join("noqa.py"); + + fs::write( + &test_path, + r#" +def unused(x): + pass +"#, + )?; + + 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(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) + .arg(&test_path) + .arg("--preview") + .args(["--add-noqa"]) + .arg("-") + .pass_stdin(r#" + +"#), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Added 1 noqa directive. + "###); + }); + + let test_code = std::fs::read_to_string(&test_path).expect("should read test file"); + + insta::assert_snapshot!(test_code, @r###" + + def unused(x): # noqa: ANN001, ANN201, ARG001, D103 + pass + "###); + + Ok(()) +} + +#[test] +fn add_noqa_multiline_diagnostic() -> Result<()> { + let tempdir = TempDir::new()?; + let ruff_toml = tempdir.path().join("ruff.toml"); + fs::write( + &ruff_toml, + r#" +[lint] +select = ["I"] +"#, + )?; + + let test_path = tempdir.path().join("noqa.py"); + + fs::write( + &test_path, + r#" +import z +import c +import a +"#, + )?; + + 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(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) + .arg(&test_path) + .args(["--add-noqa"]) + .arg("-") + .pass_stdin(r#" + +"#), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Added 1 noqa directive. + "###); + }); + + let test_code = std::fs::read_to_string(&test_path).expect("should read test file"); + + insta::assert_snapshot!(test_code, @r###" + + import z # noqa: I001 + import c + import a + "###); + + Ok(()) +} + +#[test] +fn add_noqa_existing_noqa() -> Result<()> { + let tempdir = TempDir::new()?; + let ruff_toml = tempdir.path().join("ruff.toml"); + fs::write( + &ruff_toml, + r#" +[lint] +select = ["ANN001", "ANN201", "ARG001", "D103"] +"#, + )?; + + let test_path = tempdir.path().join("noqa.py"); + + fs::write( + &test_path, + r#" +def unused(x): # noqa: ANN001, ARG001, D103 + pass +"#, + )?; + + 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(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) + .arg(&test_path) + .arg("--preview") + .args(["--add-noqa"]) + .arg("-") + .pass_stdin(r#" + +"#), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Added 1 noqa directive. + "###); + }); + + let test_code = std::fs::read_to_string(&test_path).expect("should read test file"); + + insta::assert_snapshot!(test_code, @r###" + + def unused(x): # noqa: ANN001, ANN201, ARG001, D103 + pass + "###); + + Ok(()) +} diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 712836c326654..9da535b64d7c6 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_linter" -version = "0.4.2" +version = "0.4.3" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB118.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB118.py index a680f3b12a1a9..ed515ab8145c3 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB118.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB118.py @@ -73,3 +73,10 @@ def op_add4(x, y=1): def op_add5(x, y): print("op_add5") return x + y + + +# OK +class Class: + @staticmethod + def add(x, y): + return x + y diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index a659518da25b2..2c062bef928bc 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -78,7 +78,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { Rule::DuplicateUnionMember, Rule::RedundantLiteralUnion, Rule::UnnecessaryTypeUnion, - Rule::NeverUnion, ]) { // Avoid duplicate checks if the parent is a union, since these rules already // traverse nested unions. diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs index 62e6967f6243a..a931241001563 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs @@ -91,6 +91,12 @@ pub(super) fn allow_boolean_trap(call: &ast::ExprCall, checker: &Checker) -> boo // boolean trap is allowed. We want to avoid raising a violation for cases in which the argument // is positional-only and third-party, and this tends to be the case for setters. if call.arguments.args.len() == 1 { + // Ex) `foo.set(True)` + if func_name == "set" { + return true; + } + + // Ex) `foo.set_visible(True)` if func_name .strip_prefix("set") .is_some_and(|suffix| suffix.starts_with(|c: char| c == '_' || c.is_ascii_uppercase())) diff --git a/crates/ruff_linter/src/rules/flake8_unused_arguments/helpers.rs b/crates/ruff_linter/src/rules/flake8_unused_arguments/helpers.rs deleted file mode 100644 index 4a1b6a3a8f75d..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_unused_arguments/helpers.rs +++ /dev/null @@ -1,40 +0,0 @@ -use ruff_python_ast::{self as ast, Expr, Stmt}; - -use ruff_python_ast::helpers::is_docstring_stmt; - -/// Return `true` if a `Stmt` is a "empty": a `pass`, `...`, `raise -/// NotImplementedError`, or `raise NotImplemented` (with or without arguments). -fn is_empty_stmt(stmt: &Stmt) -> bool { - match stmt { - Stmt::Pass(_) => return true, - Stmt::Expr(ast::StmtExpr { value, range: _ }) => return value.is_ellipsis_literal_expr(), - Stmt::Raise(ast::StmtRaise { exc, cause, .. }) => { - if cause.is_none() { - if let Some(exc) = exc { - match exc.as_ref() { - Expr::Name(ast::ExprName { id, .. }) => { - return id == "NotImplementedError" || id == "NotImplemented"; - } - Expr::Call(ast::ExprCall { func, .. }) => { - if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() { - return id == "NotImplementedError" || id == "NotImplemented"; - } - } - _ => {} - } - } - } - } - _ => {} - } - false -} - -pub(crate) fn is_empty(body: &[Stmt]) -> bool { - match body { - [] => true, - [stmt] => is_docstring_stmt(stmt) || is_empty_stmt(stmt), - [docstring, stmt] => is_docstring_stmt(docstring) && is_empty_stmt(stmt), - _ => false, - } -} diff --git a/crates/ruff_linter/src/rules/flake8_unused_arguments/mod.rs b/crates/ruff_linter/src/rules/flake8_unused_arguments/mod.rs index 0c412f7d0507b..8f0eaaed3a48e 100644 --- a/crates/ruff_linter/src/rules/flake8_unused_arguments/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_unused_arguments/mod.rs @@ -1,5 +1,4 @@ //! Rules from [flake8-unused-arguments](https://pypi.org/project/flake8-unused-arguments/). -pub(crate) mod helpers; pub(crate) mod rules; pub mod settings; diff --git a/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs b/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs index 37d7ebffebc06..c37888f1a7587 100644 --- a/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs +++ b/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs @@ -11,7 +11,6 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::registry::Rule; -use crate::rules::flake8_unused_arguments::helpers; /// ## What it does /// Checks for the presence of unused arguments in function definitions. @@ -322,18 +321,19 @@ pub(crate) fn unused_arguments( return; } - let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else { + let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else { return; }; match &scope.kind { - ScopeKind::Function(ast::StmtFunctionDef { - name, - parameters, - body, - decorator_list, - .. - }) => { + ScopeKind::Function( + function_def @ ast::StmtFunctionDef { + name, + parameters, + decorator_list, + .. + }, + ) => { match function_type::classify( name, decorator_list, @@ -362,7 +362,7 @@ pub(crate) fn unused_arguments( } function_type::FunctionType::Method => { if checker.enabled(Argumentable::Method.rule_code()) - && !helpers::is_empty(body) + && !function_type::is_stub(function_def, checker.semantic()) && (!visibility::is_magic(name) || visibility::is_init(name) || visibility::is_new(name) @@ -387,7 +387,7 @@ pub(crate) fn unused_arguments( } function_type::FunctionType::ClassMethod => { if checker.enabled(Argumentable::ClassMethod.rule_code()) - && !helpers::is_empty(body) + && !function_type::is_stub(function_def, checker.semantic()) && (!visibility::is_magic(name) || visibility::is_init(name) || visibility::is_new(name) @@ -412,7 +412,7 @@ pub(crate) fn unused_arguments( } function_type::FunctionType::StaticMethod => { if checker.enabled(Argumentable::StaticMethod.rule_code()) - && !helpers::is_empty(body) + && !function_type::is_stub(function_def, checker.semantic()) && (!visibility::is_magic(name) || visibility::is_init(name) || visibility::is_new(name) diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs index 26f91091b154f..f503796e277af 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs @@ -190,7 +190,7 @@ pub(crate) fn invalid_first_argument_name( panic!("Expected ScopeKind::Function") }; - let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else { + let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else { return; }; diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs index c99cd52c8cc1b..cc9ea5c36b1ea 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs @@ -64,7 +64,7 @@ pub(crate) fn bad_staticmethod_argument( .. } = func; - let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else { + let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else { return; }; diff --git a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs index a642334774ef7..7dfa73cece80f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs @@ -8,7 +8,7 @@ use ruff_python_semantic::{ Scope, ScopeId, ScopeKind, }; -use crate::{checkers::ast::Checker, rules::flake8_unused_arguments::helpers}; +use crate::checkers::ast::Checker; /// ## What it does /// Checks for the presence of unused `self` parameter in methods definitions. @@ -49,7 +49,7 @@ pub(crate) fn no_self_use( scope: &Scope, diagnostics: &mut Vec, ) { - let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else { + let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else { return; }; @@ -60,7 +60,6 @@ pub(crate) fn no_self_use( let ast::StmtFunctionDef { name, parameters, - body, decorator_list, .. } = func; @@ -87,7 +86,7 @@ pub(crate) fn no_self_use( .map(|decorator| QualifiedName::from_dotted_name(decorator)) .collect::>(); - if helpers::is_empty(body) + if function_type::is_stub(func, checker.semantic()) || visibility::is_magic(name) || visibility::is_abstract(decorator_list, checker.semantic()) || visibility::is_override(decorator_list, checker.semantic()) diff --git a/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs b/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs index 17c24e1a1962f..8f1989217ff6f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs @@ -74,7 +74,7 @@ pub(crate) fn singledispatch_method( .. } = func; - let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else { + let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else { return; }; diff --git a/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs b/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs index 98a05582eb045..fa51711cb391c 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs @@ -72,7 +72,7 @@ pub(crate) fn singledispatchmethod_function( .. } = func; - let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else { + let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else { return; }; diff --git a/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs b/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs index 7e9992a199bd2..81f9c0e44ba9f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs @@ -83,7 +83,7 @@ pub(crate) fn super_without_brackets(checker: &mut Checker, func: &Expr) { return; }; - let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else { + let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else { return; }; diff --git a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs index eabce42e1dc6b..6fa0e91cf5225 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs @@ -5,6 +5,7 @@ use anyhow::Result; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::identifier::Identifier; use ruff_python_ast::{self as ast, Expr, ExprSlice, ExprSubscript, ExprTuple, Parameters, Stmt}; use ruff_python_semantic::SemanticModel; use ruff_source_file::Locator; @@ -69,6 +70,13 @@ impl Violation for ReimplementedOperator { /// FURB118 pub(crate) fn reimplemented_operator(checker: &mut Checker, target: &FunctionLike) { + // Ignore methods. + if target.kind() == FunctionLikeKind::Function { + if checker.semantic().current_scope().kind.is_class() { + return; + } + } + let Some(params) = target.parameters() else { return; }; @@ -111,7 +119,7 @@ impl Ranged for FunctionLike<'_> { fn range(&self) -> TextRange { match self { Self::Lambda(expr) => expr.range(), - Self::Function(stmt) => stmt.range(), + Self::Function(stmt) => stmt.identifier(), } } } diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB118_FURB118.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB118_FURB118.py.snap index 122f66aded57f..daab20b991ae3 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB118_FURB118.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB118_FURB118.py.snap @@ -778,28 +778,18 @@ FURB118.py:33:17: FURB118 [*] Use `operator.itemgetter(slice(None))` instead of 35 36 | 36 37 | def op_not2(x): -FURB118.py:36:1: FURB118 Use `operator.not_` instead of defining a function +FURB118.py:36:5: FURB118 Use `operator.not_` instead of defining a function | -36 | / def op_not2(x): -37 | | return not x - | |________________^ FURB118 +36 | def op_not2(x): + | ^^^^^^^ FURB118 +37 | return not x | = help: Replace with `operator.not_` -FURB118.py:40:1: FURB118 Use `operator.add` instead of defining a function +FURB118.py:40:5: FURB118 Use `operator.add` instead of defining a function | -40 | / def op_add2(x, y): -41 | | return x + y - | |________________^ FURB118 - | - = help: Replace with `operator.add` - -FURB118.py:45:5: FURB118 Use `operator.add` instead of defining a function - | -44 | class Adder: -45 | def add(x, y): - | _____^ -46 | | return x + y - | |____________________^ FURB118 +40 | def op_add2(x, y): + | ^^^^^^^ FURB118 +41 | return x + y | = help: Replace with `operator.add` diff --git a/crates/ruff_python_formatter/CONTRIBUTING.md b/crates/ruff_python_formatter/CONTRIBUTING.md index 193ff8ef32004..540eb34748b88 100644 --- a/crates/ruff_python_formatter/CONTRIBUTING.md +++ b/crates/ruff_python_formatter/CONTRIBUTING.md @@ -133,21 +133,6 @@ python scripts/check_ecosystem.py --checkouts target/checkouts --projects github cargo run --bin ruff_dev -- format-dev --stability-check --error-file target/formatter-ecosystem-errors.txt --multi-project target/checkouts ``` -**Shrinking** To shrink a formatter error from an entire file to a minimal reproducible example, -you can use `ruff_shrinking`: - -```shell -cargo run --bin ruff_shrinking -- target/shrinking.py "Unstable formatting" "target/debug/ruff_dev format-dev --stability-check target/shrinking.py" -``` - -The first argument is the input file, the second is the output file where the candidates -and the eventual minimized version will be written to. The third argument is a regex matching the -error message, e.g. "Unstable formatting" or "Formatter error". The last argument is the command -with the error, e.g. running the stability check on the candidate file. The script will try various -strategies to remove parts of the code. If the output of the command still matches, it will use that -slightly smaller code as starting point for the next iteration, otherwise it will revert and try -a different strategy until all strategies are exhausted. - ## Helper structs To abstract formatting something into a helper, create a new struct with the data you want to diff --git a/crates/ruff_python_formatter/shrink_formatter_errors.py b/crates/ruff_python_formatter/shrink_formatter_errors.py deleted file mode 100644 index 3d104b7c0317e..0000000000000 --- a/crates/ruff_python_formatter/shrink_formatter_errors.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Take `format-dev --stability-check` output and shrink all stability errors into a -single Python file. Used to update https://github.com/astral-sh/ruff/issues/5828 .""" - -from __future__ import annotations - -import json -import os -from concurrent.futures import ThreadPoolExecutor, as_completed -from pathlib import Path -from subprocess import check_output -from tempfile import NamedTemporaryFile - -from tqdm import tqdm - -root = Path( - check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip(), -) -target = root.joinpath("target") - -error_report = target.joinpath("formatter-ecosystem-errors.txt") -error_lines_prefix = "Unstable formatting " - - -def get_filenames() -> list[str]: - files = [] - for line in error_report.read_text().splitlines(): - if not line.startswith(error_lines_prefix): - continue - files.append(line.removeprefix(error_lines_prefix)) - return files - - -def shrink_file(file: str) -> tuple[str, str]: - """Returns filename and minimization""" - with NamedTemporaryFile(suffix=".py") as temp_file: - print(f"Starting {file}") - ruff_dev = target.joinpath("release").joinpath("ruff_dev") - check_output( - [ - target.joinpath("release").joinpath("ruff_shrinking"), - file, - temp_file.name, - "Unstable formatting", - f"{ruff_dev} format-dev --stability-check {temp_file.name}", - ], - ) - print(f"Finished {file}") - return file, Path(temp_file.name).read_text() - - -def main(): - storage = target.joinpath("minimizations.json") - output_file = target.joinpath("minimizations.py") - if storage.is_file(): - outputs = json.loads(storage.read_text()) - else: - outputs = {} - files = sorted(set(get_filenames()) - set(outputs)) - # Each process will saturate one core - with ThreadPoolExecutor(max_workers=os.cpu_count()) as executor: - tasks = [executor.submit(shrink_file, file) for file in files] - for future in tqdm(as_completed(tasks), total=len(files)): - file, output = future.result() - outputs[file] = output - storage.write_text(json.dumps(outputs, indent=4)) - - # Write to one shareable python file - with output_file.open("w") as formatted: - for file, code in sorted(json.loads(storage.read_text()).items()): - file = file.split("/target/checkouts/")[1] - formatted.write(f"# {file}\n{code}\n") - - -if __name__ == "__main__": - main() diff --git a/crates/ruff_server/Cargo.toml b/crates/ruff_server/Cargo.toml index 3fb502dbc88e5..fe52e52aeff97 100644 --- a/crates/ruff_server/Cargo.toml +++ b/crates/ruff_server/Cargo.toml @@ -30,12 +30,13 @@ crossbeam = { workspace = true } jod-thread = { workspace = true } lsp-server = { workspace = true } lsp-types = { workspace = true } +regex = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +shellexpand = { workspace = true } tracing = { workspace = true } walkdir = { workspace = true } -regex = { workspace = true } [dev-dependencies] insta = { workspace = true } diff --git a/crates/ruff_server/src/server.rs b/crates/ruff_server/src/server.rs index ca9a453042b58..262f2b12ea7ae 100644 --- a/crates/ruff_server/src/server.rs +++ b/crates/ruff_server/src/server.rs @@ -114,9 +114,6 @@ impl Server { self.worker_threads, )?; self.connection.close()?; - // Note: when we start routing tracing through the LSP, - // this should be replaced with a log directly to `stderr`. - tracing::info!("Server has shut down successfully"); Ok(()) })? .join() diff --git a/crates/ruff_server/src/session/settings.rs b/crates/ruff_server/src/session/settings.rs index 0786d29be2974..6b388aa3cc6f9 100644 --- a/crates/ruff_server/src/session/settings.rs +++ b/crates/ruff_server/src/session/settings.rs @@ -1,10 +1,11 @@ -use std::{ffi::OsString, ops::Deref, path::PathBuf, str::FromStr}; +use std::{ops::Deref, path::PathBuf, str::FromStr}; use lsp_types::Url; -use ruff_linter::{line_width::LineLength, RuleSelector}; use rustc_hash::FxHashMap; use serde::Deserialize; +use ruff_linter::{line_width::LineLength, RuleSelector}; + /// Maps a workspace URI to its associated client settings. Used during server initialization. pub(crate) type WorkspaceSettingsMap = FxHashMap; @@ -234,7 +235,8 @@ impl ResolvedClientSettings { settings .configuration .as_ref() - .map(|config_path| OsString::from(config_path.clone()).into()) + .and_then(|config_path| shellexpand::full(config_path).ok()) + .map(|config_path| PathBuf::from(config_path.as_ref())) }), lint_preview: Self::resolve_optional(all_settings, |settings| { settings.lint.as_ref()?.preview @@ -272,9 +274,7 @@ impl ResolvedClientSettings { .map(|rule| RuleSelector::from_str(rule).ok()) .collect() }), - exclude: Self::resolve_optional(all_settings, |settings| { - Some(settings.exclude.as_ref()?.clone()) - }), + exclude: Self::resolve_optional(all_settings, |settings| settings.exclude.clone()), line_length: Self::resolve_optional(all_settings, |settings| settings.line_length), configuration_preference: Self::resolve_or( all_settings, @@ -341,9 +341,10 @@ impl Default for InitializationOptions { #[cfg(test)] mod tests { use insta::assert_debug_snapshot; - use ruff_linter::registry::Linter; use serde::de::DeserializeOwned; + use ruff_linter::registry::Linter; + use super::*; const VS_CODE_INIT_OPTIONS_FIXTURE: &str = diff --git a/crates/ruff_server/src/session/workspace/ruff_settings.rs b/crates/ruff_server/src/session/workspace/ruff_settings.rs index 7a8af6e073edb..3570dbaaa7500 100644 --- a/crates/ruff_server/src/session/workspace/ruff_settings.rs +++ b/crates/ruff_server/src/session/workspace/ruff_settings.rs @@ -18,9 +18,9 @@ use crate::session::settings::{ConfigurationPreference, ResolvedEditorSettings}; #[derive(Default)] pub(crate) struct RuffSettings { - // settings to pass into the ruff linter + /// Settings to pass into the Ruff linter. linter: ruff_linter::settings::LinterSettings, - // settings to pass into the ruff formatter + /// Settings to pass into the Ruff formatter. formatter: ruff_workspace::FormatterSettings, } @@ -89,7 +89,15 @@ impl RuffSettingsIndex { ) .ok() }) - .unwrap_or_default(); + .unwrap_or_else(|| { + let default_configuration = ruff_workspace::configuration::Configuration::default(); + EditorConfigurationTransformer(editor_settings, root) + .transform(default_configuration) + .into_settings(root) + .expect( + "editor configuration should merge successfully with default configuration", + ) + }); Self { index, @@ -110,7 +118,10 @@ impl RuffSettingsIndex { return settings.clone(); } - tracing::info!("No ruff settings file (pyproject.toml/ruff.toml/.ruff.toml) found for {} - falling back to default configuration", document_path.display()); + tracing::info!( + "No Ruff settings file found for {}; falling back to default configuration", + document_path.display() + ); self.fallback.clone() } @@ -119,10 +130,7 @@ impl RuffSettingsIndex { struct EditorConfigurationTransformer<'a>(&'a ResolvedEditorSettings, &'a Path); impl<'a> ConfigurationTransformer for EditorConfigurationTransformer<'a> { - fn transform( - &self, - filesystem_configuration: ruff_workspace::configuration::Configuration, - ) -> ruff_workspace::configuration::Configuration { + fn transform(&self, filesystem_configuration: Configuration) -> Configuration { let ResolvedEditorSettings { configuration, format_preview, @@ -144,13 +152,13 @@ impl<'a> ConfigurationTransformer for EditorConfigurationTransformer<'a> { select, extend_select: extend_select.unwrap_or_default(), ignore: ignore.unwrap_or_default(), - ..Default::default() + ..RuleSelection::default() }], - ..Default::default() + ..LintConfiguration::default() }, format: FormatConfiguration { preview: format_preview.map(PreviewMode::from), - ..Default::default() + ..FormatConfiguration::default() }, exclude: exclude.map(|exclude| { exclude @@ -162,15 +170,15 @@ impl<'a> ConfigurationTransformer for EditorConfigurationTransformer<'a> { .collect() }), line_length, - ..Default::default() + ..Configuration::default() }; - // Merge in the editor-specified configuration file, if it exists + // Merge in the editor-specified configuration file, if it exists. let editor_configuration = if let Some(config_file_path) = configuration { match open_configuration_file(&config_file_path, project_root) { Ok(config_from_file) => editor_configuration.combine(config_from_file), Err(err) => { - tracing::error!("Unable to find editor-specified configuration file {err}"); + tracing::error!("Unable to find editor-specified configuration file: {err}"); editor_configuration } } diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 27bfab9934a4e..2fd4bc2db3417 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -1024,7 +1024,7 @@ pub struct Flake8BanditOptions { #[option( default = "[]", value_type = "list[str]", - example = "extend-hardcoded-tmp-directory = [\"/foo/bar\"]" + example = "hardcoded-tmp-directory-extend = [\"/foo/bar\"]" )] pub hardcoded_tmp_directory_extend: Option>, diff --git a/docs/integrations.md b/docs/integrations.md index 49c36cfffa7d7..d7eba57ff8e6e 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -14,7 +14,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.4.2 + rev: v0.4.3 hooks: # Run the linter. - id: ruff @@ -27,7 +27,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook: ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.4.2 + rev: v0.4.3 hooks: # Run the linter. - id: ruff @@ -41,7 +41,7 @@ To run the hooks over Jupyter Notebooks too, add `jupyter` to the list of allowe ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.4.2 + rev: v0.4.3 hooks: # Run the linter. - id: ruff diff --git a/pyproject.toml b/pyproject.toml index 9e7250d64806c..1f023a742fb8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.4.2" +version = "0.4.3" description = "An extremely fast Python linter and code formatter, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] readme = "README.md" @@ -106,6 +106,5 @@ version_files = [ "docs/integrations.md", "crates/ruff/Cargo.toml", "crates/ruff_linter/Cargo.toml", - "crates/ruff_shrinking/Cargo.toml", "scripts/benchmarks/pyproject.toml", ] diff --git a/scripts/benchmarks/pyproject.toml b/scripts/benchmarks/pyproject.toml index 82c87d813b27b..914adbc1c9710 100644 --- a/scripts/benchmarks/pyproject.toml +++ b/scripts/benchmarks/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "scripts" -version = "0.4.2" +version = "0.4.3" description = "" authors = ["Charles Marsh "]