-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[flake8-pyi
] Avoid syntax error from conflict with PIE790
(PYI021
)
#20010
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
Conversation
|
Awesome, thanks for working on this! One note, I believe the ruff/crates/ruff_linter/src/rules/flake8_pyi/mod.rs Lines 130 to 139 in 39f7a87
so only PYI021 is active still:
I think you'll probably want to create a copy of this test function and enable both the rules. Something like this: #[test_case(Path::new("PYI021.pyi"))]
fn pyi021_pie790_empty_function(path: &Path) -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_pyi").join(path).as_path(),
&settings::LinterSettings::for_rules([Rule::DocstringInStub, Rule::UnnecessaryPlaceholder]),
)?;
assert_diagnostics!(diagnostics);
Ok(())
} Or you could just hard-code the path in this case since I doubt we'll need multiple I think your fix is likely still correct, but this will make sure we're testing the right thing! |
Perfect, thanks for the suggestions. I'll make those changes tomorrow! |
Hey, I added the changes you suggested but I wasn't getting a pass on the test locally (as it was panicking after applying both fixes), I think there's something wrong with my isolation level check in the actual implementation. I've pushed up the code so you can see, but will have a play with it after work tomorrow and try to work out the issue |
I'll take a look. |
Ah sorry, this might actually be trickier than I expected. PYI021 only looks at the deferred definitions after the AST has been traversed, so We may have to move PYI021's check into the same phase of analysis as PIE790, which would be a bit more involved. ruff/crates/ruff_linter/src/checkers/ast/analyze/suite.rs Lines 8 to 12 in a50b389
|
Hey, having a look through now, will give my best guess but I'm sure you'll have a better idea! I'll commit as far as I get |
My initial plan is: |
flake8-pyi
] add isolation level for interaction between rules #19982 (PYI021)
Sorry, I don't mean to leave you hanging on this, I'm just not quite sure the best approach yet either. I don't think I mentioned this above, but I even tried something like this method added to our pub fn statement_id(&self, stmt: &ast::Stmt) -> Option<NodeId> {
self.nodes.iter().find(|node| self.statement(*node) == stmt)
} without much luck. Basically your idea sounds right to me, and is what I expected the fix to be too, but I'm not sure if it works with the way we're traversing the AST or calling this rule. Don't feel any pressure to keep working on this, I'll try to take another look at some point too. |
Hey no worries - I can see you're pretty busy on a load of other PRs! I'm happy to keep tweaking and chipping away at ideas, it's a good opportunity for me to get familiar with more of the codebase. Out of interest what pub fn statement_id(&self, stmt: &ast::Stmt) -> Option<NodeId> {
self.nodes.iter().find(|node| self.statement(*node) == stmt)
} Maybe passing the statement that holds the ellipsis expression could work? |
I've had a play, I added the method you shared. After running The fact that the I think that the self.node_id is always None because it's being called after the ast has finished traversing? [crates/ruff_python_semantic/src/model.rs:1414:9] &self.node_id = None
[crates/ruff_linter/src/rules/flake8_pyi/rules/docstring_in_stubs.rs:65:5] checker.semantic().statement_id(&statements[0]) = None
[crates/ruff_python_semantic/src/model.rs:1414:9] &self.node_id = None
[crates/ruff_linter/src/rules/flake8_pyi/rules/docstring_in_stubs.rs:66:5] checker.semantic().statement_id(&statements[1]) = None
[crates/ruff_python_semantic/src/model.rs:1414:9] &self.node_id = None |
That all sounds right to me. I think the best course of action is to move it to the earlier analysis phase, while we're still traversing the AST, like ruff/crates/ruff_linter/src/docstrings/extraction.rs Lines 6 to 8 in bbfcf6e
and the only reason we pass a So this rule actually seems to fit nicely into ruff/crates/ruff_linter/src/checkers/ast/analyze/suite.rs Lines 9 to 12 in bbfcf6e
unless there's some additional subtlety I'm missing. |
Awesome sounds good, cheers for the hint I'll give it a go! |
I think I've got it there now, all tests pass without any hardcoding. I moved I know you're busy so no rush to review, but let me know what you think |
Hey @ntBre just realised I forgot to tag you in my last message, just to let you know I think this one is ready for review whenever you get a chance, cheers! |
Sorry! forgot to update the snapshot tests, will do now |
Hmmm I'm not getting the same fail when I run the tests locally I've run the following commands: cargo insta test --review and this which I copied from the git action cargo insta test --all-features --unreferenced reject --test-runner nextest the tests all pass when I run the code locally, not sure if I've missed something 🤔 no rush at all @ntBre , but please let me know if theres something I've overlooked! cheers |
Specifically, the [`if_not_else`] lint will sometimes flag code to change the order of `if` and `else` bodies if this would allow a `!` to be removed. While perhaps tasteful in some cases, there are many cases in my experience where this bows to other competing concerns that impact readability. (Such as the relative sizes of the `if` and `else` bodies, or perhaps an ordering that just makes the code flow in a more natural way.) [`if_not_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#/if_not_else
…-sh#20027) ## Summary Resolves astral-sh#20011 Implemented alternative triggering condition for rule [`UP043`](https://docs.astral.sh/ruff/rules/unnecessary-default-type-args/) based on requirements outlined in [issue astral-sh#20011](astral-sh#20011) ## Test Plan Created .pyi file to ensure triggering the rule --------- Co-authored-by: Igor Drokin <[email protected]> Co-authored-by: dylwil3 <[email protected]>
…sh#19665) This adds a new `backend: internal | uv` option to the LSP `FormatOptions` allowing users to perform document and range formatting operations though uv. The idea here is to prototype a solution for users to transition to a `uv format` command without encountering version mismatches (and consequently, formatting differences) between the LSP's version of `ruff` and uv's version of `ruff`. The primarily alternative to this would be to use uv to discover the `ruff` version used to start the LSP in the first place. However, this would increase the scope of a minimal `uv format` command beyond "run a formatter", and raise larger questions about how uv should be used to coordinate toolchain discovery. I think those are good things to explore, but I'm hesitant to let them block a `uv format` implementation. Another downside of using uv to discover `ruff`, is that it needs to be implemented _outside_ the LSP; e.g., we'd need to change the instructions on how to run the LSP and implement it in each editor integration, like the VS Code plugin. --------- Co-authored-by: Dhruv Manilawala <[email protected]>
Might be a good candidate to squash when merging 😅 sorry! |
No worries, we always squash merge! It does look like something went slightly awry with the merge. At least on GitHub, it's showing 201 commits. I think it should still be okay, though. Only the relevant changes are showing up. |
Yeah sorry, I think I botched the rebase (thats what I get for rebasing when I'm firmly in camp merge!). I had a look at the changes too and they only are the ones I introduced so should be ok 🤞 |
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.
Thank you! This looks great overall, I just had a couple of nits and one slightly larger issue with the suite
handling. But even the fix for that shouldn't be too bad.
/// Return the [`NodeId`] for the given [`Stmt`]. | ||
pub fn statement_id(&self, stmt: &ast::Stmt) -> Option<NodeId> { |
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 this called anywhere now? I think we can revert this.
/// Return `true` if the model is in a `typing.Protocol` subclass or an abstract | ||
/// method. | ||
pub fn in_protocol_or_abstract_method(&self) -> 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.
I'd probably lean toward leaving this in the same file as before, unless there's a specific reason to move it out.
@@ -0,0 +1,3 @@ | |||
def check_isolation_level(mode: int) -> None: | |||
"""Shouldn't try to fix either.""" # ERROR PYI021 |
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.
This docstring seems slightly misleading since the snapshot shows it trying to fix both diagnostics 😄
In testing this locally in the CLI, the net effect is that we still emit both diagnostics when checking, but the isolation level means only the first diagnostic actually gets fixed. Since we sort by diagnostic range, that means PYI021 gets fixed and PIE790 does not. You don't need to write all of that in the docstring, to be clear, just recording my observations!
if checker.is_rule_enabled(Rule::UnnecessaryPlaceholder) { | ||
flake8_pie::rules::unnecessary_placeholder(checker, suite); | ||
} | ||
if checker.source_type.is_stub() && checker.is_rule_enabled(Rule::DocstringInStub) { |
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.
One thing I didn't realize about moving this here is that it now gets called for non-function (or -class or -module) suites, such as context managers or if
statements:
with foo():
"""docstring"""
pass
if True:
"""docstring"""
pass
I tested these locally, and we flag both of these. Fortunately, it looks like we already track a docstring_state
field on Checker
. We'd have to add a pub(crate)
getter function for this and make the DocstringState
and ExpectedDocstringKind
enums pub(crate)
too, but I think we can use that to check that we're in an actual docstring.
There's an existing SemanticModel::in_pep_257_docstring
method, but unfortunately that flag only gets set on the SemanticModel
once we're actually visiting the docstring.
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.
Hey thanks for this, I think I've addressed all of the comments from the review, please let me know if I've missed anything!
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.
Thank you, this is great!
I pushed one small commit fixing a few nits:
- revert changes to
unnecessary_placeholder.rs
current_docstring_state
->docstring_state
- exclude
ExpectedDocstringKind::Attribute
The last one was probably the biggest, but I also think it wasn't possible to reach based on how we're calling analyze::suite
. It just seemed better to be explicit in case that changed in the future.
flake8-pyi
] add isolation level for interaction between rules #19982 (PYI021)flake8-pyi
] Avoid syntax error from conflict with PIE790
(PYI021
)
Awesome, nice one - I didn't realise that we didn't have to worry about docstring_state clashing being both an attribute and a method of the struct that's good to know. Yeah checking explicitly for the expected docstring kind makes sense too, I'll have a closer read of the changes you made to see if there are any tricks to steal! Cheers for your help and guidance on this PR btw, I'll keep an eye out for any others that I think are my level of difficulty! |
Summary
First contribution so please let me know if I've made a mistake anywhere. This was aimed to fix #19982, it adds the isolation level to PYI021 to in the same style as the PIE790 rule.
fixes: #19982
Test Plan
I added a case to the PYI021.pyi file where the two rules are present as there wasn't a case with them both interacting, using the minimal reproducible example that @ntBre created on the issue (I think I got the
# ERROR
markings wrong, so please let me know how to fix that if I did).