diff --git a/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap b/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap index 1f673008047244..cbe6a7bc4fd119 100644 --- a/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap +++ b/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap @@ -232,6 +232,7 @@ linter.flake8_bandit.hardcoded_tmp_directory = [ ] linter.flake8_bandit.check_typed_exception = false linter.flake8_bugbear.extend_immutable_calls = [] +linter.flake8_builtins.builtins_allowed_modules = [] linter.flake8_builtins.builtins_ignorelist = [] linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|,\s)\d{4})* diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A004.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A004.py new file mode 100644 index 00000000000000..ed07e5502294dc --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A004.py @@ -0,0 +1,5 @@ +import some as sum +import float +from some import other as int +from some import input, exec +from directory import new as dir diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/logging/__init__.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/logging/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/non_builtin/__init__.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/non_builtin/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/__init__.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/bisect.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/bisect.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/xml.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/xml.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/string/__init__.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/string/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A006.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A006.py new file mode 100644 index 00000000000000..629ce4165ccc25 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A006.py @@ -0,0 +1,5 @@ +lambda print, copyright: print +lambda x, float, y: x + y +lambda min, max: min +lambda id: id +lambda dir: dir diff --git a/crates/ruff_linter/src/checkers/ast/analyze/deferred_lambdas.rs b/crates/ruff_linter/src/checkers/ast/analyze/deferred_lambdas.rs index 421a972e760bb4..83af7589a2c77c 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/deferred_lambdas.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/deferred_lambdas.rs @@ -2,7 +2,7 @@ use ruff_python_ast::Expr; use crate::checkers::ast::Checker; use crate::codes::Rule; -use crate::rules::{flake8_pie, pylint, refurb}; +use crate::rules::{flake8_builtins, flake8_pie, pylint, refurb}; /// Run lint rules over all deferred lambdas in the [`SemanticModel`]. pub(crate) fn deferred_lambdas(checker: &mut Checker) { @@ -24,6 +24,9 @@ pub(crate) fn deferred_lambdas(checker: &mut Checker) { if checker.enabled(Rule::ReimplementedOperator) { refurb::rules::reimplemented_operator(checker, &lambda.into()); } + if checker.enabled(Rule::BuiltinLambdaArgumentShadowing) { + flake8_builtins::rules::builtin_lambda_argument_shadowing(checker, lambda); + } } } } diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 67f28b84ba94b8..69dff843c65122 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -597,8 +597,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::NonAsciiImportName) { pylint::rules::non_ascii_module_import(checker, alias); } + // TODO(charlie): Remove when stabilizing A004. if let Some(asname) = &alias.asname { - if checker.enabled(Rule::BuiltinVariableShadowing) { + if checker.settings.preview.is_disabled() + && checker.enabled(Rule::BuiltinVariableShadowing) + { flake8_builtins::rules::builtin_variable_shadowing( checker, asname, @@ -739,6 +742,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { checker.diagnostics.push(diagnostic); } } + if checker.enabled(Rule::BuiltinImportShadowing) { + flake8_builtins::rules::builtin_import_shadowing(checker, alias); + } } } Stmt::ImportFrom( @@ -917,8 +923,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { )); } } else { + // TODO(charlie): Remove when stabilizing A004. if let Some(asname) = &alias.asname { - if checker.enabled(Rule::BuiltinVariableShadowing) { + if checker.settings.preview.is_disabled() + && checker.enabled(Rule::BuiltinVariableShadowing) + { flake8_builtins::rules::builtin_variable_shadowing( checker, asname, @@ -1030,6 +1039,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } } + if checker.enabled(Rule::BuiltinImportShadowing) { + flake8_builtins::rules::builtin_import_shadowing(checker, alias); + } } if checker.enabled(Rule::ImportSelf) { if let Some(diagnostic) = pylint::rules::import_from_self( diff --git a/crates/ruff_linter/src/checkers/filesystem.rs b/crates/ruff_linter/src/checkers/filesystem.rs index c71db50cb35631..2427409b254dec 100644 --- a/crates/ruff_linter/src/checkers/filesystem.rs +++ b/crates/ruff_linter/src/checkers/filesystem.rs @@ -5,6 +5,7 @@ use ruff_python_trivia::CommentRanges; use ruff_source_file::Locator; use crate::registry::Rule; +use crate::rules::flake8_builtins::rules::builtin_module_shadowing; use crate::rules::flake8_no_pep420::rules::implicit_namespace_package; use crate::rules::pep8_naming::rules::invalid_module_name; use crate::settings::LinterSettings; @@ -41,5 +42,17 @@ pub(crate) fn check_file_path( } } + // flake8-builtins + if settings.rules.enabled(Rule::BuiltinModuleShadowing) { + if let Some(diagnostic) = builtin_module_shadowing( + path, + package, + &settings.flake8_builtins.builtins_allowed_modules, + settings.target_version, + ) { + diagnostics.push(diagnostic); + } + } + diagnostics } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 9eb22cf3069233..e475e680516d85 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -310,6 +310,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing), (Flake8Builtins, "002") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinArgumentShadowing), (Flake8Builtins, "003") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinAttributeShadowing), + // TODO(charlie): When stabilizing, remove preview gating for A001's treatment of imports. + (Flake8Builtins, "004") => (RuleGroup::Preview, rules::flake8_builtins::rules::BuiltinImportShadowing), + (Flake8Builtins, "005") => (RuleGroup::Preview, rules::flake8_builtins::rules::BuiltinModuleShadowing), + (Flake8Builtins, "006") => (RuleGroup::Preview, rules::flake8_builtins::rules::BuiltinLambdaArgumentShadowing), // flake8-bugbear (Flake8Bugbear, "002") => (RuleGroup::Stable, rules::flake8_bugbear::rules::UnaryPrefixIncrementDecrement), diff --git a/crates/ruff_linter/src/registry.rs b/crates/ruff_linter/src/registry.rs index 4901c2e47f33d0..1ee0cc5102addd 100644 --- a/crates/ruff_linter/src/registry.rs +++ b/crates/ruff_linter/src/registry.rs @@ -304,7 +304,9 @@ impl Rule { | Rule::UTF8EncodingDeclaration => LintSource::Tokens, Rule::IOError => LintSource::Io, Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports, - Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem, + Rule::ImplicitNamespacePackage + | Rule::InvalidModuleName + | Rule::BuiltinModuleShadowing => LintSource::Filesystem, Rule::IndentationWithInvalidMultiple | Rule::IndentationWithInvalidMultipleComment | Rule::MissingWhitespace diff --git a/crates/ruff_linter/src/rules/flake8_builtins/mod.rs b/crates/ruff_linter/src/rules/flake8_builtins/mod.rs index 3ce0725066ca23..8a35abb23d5bef 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/mod.rs @@ -18,6 +18,25 @@ mod tests { #[test_case(Rule::BuiltinVariableShadowing, Path::new("A001.py"))] #[test_case(Rule::BuiltinArgumentShadowing, Path::new("A002.py"))] #[test_case(Rule::BuiltinAttributeShadowing, Path::new("A003.py"))] + #[test_case(Rule::BuiltinImportShadowing, Path::new("A004.py"))] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/non_builtin/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/logging/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/string/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/package/bisect.py") + )] + #[test_case(Rule::BuiltinModuleShadowing, Path::new("A005/modules/package/xml.py"))] + #[test_case(Rule::BuiltinLambdaArgumentShadowing, Path::new("A006.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( @@ -31,6 +50,8 @@ mod tests { #[test_case(Rule::BuiltinVariableShadowing, Path::new("A001.py"))] #[test_case(Rule::BuiltinArgumentShadowing, Path::new("A002.py"))] #[test_case(Rule::BuiltinAttributeShadowing, Path::new("A003.py"))] + #[test_case(Rule::BuiltinImportShadowing, Path::new("A004.py"))] + #[test_case(Rule::BuiltinLambdaArgumentShadowing, Path::new("A006.py"))] fn builtins_ignorelist(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "{}_{}_builtins_ignorelist", @@ -43,6 +64,46 @@ mod tests { &LinterSettings { flake8_builtins: super::settings::Settings { builtins_ignorelist: vec!["id".to_string(), "dir".to_string()], + ..Default::default() + }, + ..LinterSettings::for_rules(vec![rule_code]) + }, + )?; + + assert_messages!(snapshot, diagnostics); + Ok(()) + } + + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/non_builtin/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/logging/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/string/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/package/bisect.py") + )] + #[test_case(Rule::BuiltinModuleShadowing, Path::new("A005/modules/package/xml.py"))] + fn builtins_allowed_modules(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "{}_{}_builtins_allowed_modules", + rule_code.noqa_code(), + path.to_string_lossy() + ); + + let diagnostics = test_path( + Path::new("flake8_builtins").join(path).as_path(), + &LinterSettings { + flake8_builtins: super::settings::Settings { + builtins_allowed_modules: vec!["xml".to_string(), "logging".to_string()], + ..Default::default() }, ..LinterSettings::for_rules(vec![rule_code]) }, diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs index 74088538aa51fc..e8337dadbccb02 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs @@ -1,8 +1,7 @@ -use ruff_python_ast::Parameter; - use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::Parameter; use ruff_python_semantic::analyze::visibility::{is_overload, is_override}; use ruff_text_size::Ranged; @@ -11,7 +10,7 @@ use crate::checkers::ast::Checker; use super::super::helpers::shadows_builtin; /// ## What it does -/// Checks for any function arguments that use the same name as a builtin. +/// Checks for function arguments that use the same names as builtins. /// /// ## Why is this bad? /// Reusing a builtin name for the name of an argument increases the diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs index de4a625c3dac92..124a1bd574aac8 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs @@ -10,8 +10,8 @@ use crate::checkers::ast::Checker; use crate::rules::flake8_builtins::helpers::shadows_builtin; /// ## What it does -/// Checks for any class attributes or methods that use the same name as a -/// builtin. +/// Checks for class attributes and methods that use the same names as +/// Python builtins. /// /// ## Why is this bad? /// Reusing a builtin name for the name of an attribute increases the diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs new file mode 100644 index 00000000000000..2c601a48af97a3 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs @@ -0,0 +1,49 @@ +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::Alias; + +use crate::checkers::ast::Checker; +use crate::rules::flake8_builtins::helpers::shadows_builtin; + +/// ## What it does +/// Checks for imports that use the same names as builtins. +/// +/// ## Why is this bad? +/// Reusing a builtin for the name of an import increases the difficulty +/// of reading and maintaining the code, and can cause non-obvious errors, +/// as readers may mistake the variable for the builtin and vice versa. +/// +/// Builtins can be marked as exceptions to this rule via the +/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option. +/// +/// ## Options +/// - `lint.flake8-builtins.builtins-ignorelist` +#[violation] +pub struct BuiltinImportShadowing { + name: String, +} + +impl Violation for BuiltinImportShadowing { + #[derive_message_formats] + fn message(&self) -> String { + let BuiltinImportShadowing { name } = self; + format!("Import `{name}` is shadowing a Python builtin") + } +} + +/// A004 +pub(crate) fn builtin_import_shadowing(checker: &mut Checker, alias: &Alias) { + let name = alias.asname.as_ref().unwrap_or(&alias.name); + if shadows_builtin( + name.as_str(), + &checker.settings.flake8_builtins.builtins_ignorelist, + checker.source_type, + ) { + checker.diagnostics.push(Diagnostic::new( + BuiltinImportShadowing { + name: name.to_string(), + }, + name.range, + )); + } +} diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs new file mode 100644 index 00000000000000..446e3f2ddec82f --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs @@ -0,0 +1,56 @@ +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::ExprLambda; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; +use crate::rules::flake8_builtins::helpers::shadows_builtin; + +/// ## What it does +/// Checks for lambda arguments that use the same names as Python builtins. +/// +/// ## Why is this bad? +/// Reusing a builtin name for the name of a lambda argument increases the +/// difficulty of reading and maintaining the code, and can cause +/// non-obvious errors, as readers may mistake the variable for the +/// builtin and vice versa. +/// +/// Builtins can be marked as exceptions to this rule via the +/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option. +/// +/// ## Options +/// - `lint.flake8-builtins.builtins-ignorelist` +#[violation] +pub struct BuiltinLambdaArgumentShadowing { + name: String, +} + +impl Violation for BuiltinLambdaArgumentShadowing { + #[derive_message_formats] + fn message(&self) -> String { + let BuiltinLambdaArgumentShadowing { name } = self; + format!("Lambda argument `{name}` is shadowing a Python builtin") + } +} + +/// A006 +pub(crate) fn builtin_lambda_argument_shadowing(checker: &mut Checker, lambda: &ExprLambda) { + let Some(parameters) = lambda.parameters.as_ref() else { + return; + }; + for param in parameters.iter_non_variadic_params() { + let name = ¶m.parameter.name; + if shadows_builtin( + name.as_ref(), + &checker.settings.flake8_builtins.builtins_ignorelist, + checker.source_type, + ) { + checker.diagnostics.push(Diagnostic::new( + BuiltinLambdaArgumentShadowing { + name: name.to_string(), + }, + name.range(), + )); + } + } +} diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_module_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_module_shadowing.rs new file mode 100644 index 00000000000000..d38665274d3288 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_module_shadowing.rs @@ -0,0 +1,73 @@ +use std::path::Path; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_stdlib::path::is_module_file; +use ruff_python_stdlib::sys::is_known_standard_library; +use ruff_text_size::TextRange; + +use crate::settings::types::PythonVersion; + +/// ## What it does +/// Checks for modules that use the same names as Python builtin modules. +/// +/// ## Why is this bad? +/// Reusing a builtin module name for the name of a module increases the +/// difficulty of reading and maintaining the code, and can cause +/// non-obvious errors, as readers may mistake the variable for the +/// builtin and vice versa. +/// +/// Builtin modules can be marked as exceptions to this rule via the +/// [`lint.flake8-builtins.builtins-allowed-modules`] configuration option. +/// +/// ## Options +/// - `lint.flake8-builtins.builtins-allowed-modules` +#[violation] +pub struct BuiltinModuleShadowing { + name: String, +} + +impl Violation for BuiltinModuleShadowing { + #[derive_message_formats] + fn message(&self) -> String { + let BuiltinModuleShadowing { name } = self; + format!("Module `{name}` is shadowing a Python builtin module") + } +} + +/// A005 +pub(crate) fn builtin_module_shadowing( + path: &Path, + package: Option<&Path>, + allowed_modules: &[String], + target_version: PythonVersion, +) -> Option { + if !path + .extension() + .is_some_and(|ext| ext == "py" || ext == "pyi") + { + return None; + } + + if let Some(package) = package { + let module_name = if is_module_file(path) { + package.file_name().unwrap().to_string_lossy() + } else { + path.file_stem().unwrap().to_string_lossy() + }; + + if is_known_standard_library(target_version.minor(), &module_name) + && allowed_modules + .iter() + .all(|allowed_module| allowed_module != &module_name) + { + return Some(Diagnostic::new( + BuiltinModuleShadowing { + name: module_name.to_string(), + }, + TextRange::default(), + )); + } + } + None +} diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs index 0b5a0080d063ba..1654b59422760e 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs @@ -1,15 +1,14 @@ -use ruff_text_size::TextRange; - use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{derive_message_formats, violation}; +use ruff_text_size::TextRange; use crate::checkers::ast::Checker; use crate::rules::flake8_builtins::helpers::shadows_builtin; /// ## What it does -/// Checks for variable (and function) assignments that use the same name -/// as a builtin. +/// Checks for variable (and function) assignments that use the same names +/// as builtins. /// /// ## Why is this bad? /// Reusing a builtin name for the name of a variable increases the diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/mod.rs index d81afec0d6e5b2..46478f7c236a1a 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/mod.rs @@ -1,7 +1,13 @@ pub(crate) use builtin_argument_shadowing::*; pub(crate) use builtin_attribute_shadowing::*; +pub(crate) use builtin_import_shadowing::*; +pub(crate) use builtin_lambda_argument_shadowing::*; +pub(crate) use builtin_module_shadowing::*; pub(crate) use builtin_variable_shadowing::*; mod builtin_argument_shadowing; mod builtin_attribute_shadowing; +mod builtin_import_shadowing; +mod builtin_lambda_argument_shadowing; +mod builtin_module_shadowing; mod builtin_variable_shadowing; diff --git a/crates/ruff_linter/src/rules/flake8_builtins/settings.rs b/crates/ruff_linter/src/rules/flake8_builtins/settings.rs index e11537efb7ff44..cfb5573ee05a92 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/settings.rs @@ -7,6 +7,7 @@ use std::fmt::{Display, Formatter}; #[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub builtins_ignorelist: Vec, + pub builtins_allowed_modules: Vec, } impl Display for Settings { @@ -15,7 +16,8 @@ impl Display for Settings { formatter = f, namespace = "linter.flake8_builtins", fields = [ - self.builtins_ignorelist | array + self.builtins_allowed_modules | array, + self.builtins_ignorelist | array, ] } Ok(()) diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py.snap new file mode 100644 index 00000000000000..a6457721464339 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py.snap @@ -0,0 +1,55 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +A004.py:1:16: A004 Import `sum` is shadowing a Python builtin + | +1 | import some as sum + | ^^^ A004 +2 | import float +3 | from some import other as int + | + +A004.py:2:8: A004 Import `float` is shadowing a Python builtin + | +1 | import some as sum +2 | import float + | ^^^^^ A004 +3 | from some import other as int +4 | from some import input, exec + | + +A004.py:3:27: A004 Import `int` is shadowing a Python builtin + | +1 | import some as sum +2 | import float +3 | from some import other as int + | ^^^ A004 +4 | from some import input, exec +5 | from directory import new as dir + | + +A004.py:4:18: A004 Import `input` is shadowing a Python builtin + | +2 | import float +3 | from some import other as int +4 | from some import input, exec + | ^^^^^ A004 +5 | from directory import new as dir + | + +A004.py:4:25: A004 Import `exec` is shadowing a Python builtin + | +2 | import float +3 | from some import other as int +4 | from some import input, exec + | ^^^^ A004 +5 | from directory import new as dir + | + +A004.py:5:30: A004 Import `dir` is shadowing a Python builtin + | +3 | from some import other as int +4 | from some import input, exec +5 | from directory import new as dir + | ^^^ A004 + | diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py_builtins_ignorelist.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py_builtins_ignorelist.snap new file mode 100644 index 00000000000000..e9d130125bc020 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py_builtins_ignorelist.snap @@ -0,0 +1,47 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +A004.py:1:16: A004 Import `sum` is shadowing a Python builtin + | +1 | import some as sum + | ^^^ A004 +2 | import float +3 | from some import other as int + | + +A004.py:2:8: A004 Import `float` is shadowing a Python builtin + | +1 | import some as sum +2 | import float + | ^^^^^ A004 +3 | from some import other as int +4 | from some import input, exec + | + +A004.py:3:27: A004 Import `int` is shadowing a Python builtin + | +1 | import some as sum +2 | import float +3 | from some import other as int + | ^^^ A004 +4 | from some import input, exec +5 | from directory import new as dir + | + +A004.py:4:18: A004 Import `input` is shadowing a Python builtin + | +2 | import float +3 | from some import other as int +4 | from some import input, exec + | ^^^^^ A004 +5 | from directory import new as dir + | + +A004.py:4:25: A004 Import `exec` is shadowing a Python builtin + | +2 | import float +3 | from some import other as int +4 | from some import input, exec + | ^^^^ A004 +5 | from directory import new as dir + | diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py.snap new file mode 100644 index 00000000000000..9f05f0aa763d5c --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +__init__.py:1:1: A005 Module `logging` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py_builtins_allowed_modules.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py_builtins_allowed_modules.snap new file mode 100644 index 00000000000000..df35fcb66a979b --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py_builtins_allowed_modules.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py.snap new file mode 100644 index 00000000000000..df35fcb66a979b --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py_builtins_allowed_modules.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py_builtins_allowed_modules.snap new file mode 100644 index 00000000000000..df35fcb66a979b --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py_builtins_allowed_modules.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py.snap new file mode 100644 index 00000000000000..3615ea42974abd --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +bisect.py:1:1: A005 Module `bisect` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py_builtins_allowed_modules.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py_builtins_allowed_modules.snap new file mode 100644 index 00000000000000..3615ea42974abd --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py_builtins_allowed_modules.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +bisect.py:1:1: A005 Module `bisect` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py.snap new file mode 100644 index 00000000000000..3fade6e0a37576 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +xml.py:1:1: A005 Module `xml` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py_builtins_allowed_modules.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py_builtins_allowed_modules.snap new file mode 100644 index 00000000000000..df35fcb66a979b --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py_builtins_allowed_modules.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py.snap new file mode 100644 index 00000000000000..69dc571b6ffc59 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +__init__.py:1:1: A005 Module `string` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py_builtins_allowed_modules.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py_builtins_allowed_modules.snap new file mode 100644 index 00000000000000..69dc571b6ffc59 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py_builtins_allowed_modules.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +__init__.py:1:1: A005 Module `string` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py.snap new file mode 100644 index 00000000000000..14544a4f3da76c --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py.snap @@ -0,0 +1,64 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +A006.py:1:8: A006 Lambda argument `print` is shadowing a Python builtin + | +1 | lambda print, copyright: print + | ^^^^^ A006 +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | + +A006.py:1:15: A006 Lambda argument `copyright` is shadowing a Python builtin + | +1 | lambda print, copyright: print + | ^^^^^^^^^ A006 +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | + +A006.py:2:11: A006 Lambda argument `float` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y + | ^^^^^ A006 +3 | lambda min, max: min +4 | lambda id: id + | + +A006.py:3:8: A006 Lambda argument `min` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | ^^^ A006 +4 | lambda id: id +5 | lambda dir: dir + | + +A006.py:3:13: A006 Lambda argument `max` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | ^^^ A006 +4 | lambda id: id +5 | lambda dir: dir + | + +A006.py:4:8: A006 Lambda argument `id` is shadowing a Python builtin + | +2 | lambda x, float, y: x + y +3 | lambda min, max: min +4 | lambda id: id + | ^^ A006 +5 | lambda dir: dir + | + +A006.py:5:8: A006 Lambda argument `dir` is shadowing a Python builtin + | +3 | lambda min, max: min +4 | lambda id: id +5 | lambda dir: dir + | ^^^ A006 + | diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py_builtins_ignorelist.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py_builtins_ignorelist.snap new file mode 100644 index 00000000000000..2ac1bb621e4474 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py_builtins_ignorelist.snap @@ -0,0 +1,47 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +A006.py:1:8: A006 Lambda argument `print` is shadowing a Python builtin + | +1 | lambda print, copyright: print + | ^^^^^ A006 +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | + +A006.py:1:15: A006 Lambda argument `copyright` is shadowing a Python builtin + | +1 | lambda print, copyright: print + | ^^^^^^^^^ A006 +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | + +A006.py:2:11: A006 Lambda argument `float` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y + | ^^^^^ A006 +3 | lambda min, max: min +4 | lambda id: id + | + +A006.py:3:8: A006 Lambda argument `min` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | ^^^ A006 +4 | lambda id: id +5 | lambda dir: dir + | + +A006.py:3:13: A006 Lambda argument `max` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | ^^^ A006 +4 | lambda id: id +5 | lambda dir: dir + | diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs index 7c8a178b4ab803..6be2eb83aa7b49 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs @@ -4,6 +4,7 @@ use std::path::Path; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_stdlib::identifiers::{is_migration_name, is_module_name}; +use ruff_python_stdlib::path::is_module_file; use ruff_text_size::TextRange; use crate::rules::pep8_naming::settings::IgnoreNames; @@ -92,16 +93,6 @@ pub(crate) fn invalid_module_name( None } -/// Return `true` if a [`Path`] should use the name of its parent directory as its module name. -fn is_module_file(path: &Path) -> bool { - path.file_name().is_some_and(|file_name| { - file_name == "__init__.py" - || file_name == "__init__.pyi" - || file_name == "__main__.py" - || file_name == "__main__.pyi" - }) -} - /// Return `true` if a [`Path`] refers to a migration file. fn is_migration_file(path: &Path) -> bool { path.parent() diff --git a/crates/ruff_python_stdlib/src/path.rs b/crates/ruff_python_stdlib/src/path.rs index 5f7d6253d496f8..f9998efec3d897 100644 --- a/crates/ruff_python_stdlib/src/path.rs +++ b/crates/ruff_python_stdlib/src/path.rs @@ -16,6 +16,16 @@ pub fn is_jupyter_notebook(path: &Path) -> bool { path.extension().is_some_and(|ext| ext == "ipynb") } +/// Return `true` if a [`Path`] should use the name of its parent directory as its module name. +pub fn is_module_file(path: &Path) -> bool { + path.file_name().is_some_and(|file_name| { + file_name == "__init__.py" + || file_name == "__init__.pyi" + || file_name == "__main__.py" + || file_name == "__main__.pyi" + }) +} + #[cfg(test)] mod tests { use std::path::Path; diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 740abf63e9c15c..b54e6275f6ad1c 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -1104,12 +1104,20 @@ pub struct Flake8BuiltinsOptions { )] /// Ignore list of builtins. pub builtins_ignorelist: Option>, + #[option( + default = r#"[]"#, + value_type = "list[str]", + example = "builtins-allowed-modules = [\"id\"]" + )] + /// List of builtin module names to allow. + pub builtins_allowed_modules: Option>, } impl Flake8BuiltinsOptions { pub fn into_settings(self) -> ruff_linter::rules::flake8_builtins::settings::Settings { ruff_linter::rules::flake8_builtins::settings::Settings { builtins_ignorelist: self.builtins_ignorelist.unwrap_or_default(), + builtins_allowed_modules: self.builtins_allowed_modules.unwrap_or_default(), } } } diff --git a/ruff.schema.json b/ruff.schema.json index 5a51e46f633630..daf012bc8f7593 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -933,6 +933,16 @@ "Flake8BuiltinsOptions": { "type": "object", "properties": { + "builtins-allowed-modules": { + "description": "List of builtin module names to allow.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "builtins-ignorelist": { "description": "Ignore list of builtins.", "type": [ @@ -2669,6 +2679,9 @@ "A001", "A002", "A003", + "A004", + "A005", + "A006", "AIR", "AIR0", "AIR00",