-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement flake8-i18n #3741
Merged
charliermarsh
merged 13 commits into
astral-sh:main
from
leiserfg:implement-flake8-i18n
Mar 27, 2023
Merged
Implement flake8-i18n #3741
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
469faf7
Add skelleton code
leiserfg 7d4108f
Add fixtures for flake8_i18n
leiserfg a587553
Add tests
leiserfg 1861ed3
Add implementation and insta snapshots
leiserfg e77dcf5
Add license entry
leiserfg b19486c
Apply fmt fixes
leiserfg bbbe078
Use extend instead of checking optional
leiserfg f811ae7
Use vec for extra args as empty vec works fine there
leiserfg 41b4ba1
Set default value for extend_function_names and update schema
leiserfg e8a8483
Update insta snapshots after rebasing master
leiserfg 0fa4e41
Modify signatures; add to docs
charliermarsh 411a0d6
Adjust defaults
charliermarsh 99f4970
Edit license
charliermarsh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
_(f"{'value'}") | ||
charliermarsh marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
_("{}".format("line")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
_("%s" % "line") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Diagnostic> { | ||
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<Diagnostic> { | ||
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<Diagnostic> { | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Vec<String>>, | ||
#[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<String>, | ||
} | ||
|
||
#[derive(Debug, CacheKey)] | ||
pub struct Settings { | ||
pub functions_names: Vec<String>, | ||
} | ||
|
||
fn default_func_names() -> Vec<String> { | ||
["_", "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<Options> 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(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed this to use an iterator. |
||
} | ||
} | ||
} | ||
|
||
impl From<Settings> for Options { | ||
fn from(settings: Settings) -> Self { | ||
Self { | ||
function_names: Some(settings.functions_names), | ||
extend_function_names: vec![], | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...i18n/snapshots/ruff__rules__flake8_i18n__tests__f-string-in-i18n-func-call_INT001.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: ~ | ||
|
19 changes: 19 additions & 0 deletions
19
...8_i18n/snapshots/ruff__rules__flake8_i18n__tests__format-in-i18n-func-call_INT002.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: ~ | ||
|
19 changes: 19 additions & 0 deletions
19
...8_i18n/snapshots/ruff__rules__flake8_i18n__tests__printf-in-i18n-func-call_INT003.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: ~ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@charliermarsh -- I'm not certain on this but wanted to ask the question -- does including a GPL tool in Ruff mean that (all/part of) Ruff would be considered a derivative work for the purposes of GPL, and therefore that Ruff would need to be re-licenced (or this PR reverted or etc)? This is the first GPL tool to be integrated, and I wanted to flag the concern as early as possible. Thanks, Adam
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lemme look into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are already other parts including GPL3 (flake8-django for instance).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll have to get some more informed opinions on this, I'm not sure myself on whether these constitute derivative works.
If they do, and we'd have to re-license under GPL, then I'd err on the side of removing them, or going back to the authors and asking if they'd consider re-licensing under MIT or a dual license.
flake8-django
is confusing because on PyPI it appears dual-licensed, and it includes the MIT trove classifiers:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to cause the trouble and thank you both for looking in to this.
It seems this has been noted already: rocioar/flake8-django#101 and rocioar/flake8-django#102. Sorry not to have flagged this at the time for
flake8-django
, but it seems theMIT
trove classifier may have been a mistake when that project adopted Poetry.A
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, thank you for calling this out! It's important to get right. I'll try to reach out to the
flake8-django
maintainer.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hum... This is such a common "idea", I've done it before without even knowing about flake8-i18n. We were working in a project and switched to Python 3.6 and many members of a the team started to use f-strings inside
_
. I became such a common mistake we had to "invent" our checker.I'm not a lawyer, but I don't think this can be gathered as derivative work.