-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
enforce required imports even with useless alias #14287
base: main
Are you sure you want to change the base?
Conversation
Not so happy about the big diff in such a simple rule for an edge case... I welcome alternative suggestions! |
|
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.
Could you expand the PR summary with details about what was causing the infinite loop or panic? It would help me review this PR and possibly suggest alternatives.
Done! It also occurred to me that it might be better to keep the diagnostic but elide the fix for useless-import-alias. That would serve the purpose of warning the user that something was amiss, and they could override the warning by ignoring the rule. Edit: Made this change. I did not change the "Fix availability" or documentation because it seems like this is a very rare (nonexistent?) exception, but let me know what you think. |
let Some(asname) = &alias.asname else { | ||
return; | ||
}; | ||
if alias.name.contains('.') { |
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.
Why this part?
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 don't know... This seems to have been there since the original implementation: https://github.com/harupy/ruff/blob/d765fbe5ddca1fa7b735256ebb303799a2e7aa95/src/pylint/plugins.rs#L16
I don't think it's possible to have an alias with a dot in it... maybe it's supposed to be an early return? Is checking for a dot faster than comparing two strings? Certainly can't be that much of a speed up... Anyway, happy to remove 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.
Thanks. Do you mind removing it (here and in the linked location) in a separate PR?
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.
Would it make sense to implement helper methods on the isort
settings to test if it is a required import to avoid duplicating the logic?
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.
Ok! Refactored to use new helper methods on isort
settings, and I will follow up after this PR is merged to get rid of the check for "."
Do you mind adding a dedicated test for this? Sorry! |
// While the fix is specified as always available, | ||
// the presence of a user-specified required import (I002) | ||
// with a useless alias causes an infinite loop. So | ||
// in this case we keep the diagnostic but omit the fix. | ||
// See https://github.com/astral-sh/ruff/issues/14283 |
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 don't think our testing framework will accept that an always fixable fix isn't always fixable, which makes sense.
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.
Of course you're right! Changed to "Sometimes" with special message/fix title emitted in case of conflict.
Added! |
if checker | ||
.settings | ||
.isort | ||
.requires_module_import(alias.name.to_string(), Some(asname.to_string())) | ||
{ | ||
let diagnostic = Diagnostic::new( | ||
UselessImportAlias { | ||
required_import_conflict: true, | ||
}, | ||
alias.range(), | ||
); | ||
checker.diagnostics.push(diagnostic); | ||
return; | ||
} | ||
|
||
let mut diagnostic = Diagnostic::new( | ||
UselessImportAlias { | ||
required_import_conflict: false, | ||
}, | ||
alias.range(), | ||
); | ||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( | ||
asname.to_string(), | ||
alias.range(), | ||
))); | ||
checker.diagnostics.push(diagnostic); |
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.
Nit: To reduce the amount of repeated code
if checker | |
.settings | |
.isort | |
.requires_module_import(alias.name.to_string(), Some(asname.to_string())) | |
{ | |
let diagnostic = Diagnostic::new( | |
UselessImportAlias { | |
required_import_conflict: true, | |
}, | |
alias.range(), | |
); | |
checker.diagnostics.push(diagnostic); | |
return; | |
} | |
let mut diagnostic = Diagnostic::new( | |
UselessImportAlias { | |
required_import_conflict: false, | |
}, | |
alias.range(), | |
); | |
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( | |
asname.to_string(), | |
alias.range(), | |
))); | |
checker.diagnostics.push(diagnostic); | |
let mut diagnostic = Diagnostic::new( | |
UselessImportAlias { | |
required_import_conflict: true, | |
}, | |
alias.range(), | |
); | |
if !checker | |
.settings | |
.isort | |
.requires_module_import(alias.name.to_string(), Some(asname.to_string())) | |
{ | |
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( | |
asname.to_string(), | |
alias.range(), | |
))); | |
} | |
checker.diagnostics.push(diagnostic); |
let Some(asname) = &alias.asname else { | ||
return; | ||
}; | ||
if alias.name.contains('.') { | ||
return; | ||
} | ||
if alias.name.as_str() != asname.as_str() { | ||
return; | ||
} |
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.
Is it possible to move this as well into requires_member_import
?
if checker.settings.isort.requires_member_import( | ||
module.map(str::to_string), | ||
alias.name.to_string(), | ||
Some(asname.to_string()), | ||
level, | ||
) { | ||
let diagnostic = Diagnostic::new( | ||
UselessImportAlias { | ||
required_import_conflict: true, | ||
}, | ||
alias.range(), | ||
); | ||
checker.diagnostics.push(diagnostic); | ||
return; | ||
} | ||
|
||
let mut diagnostic = Diagnostic::new(UselessImportAlias, alias.range()); | ||
let mut diagnostic = Diagnostic::new( | ||
UselessImportAlias { | ||
required_import_conflict: false, | ||
}, | ||
alias.range(), | ||
); |
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.
Nit: Same as above, let's invert the check so that we don't need to duplicate the diagnostic creation
@@ -75,6 +75,29 @@ pub struct Settings { | |||
pub length_sort_straight: bool, | |||
} | |||
|
|||
impl Settings { | |||
pub fn requires_module_import(&self, name: String, as_name: Option<String>) -> bool { |
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.
Can we use these new methods in the isort
implementation as well? It would help to ensure that both implementations stay in sync
This PR handles a panic that occurs when applying unsafe fixes if a user inserts a required import (I002) that has a "useless alias" in it, like
import numpy as numpy
, and also selects PLC0414 (useless-import-alias)In this case, the fixes alternate between adding the required import statement, then removing the alias, until the recursion limit is reached. See linked issue for an example.
Tests to confirm panic resolved:
Closes #14283