diff --git a/README.md b/README.md index 270ed10656..976aa1a116 100644 --- a/README.md +++ b/README.md @@ -1739,6 +1739,24 @@ allowed-confusables = ["−", "ρ", "∗"] --- +#### [`builtins`](#builtins) + +A list of builtins to treat as defined references, in addition to the +system builtins. + +**Default value**: `[]` + +**Type**: `Vec` + +**Example usage**: + +```toml +[tool.ruff] +builtins = ["_"] +``` + +--- + #### [`cache-dir`](#cache-dir) A path to the cache directory. diff --git a/flake8_to_ruff/examples/jupyterhub.ini b/flake8_to_ruff/examples/jupyterhub.ini new file mode 100644 index 0000000000..41852dd2ca --- /dev/null +++ b/flake8_to_ruff/examples/jupyterhub.ini @@ -0,0 +1,19 @@ +[flake8] +# Ignore style and complexity +# E: style errors +# W: style warnings +# C: complexity +# D: docstring warnings (unused pydocstyle extension) +# F841: local variable assigned but never used +ignore = E, C, W, D, F841 +builtins = c, get_config +exclude = + .cache, + .github, + docs, + jupyterhub/alembic*, + onbuild, + scripts, + share, + tools, + setup.py diff --git a/flake8_to_ruff/src/converter.rs b/flake8_to_ruff/src/converter.rs index e4a61116b3..2ed6d1082a 100644 --- a/flake8_to_ruff/src/converter.rs +++ b/flake8_to_ruff/src/converter.rs @@ -99,6 +99,9 @@ pub fn convert( if let Some(value) = value { match key.as_str() { // flake8 + "builtins" => { + options.builtins = Some(parser::parse_strings(value.as_ref())); + } "max-line-length" | "max_line_length" => match value.clone().parse::() { Ok(line_length) => options.line_length = Some(line_length), Err(e) => eprintln!("Unable to parse '{key}' property: {e}"), @@ -362,6 +365,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: None, @@ -425,6 +429,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: None, @@ -488,6 +493,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: None, @@ -551,6 +557,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: None, @@ -614,6 +621,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: None, @@ -685,6 +693,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: None, @@ -751,6 +760,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: None, diff --git a/resources/test/fixtures/pyflakes/builtins.py b/resources/test/fixtures/pyflakes/builtins.py new file mode 100644 index 0000000000..afa7d4cf91 --- /dev/null +++ b/resources/test/fixtures/pyflakes/builtins.py @@ -0,0 +1 @@ +_("Translations") diff --git a/ruff.schema.json b/ruff.schema.json index eebc671701..8285df0705 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -15,6 +15,16 @@ "minLength": 1 } }, + "builtins": { + "description": "A list of builtins to treat as defined references, in addition to the system builtins.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "cache-dir": { "description": "A path to the cache directory.\n\nBy default, Ruff stores cache results in a `.ruff_cache` directory in the current project root.\n\nHowever, Ruff will also respect the `RUFF_CACHE_DIR` environment variable, which takes precedence over that default.\n\nThis setting will override even the `RUFF_CACHE_DIR` environment variable, if set.", "type": [ diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index 429f9207cc..7082a6e01a 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -3291,7 +3291,12 @@ impl<'a> Checker<'a> { fn bind_builtins(&mut self) { let scope = &mut self.scopes[*(self.scope_stack.last().expect("No current scope found"))]; - for builtin in BUILTINS.iter().chain(MAGIC_GLOBALS.iter()) { + for builtin in BUILTINS + .iter() + .chain(MAGIC_GLOBALS.iter()) + .copied() + .chain(self.settings.builtins.iter().map(String::as_str)) + { let index = self.bindings.len(); self.bindings.push(Binding { kind: BindingKind::Builtin, diff --git a/src/pyflakes/mod.rs b/src/pyflakes/mod.rs index df80206cd9..0cf882f20b 100644 --- a/src/pyflakes/mod.rs +++ b/src/pyflakes/mod.rs @@ -140,6 +140,29 @@ mod tests { Ok(()) } + #[test] + fn default_builtins() -> Result<()> { + let diagnostics = test_path( + Path::new("./resources/test/fixtures/pyflakes/builtins.py"), + &settings::Settings::for_rules(vec![RuleCode::F821]), + )?; + insta::assert_yaml_snapshot!(diagnostics); + Ok(()) + } + + #[test] + fn extra_builtins() -> Result<()> { + let diagnostics = test_path( + Path::new("./resources/test/fixtures/pyflakes/builtins.py"), + &settings::Settings { + builtins: vec!["_".to_string()], + ..settings::Settings::for_rules(vec![RuleCode::F821]) + }, + )?; + insta::assert_yaml_snapshot!(diagnostics); + Ok(()) + } + #[test] fn future_annotations() -> Result<()> { let diagnostics = test_path( diff --git a/src/pyflakes/snapshots/ruff__pyflakes__tests__default_builtins.snap b/src/pyflakes/snapshots/ruff__pyflakes__tests__default_builtins.snap new file mode 100644 index 0000000000..3169d15cf3 --- /dev/null +++ b/src/pyflakes/snapshots/ruff__pyflakes__tests__default_builtins.snap @@ -0,0 +1,15 @@ +--- +source: src/pyflakes/mod.rs +expression: diagnostics +--- +- kind: + UndefinedName: _ + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 1 + fix: ~ + parent: ~ + diff --git a/src/pyflakes/snapshots/ruff__pyflakes__tests__extra_builtins.snap b/src/pyflakes/snapshots/ruff__pyflakes__tests__extra_builtins.snap new file mode 100644 index 0000000000..d5a10f1b82 --- /dev/null +++ b/src/pyflakes/snapshots/ruff__pyflakes__tests__extra_builtins.snap @@ -0,0 +1,6 @@ +--- +source: src/pyflakes/mod.rs +expression: diagnostics +--- +[] + diff --git a/src/settings/configuration.rs b/src/settings/configuration.rs index f053c63728..cb6c71aaeb 100644 --- a/src/settings/configuration.rs +++ b/src/settings/configuration.rs @@ -28,6 +28,7 @@ use crate::{ #[derive(Debug, Default)] pub struct Configuration { pub allowed_confusables: Option>, + pub builtins: Option>, pub cache_dir: Option, pub dummy_variable_rgx: Option, pub exclude: Option>, @@ -80,6 +81,7 @@ impl Configuration { pub fn from_options(options: Options, project_root: &Path) -> Result { Ok(Configuration { allowed_confusables: options.allowed_confusables, + builtins: options.builtins, cache_dir: options .cache_dir .map(|dir| { @@ -177,6 +179,7 @@ impl Configuration { pub fn combine(self, config: Configuration) -> Self { Self { allowed_confusables: self.allowed_confusables.or(config.allowed_confusables), + builtins: self.builtins.or(config.builtins), cache_dir: self.cache_dir.or(config.cache_dir), dummy_variable_rgx: self.dummy_variable_rgx.or(config.dummy_variable_rgx), exclude: self.exclude.or(config.exclude), diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 6305f48997..0b7292ff6c 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -40,6 +40,7 @@ const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); #[allow(clippy::struct_excessive_bools)] pub struct Settings { pub allowed_confusables: FxHashSet, + pub builtins: Vec, pub cache_dir: PathBuf, pub dummy_variable_rgx: Regex, pub enabled: FxHashSet, @@ -113,6 +114,7 @@ impl Settings { .allowed_confusables .map(FxHashSet::from_iter) .unwrap_or_default(), + builtins: config.builtins.unwrap_or_default(), cache_dir: config.cache_dir.unwrap_or_else(|| cache_dir(project_root)), dummy_variable_rgx: config .dummy_variable_rgx @@ -216,6 +218,7 @@ impl Settings { pub fn for_rule(rule_code: RuleCode) -> Self { Self { allowed_confusables: FxHashSet::from_iter([]), + builtins: vec![], cache_dir: cache_dir(path_dedot::CWD.as_path()), dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), enabled: FxHashSet::from_iter([rule_code.clone()]), @@ -258,6 +261,7 @@ impl Settings { pub fn for_rules(rule_codes: Vec) -> Self { Self { allowed_confusables: FxHashSet::from_iter([]), + builtins: vec![], cache_dir: cache_dir(path_dedot::CWD.as_path()), dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), enabled: FxHashSet::from_iter(rule_codes.clone()), diff --git a/src/settings/options.rs b/src/settings/options.rs index f8732ce6ee..a541cc8cf8 100644 --- a/src/settings/options.rs +++ b/src/settings/options.rs @@ -30,6 +30,16 @@ pub struct Options { /// A list of allowed "confusable" Unicode characters to ignore when /// enforcing `RUF001`, `RUF002`, and `RUF003`. pub allowed_confusables: Option>, + #[option( + default = r#"[]"#, + value_type = "Vec", + example = r#" + builtins = ["_"] + "# + )] + /// A list of builtins to treat as defined references, in addition to the + /// system builtins. + pub builtins: Option>, #[option( default = ".ruff_cache", value_type = "PathBuf", diff --git a/src/settings/pyproject.rs b/src/settings/pyproject.rs index 915aa8030f..4cd7ac55e2 100644 --- a/src/settings/pyproject.rs +++ b/src/settings/pyproject.rs @@ -164,6 +164,7 @@ mod tests { Some(Tools { ruff: Some(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: None, @@ -221,6 +222,7 @@ line-length = 79 Some(Tools { ruff: Some(Options { allowed_confusables: None, + builtins: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -278,6 +280,7 @@ exclude = ["foo.py"] Some(Tools { ruff: Some(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: Some(vec!["foo.py".to_string()]), @@ -335,6 +338,7 @@ select = ["E501"] Some(Tools { ruff: Some(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: None, @@ -393,6 +397,7 @@ ignore = ["E501"] Some(Tools { ruff: Some(Options { allowed_confusables: None, + builtins: None, cache_dir: None, dummy_variable_rgx: None, exclude: None, @@ -485,6 +490,7 @@ other-attribute = 1 config, Options { allowed_confusables: Some(vec!['−', 'ρ', '∗']), + builtins: None, line_length: Some(88), fix: None, fix_only: None,