From 5cb120327c49cb86148fb842519d1c08f8c06599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leiser=20Fern=C3=A1ndez=20Gallo?= Date: Mon, 27 Mar 2023 20:03:39 +0200 Subject: [PATCH] Implement `flake8-i18n` (#3741) --- LICENSE | 2 + README.md | 1 + .../test/fixtures/flake8_i18n/INT001.py | 1 + .../test/fixtures/flake8_i18n/INT002.py | 1 + .../test/fixtures/flake8_i18n/INT003.py | 1 + crates/ruff/src/checkers/ast/mod.rs | 34 ++++++- crates/ruff/src/codes.rs | 5 + crates/ruff/src/registry.rs | 7 ++ crates/ruff/src/rules/flake8_i18n/mod.rs | 29 ++++++ crates/ruff/src/rules/flake8_i18n/rules.rs | 91 +++++++++++++++++++ crates/ruff/src/rules/flake8_i18n/settings.rs | 73 +++++++++++++++ ..._f-string-in-i18n-func-call_INT001.py.snap | 19 ++++ ...s__format-in-i18n-func-call_INT002.py.snap | 19 ++++ ...s__printf-in-i18n-func-call_INT003.py.snap | 19 ++++ crates/ruff/src/rules/mod.rs | 1 + crates/ruff/src/settings/configuration.rs | 10 +- crates/ruff/src/settings/defaults.rs | 8 +- crates/ruff/src/settings/mod.rs | 9 +- crates/ruff/src/settings/options.rs | 10 +- crates/ruff_wasm/src/lib.rs | 8 +- docs/faq.md | 2 + ruff.schema.json | 41 +++++++++ 22 files changed, 371 insertions(+), 20 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/flake8_i18n/INT001.py create mode 100644 crates/ruff/resources/test/fixtures/flake8_i18n/INT002.py create mode 100644 crates/ruff/resources/test/fixtures/flake8_i18n/INT003.py create mode 100644 crates/ruff/src/rules/flake8_i18n/mod.rs create mode 100644 crates/ruff/src/rules/flake8_i18n/rules.rs create mode 100644 crates/ruff/src/rules/flake8_i18n/settings.rs create mode 100644 crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__f-string-in-i18n-func-call_INT001.py.snap create mode 100644 crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__format-in-i18n-func-call_INT002.py.snap create mode 100644 crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__printf-in-i18n-func-call_INT003.py.snap diff --git a/LICENSE b/LICENSE index 91dcadb77ee9f..3f2e481f10840 100644 --- a/LICENSE +++ b/LICENSE @@ -195,6 +195,8 @@ are: CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +- flake8-i18n, licensed as "GNU General Public License v2 (GPLv2)". + - flake8-implicit-str-concat, licensed as follows: """ The MIT License (MIT) diff --git a/README.md b/README.md index f587caf29398b..0b05bce795ac6 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ quality tools, including: - [flake8-eradicate](https://pypi.org/project/flake8-eradicate/) - [flake8-errmsg](https://pypi.org/project/flake8-errmsg/) - [flake8-executable](https://pypi.org/project/flake8-executable/) +- [flake8-i18n](https://pypi.org/project/flake8-i18n/) - [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/) - [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions) - [flake8-logging-format](https://pypi.org/project/flake8-logging-format/) diff --git a/crates/ruff/resources/test/fixtures/flake8_i18n/INT001.py b/crates/ruff/resources/test/fixtures/flake8_i18n/INT001.py new file mode 100644 index 0000000000000..703ad7df4bd66 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_i18n/INT001.py @@ -0,0 +1 @@ +_(f"{'value'}") diff --git a/crates/ruff/resources/test/fixtures/flake8_i18n/INT002.py b/crates/ruff/resources/test/fixtures/flake8_i18n/INT002.py new file mode 100644 index 0000000000000..80a220aa94255 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_i18n/INT002.py @@ -0,0 +1 @@ +_("{}".format("line")) diff --git a/crates/ruff/resources/test/fixtures/flake8_i18n/INT003.py b/crates/ruff/resources/test/fixtures/flake8_i18n/INT003.py new file mode 100644 index 0000000000000..3bdc1d052adbd --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_i18n/INT003.py @@ -0,0 +1 @@ +_("%s" % "line") diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index ab089d11b1498..d027e6501a59d 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -42,11 +42,12 @@ use crate::registry::{AsRule, Rule}; use crate::rules::{ flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger, - flake8_django, flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, - flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_raise, - flake8_return, flake8_self, flake8_simplify, flake8_tidy_imports, flake8_type_checking, - flake8_unused_arguments, flake8_use_pathlib, mccabe, numpy, pandas_vet, pep8_naming, - pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops, + flake8_django, flake8_errmsg, flake8_i18n, flake8_implicit_str_concat, + flake8_import_conventions, flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, + flake8_pytest_style, flake8_raise, flake8_return, flake8_self, flake8_simplify, + flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, flake8_use_pathlib, mccabe, + numpy, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, + pyupgrade, ruff, tryceratops, }; use crate::settings::types::PythonVersion; use crate::settings::{flags, Settings}; @@ -2898,6 +2899,29 @@ where } } + // flake8-i18n + if self.settings.rules.any_enabled(&[ + Rule::FStringInI18NFuncCall, + Rule::FormatInI18NFuncCall, + Rule::PrintfInI18NFuncCall, + ]) && flake8_i18n::rules::is_i18n_func_call( + func, + &self.settings.flake8_i18n.functions_names, + ) { + if self.settings.rules.enabled(Rule::FStringInI18NFuncCall) { + self.diagnostics + .extend(flake8_i18n::rules::f_string_in_i18n_func_call(args)); + } + if self.settings.rules.enabled(Rule::FormatInI18NFuncCall) { + self.diagnostics + .extend(flake8_i18n::rules::format_in_i18n_func_call(args)); + } + if self.settings.rules.enabled(Rule::PrintfInI18NFuncCall) { + self.diagnostics + .extend(flake8_i18n::rules::printf_in_i18n_func_call(args)); + } + } + // flake8-simplify if self .settings diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 35befae6c454a..12b4b8ece53b0 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -284,6 +284,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Flake8Return, "507") => Rule::SuperfluousElseContinue, (Flake8Return, "508") => Rule::SuperfluousElseBreak, + // flake8-i18n + (Flake8I18N, "001") => Rule::FStringInI18NFuncCall, + (Flake8I18N, "002") => Rule::FormatInI18NFuncCall, + (Flake8I18N, "003") => Rule::PrintfInI18NFuncCall, + // flake8-implicit-str-concat (Flake8ImplicitStrConcat, "001") => Rule::SingleLineImplicitStringConcatenation, (Flake8ImplicitStrConcat, "002") => Rule::MultiLineImplicitStringConcatenation, diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index b6f000bc6db63..01ed34da9ae9f 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -627,6 +627,10 @@ ruff_macros::register_rules!( rules::flake8_raise::rules::UnnecessaryParenOnRaiseException, // flake8-self rules::flake8_self::rules::PrivateMemberAccess, + // flake8-i18n + rules::flake8_i18n::rules::FStringInI18NFuncCall, + rules::flake8_i18n::rules::FormatInI18NFuncCall, + rules::flake8_i18n::rules::PrintfInI18NFuncCall, // numpy rules::numpy::rules::NumpyDeprecatedTypeAlias, rules::numpy::rules::NumpyLegacyRandom, @@ -777,6 +781,9 @@ pub enum Linter { /// [flake8-type-checking](https://pypi.org/project/flake8-type-checking/) #[prefix = "TCH"] Flake8TypeChecking, + /// [flake8-i18n](https://pypi.org/project/flake8-i18n/) + #[prefix = "INT"] + Flake8I18N, /// [flake8-unused-arguments](https://pypi.org/project/flake8-unused-arguments/) #[prefix = "ARG"] Flake8UnusedArguments, diff --git a/crates/ruff/src/rules/flake8_i18n/mod.rs b/crates/ruff/src/rules/flake8_i18n/mod.rs new file mode 100644 index 0000000000000..00729c04efb0d --- /dev/null +++ b/crates/ruff/src/rules/flake8_i18n/mod.rs @@ -0,0 +1,29 @@ +//! Rules from [flake8-i18n](https://pypi.org/project/flake8-i18n/). +pub(crate) mod rules; +pub mod settings; + +#[cfg(test)] +mod tests { + use std::path::Path; + + use anyhow::Result; + use insta::assert_yaml_snapshot; + use test_case::test_case; + + use crate::registry::Rule; + use crate::settings; + use crate::test::test_path; + + #[test_case(Rule::FStringInI18NFuncCall,Path::new("INT001.py"); "INT001")] + #[test_case(Rule::FormatInI18NFuncCall, Path::new("INT002.py"); "INT002")] + #[test_case(Rule::PrintfInI18NFuncCall, Path::new("INT003.py"); "INT003")] + fn rules(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let diagnostics = test_path( + Path::new("flake8_i18n").join(path).as_path(), + &settings::Settings::for_rule(rule_code), + )?; + assert_yaml_snapshot!(snapshot, diagnostics); + Ok(()) + } +} diff --git a/crates/ruff/src/rules/flake8_i18n/rules.rs b/crates/ruff/src/rules/flake8_i18n/rules.rs new file mode 100644 index 0000000000000..7597282516e69 --- /dev/null +++ b/crates/ruff/src/rules/flake8_i18n/rules.rs @@ -0,0 +1,91 @@ +use rustpython_parser::ast::{Constant, Expr, ExprKind, Operator}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::types::Range; + +#[violation] +pub struct FStringInI18NFuncCall; + +impl Violation for FStringInI18NFuncCall { + #[derive_message_formats] + fn message(&self) -> String { + format!("f-string is resolved before function call; consider `_(\"string %s\") % arg`") + } +} + +#[violation] +pub struct FormatInI18NFuncCall; + +impl Violation for FormatInI18NFuncCall { + #[derive_message_formats] + fn message(&self) -> String { + format!("`format` method argument is resolved before function call; consider `_(\"string %s\") % arg`") + } +} +#[violation] +pub struct PrintfInI18NFuncCall; + +impl Violation for PrintfInI18NFuncCall { + #[derive_message_formats] + fn message(&self) -> String { + format!("printf-style format is resolved before function call; consider `_(\"string %s\") % arg`") + } +} + +/// Returns true if the [`Expr`] is an internationalization function call. +pub fn is_i18n_func_call(func: &Expr, functions_names: &[String]) -> bool { + if let ExprKind::Name { id, .. } = &func.node { + functions_names.contains(id) + } else { + false + } +} + +/// INT001 +pub fn f_string_in_i18n_func_call(args: &[Expr]) -> Option { + if let Some(first) = args.first() { + if matches!(first.node, ExprKind::JoinedStr { .. }) { + return Some(Diagnostic::new( + FStringInI18NFuncCall {}, + Range::from(first), + )); + } + } + None +} + +/// INT002 +pub fn format_in_i18n_func_call(args: &[Expr]) -> Option { + if let Some(first) = args.first() { + if let ExprKind::Call { func, .. } = &first.node { + if let ExprKind::Attribute { attr, .. } = &func.node { + if attr == "format" { + return Some(Diagnostic::new(FormatInI18NFuncCall {}, Range::from(first))); + } + } + } + } + None +} + +/// INT003 +pub fn printf_in_i18n_func_call(args: &[Expr]) -> Option { + if let Some(first) = args.first() { + if let ExprKind::BinOp { + op: Operator::Mod { .. }, + left, + .. + } = &first.node + { + if let ExprKind::Constant { + value: Constant::Str(_), + .. + } = left.node + { + return Some(Diagnostic::new(PrintfInI18NFuncCall {}, Range::from(first))); + } + } + } + None +} diff --git a/crates/ruff/src/rules/flake8_i18n/settings.rs b/crates/ruff/src/rules/flake8_i18n/settings.rs new file mode 100644 index 0000000000000..8e189263b5373 --- /dev/null +++ b/crates/ruff/src/rules/flake8_i18n/settings.rs @@ -0,0 +1,73 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use ruff_macros::{CacheKey, ConfigurationOptions}; + +#[derive( + Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema, +)] +#[serde( + deny_unknown_fields, + rename_all = "kebab-case", + rename = "Flake8I18NOptions" +)] +pub struct Options { + #[option( + default = r#"["_", "gettext", "ngettext"]"#, + value_type = "list[str]", + example = r#"function-names = ["_", "gettext", "ngettext", "ugettetxt"]"# + )] + /// The function names to consider as internationalization calls. + pub function_names: Option>, + #[option( + default = r#"[]"#, + value_type = "list[str]", + example = r#"extend-function-names = ["ugettetxt"]"# + )] + #[serde(default)] + /// Additional function names to consider as internationalization calls, in addition to those + /// included in `function-names`. + pub extend_function_names: Vec, +} + +#[derive(Debug, CacheKey)] +pub struct Settings { + pub functions_names: Vec, +} + +fn default_func_names() -> Vec { + ["_", "gettext", "ngettext"] + .iter() + .map(std::string::ToString::to_string) + .collect() +} + +impl Default for Settings { + fn default() -> Self { + Self { + functions_names: default_func_names(), + } + } +} + +impl From for Settings { + fn from(options: Options) -> Self { + Self { + functions_names: options + .function_names + .unwrap_or_else(default_func_names) + .into_iter() + .chain(options.extend_function_names) + .collect(), + } + } +} + +impl From for Options { + fn from(settings: Settings) -> Self { + Self { + function_names: Some(settings.functions_names), + extend_function_names: vec![], + } + } +} diff --git a/crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__f-string-in-i18n-func-call_INT001.py.snap b/crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__f-string-in-i18n-func-call_INT001.py.snap new file mode 100644 index 0000000000000..16d3fe598b951 --- /dev/null +++ b/crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__f-string-in-i18n-func-call_INT001.py.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff/src/rules/flake8_i18n/mod.rs +expression: diagnostics +--- +- kind: + name: FStringInI18NFuncCall + body: "f-string is resolved before function call; consider `_(\"string %s\") % arg`" + suggestion: ~ + fixable: false + location: + row: 1 + column: 2 + end_location: + row: 1 + column: 14 + fix: + edits: [] + parent: ~ + diff --git a/crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__format-in-i18n-func-call_INT002.py.snap b/crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__format-in-i18n-func-call_INT002.py.snap new file mode 100644 index 0000000000000..2251f652b6080 --- /dev/null +++ b/crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__format-in-i18n-func-call_INT002.py.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff/src/rules/flake8_i18n/mod.rs +expression: diagnostics +--- +- kind: + name: FormatInI18NFuncCall + body: "`format` method argument is resolved before function call; consider `_(\"string %s\") % arg`" + suggestion: ~ + fixable: false + location: + row: 1 + column: 2 + end_location: + row: 1 + column: 21 + fix: + edits: [] + parent: ~ + diff --git a/crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__printf-in-i18n-func-call_INT003.py.snap b/crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__printf-in-i18n-func-call_INT003.py.snap new file mode 100644 index 0000000000000..01a19df2202ca --- /dev/null +++ b/crates/ruff/src/rules/flake8_i18n/snapshots/ruff__rules__flake8_i18n__tests__printf-in-i18n-func-call_INT003.py.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff/src/rules/flake8_i18n/mod.rs +expression: diagnostics +--- +- kind: + name: PrintfInI18NFuncCall + body: "printf-style format is resolved before function call; consider `_(\"string %s\") % arg`" + suggestion: ~ + fixable: false + location: + row: 1 + column: 2 + end_location: + row: 1 + column: 15 + fix: + edits: [] + parent: ~ + diff --git a/crates/ruff/src/rules/mod.rs b/crates/ruff/src/rules/mod.rs index 4c3330227ed04..3ec8ec1326c8f 100644 --- a/crates/ruff/src/rules/mod.rs +++ b/crates/ruff/src/rules/mod.rs @@ -14,6 +14,7 @@ pub mod flake8_debugger; pub mod flake8_django; pub mod flake8_errmsg; pub mod flake8_executable; +pub mod flake8_i18n; pub mod flake8_implicit_str_concat; pub mod flake8_import_conventions; pub mod flake8_logging_format; diff --git a/crates/ruff/src/settings/configuration.rs b/crates/ruff/src/settings/configuration.rs index b83682370f21e..ab5cc4f45891a 100644 --- a/crates/ruff/src/settings/configuration.rs +++ b/crates/ruff/src/settings/configuration.rs @@ -16,9 +16,10 @@ use crate::fs; use crate::rule_selector::RuleSelector; use crate::rules::{ flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, - flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_pytest_style, - flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, - isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade, + flake8_errmsg, flake8_i18n, flake8_implicit_str_concat, flake8_import_conventions, + flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, + flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, + pyupgrade, }; use crate::settings::options::Options; use crate::settings::types::{ @@ -75,6 +76,7 @@ pub struct Configuration { pub flake8_pytest_style: Option, pub flake8_quotes: Option, pub flake8_self: Option, + pub flake8_i18n: Option, pub flake8_tidy_imports: Option, pub flake8_type_checking: Option, pub flake8_unused_arguments: Option, @@ -184,6 +186,7 @@ impl Configuration { flake8_builtins: options.flake8_builtins, flake8_comprehensions: options.flake8_comprehensions, flake8_errmsg: options.flake8_errmsg, + flake8_i18n: options.flake8_i18n, flake8_implicit_str_concat: options.flake8_implicit_str_concat, flake8_import_conventions: options.flake8_import_conventions, flake8_pytest_style: options.flake8_pytest_style, @@ -248,6 +251,7 @@ impl Configuration { flake8_builtins: self.flake8_builtins.or(config.flake8_builtins), flake8_comprehensions: self.flake8_comprehensions.or(config.flake8_comprehensions), flake8_errmsg: self.flake8_errmsg.or(config.flake8_errmsg), + flake8_i18n: self.flake8_i18n.or(config.flake8_i18n), flake8_implicit_str_concat: self .flake8_implicit_str_concat .or(config.flake8_implicit_str_concat), diff --git a/crates/ruff/src/settings/defaults.rs b/crates/ruff/src/settings/defaults.rs index 66f9f05024859..d6b150d734403 100644 --- a/crates/ruff/src/settings/defaults.rs +++ b/crates/ruff/src/settings/defaults.rs @@ -11,9 +11,10 @@ use crate::registry::Linter; use crate::rule_selector::{prefix_to_selector, RuleSelector}; use crate::rules::{ flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, - flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_pytest_style, - flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, - isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade, + flake8_errmsg, flake8_i18n, flake8_implicit_str_concat, flake8_import_conventions, + flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, + flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, + pyupgrade, }; use crate::settings::types::FilePatternSet; @@ -88,6 +89,7 @@ impl Default for Settings { flake8_import_conventions: flake8_import_conventions::settings::Settings::default(), flake8_pytest_style: flake8_pytest_style::settings::Settings::default(), flake8_quotes: flake8_quotes::settings::Settings::default(), + flake8_i18n: flake8_i18n::settings::Settings::default(), flake8_self: flake8_self::settings::Settings::default(), flake8_tidy_imports: flake8_tidy_imports::Settings::default(), flake8_type_checking: flake8_type_checking::settings::Settings::default(), diff --git a/crates/ruff/src/settings/mod.rs b/crates/ruff/src/settings/mod.rs index 1fb9449599ace..9b6d6f00f1d7d 100644 --- a/crates/ruff/src/settings/mod.rs +++ b/crates/ruff/src/settings/mod.rs @@ -17,9 +17,10 @@ use crate::registry::{Rule, RuleNamespace, RuleSet, INCOMPATIBLE_CODES}; use crate::rule_selector::{RuleSelector, Specificity}; use crate::rules::{ flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, - flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_pytest_style, - flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, - isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade, + flake8_errmsg, flake8_i18n, flake8_implicit_str_concat, flake8_import_conventions, + flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, + flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, + pyupgrade, }; use crate::settings::configuration::Configuration; use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion, SerializationFormat}; @@ -112,6 +113,7 @@ pub struct Settings { pub flake8_errmsg: flake8_errmsg::settings::Settings, pub flake8_implicit_str_concat: flake8_implicit_str_concat::settings::Settings, pub flake8_import_conventions: flake8_import_conventions::settings::Settings, + pub flake8_i18n: flake8_i18n::settings::Settings, pub flake8_pytest_style: flake8_pytest_style::settings::Settings, pub flake8_quotes: flake8_quotes::settings::Settings, pub flake8_self: flake8_self::settings::Settings, @@ -216,6 +218,7 @@ impl Settings { .flake8_unused_arguments .map(Into::into) .unwrap_or_default(), + flake8_i18n: config.flake8_i18n.map(Into::into).unwrap_or_default(), isort: config.isort.map(Into::into).unwrap_or_default(), mccabe: config.mccabe.map(Into::into).unwrap_or_default(), pep8_naming: config.pep8_naming.map(Into::into).unwrap_or_default(), diff --git a/crates/ruff/src/settings/options.rs b/crates/ruff/src/settings/options.rs index ea199aa5bfe75..de8368f95c0b3 100644 --- a/crates/ruff/src/settings/options.rs +++ b/crates/ruff/src/settings/options.rs @@ -8,9 +8,10 @@ use serde::{Deserialize, Serialize}; use crate::rule_selector::RuleSelector; use crate::rules::{ flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, - flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_pytest_style, - flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, - isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade, + flake8_errmsg, flake8_i18n, flake8_implicit_str_concat, flake8_import_conventions, + flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, + flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, + pyupgrade, }; use crate::settings::types::{PythonVersion, SerializationFormat, Version}; @@ -456,6 +457,9 @@ pub struct Options { /// Options for the `flake8-type-checking` plugin. pub flake8_type_checking: Option, #[option_group] + /// Options for the `flake8-i18n` plugin. + pub flake8_i18n: Option, + #[option_group] /// Options for the `flake8-implicit-str-concat` plugin. pub flake8_implicit_str_concat: Option, #[option_group] diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index 57b3fd96d4d4b..a49991c2733c4 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -10,9 +10,10 @@ use ruff::linter::{check_path, LinterResult}; use ruff::registry::AsRule; use ruff::rules::{ flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, - flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_pytest_style, - flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, - isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade, + flake8_errmsg, flake8_i18n, flake8_implicit_str_concat, flake8_import_conventions, + flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, + flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, + pyupgrade, }; use ruff::settings::configuration::Configuration; use ruff::settings::options::Options; @@ -136,6 +137,7 @@ pub fn defaultSettings() -> Result { flake8_pytest_style: Some(flake8_pytest_style::settings::Settings::default().into()), flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()), flake8_self: Some(flake8_self::settings::Settings::default().into()), + flake8_i18n: Some(flake8_i18n::settings::Settings::default().into()), flake8_implicit_str_concat: Some( flake8_implicit_str_concat::settings::Settings::default().into(), ), diff --git a/docs/faq.md b/docs/faq.md index ad19b178ba335..afe8b6737d167 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -41,6 +41,7 @@ natively, including: - [flake8-eradicate](https://pypi.org/project/flake8-eradicate/) - [flake8-errmsg](https://pypi.org/project/flake8-errmsg/) - [flake8-executable](https://pypi.org/project/flake8-executable/) +- [flake8-i18n](https://pypi.org/project/flake8-i18n/) - [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/) - [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions) - [flake8-logging-format](https://pypi.org/project/flake8-logging-format/) @@ -138,6 +139,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [flake8-eradicate](https://pypi.org/project/flake8-eradicate/) - [flake8-errmsg](https://pypi.org/project/flake8-errmsg/) - [flake8-executable](https://pypi.org/project/flake8-executable/) +- [flake8-i18n](https://pypi.org/project/flake8-i18n/) - [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/) - [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions) - [flake8-logging-format](https://pypi.org/project/flake8-logging-format/) diff --git a/ruff.schema.json b/ruff.schema.json index 6c46bd297fdd7..38db2bd60cdfc 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -176,6 +176,17 @@ } ] }, + "flake8-i18n": { + "description": "Options for the `flake8-i18n` plugin.", + "anyOf": [ + { + "$ref": "#/definitions/Flake8I18NOptions" + }, + { + "type": "null" + } + ] + }, "flake8-implicit-str-concat": { "description": "Options for the `flake8-implicit-str-concat` plugin.", "anyOf": [ @@ -694,6 +705,30 @@ }, "additionalProperties": false }, + "Flake8I18NOptions": { + "type": "object", + "properties": { + "extend-function-names": { + "description": "Additional function names to consider as internationalization calls, in addition to those included in `function-names`.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "function-names": { + "description": "The function names to consider as internationalization calls.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, "Flake8ImplicitStrConcatOptions": { "type": "object", "properties": { @@ -1742,6 +1777,12 @@ "INP0", "INP00", "INP001", + "INT", + "INT0", + "INT00", + "INT001", + "INT002", + "INT003", "ISC", "ISC0", "ISC00",