Implement #ruff:ignore logical-line suppressions#23404
Conversation
|
|
Open question:
|
ba3586f to
e4a49f4
Compare
|
Am I interpreting the summary correctly that this case: # covers the entire header
def foo(
arg1,
arg2,
):
passonly covers the header, as in diagnostics in the range of I think in Rust these kinds of suppressions apply to the whole body of a block, so I'd expect this to apply to the whole function, with similar behavior for other statements with bodies, like I haven't looked closely at the diff yet, but this seems like it could simplify the implementation too if the suppression just applied to the statement starting on the next line. |
That's correct. It's meant to cover what Python considers one "logical line". I'd personally argue against having it cover the entire body, because that would overlap more with the disable/enable suppressions, and again leave ruff with no way to cover just a multi-line header without needing an end-of-line suppression on every line. |
| #### Line-level | ||
|
|
||
| Ruff supports a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html). | ||
| To ignore one or more violations within a single "logical" line (a statement or |
There was a problem hiding this comment.
I'd be curious to get your thoughts on the preview gating. If we make this a preview change, I think this should probably be a bit less prominent in the docs. For example, I'd probably leave the existing noqa docs here at the top of the section and put this at the end after In preview, ....
It doesn't feel like this necessarily has to be a preview change since it's a new feature and thus not breaking any existing code, but I wonder if we're going to want to make substantial changes to the syntax or to the exact semantics before we consider it stable. For that reason, I'd still lean toward doing this in preview.
I'd also love to get @MichaReiser's input on this before landing it since he'll be back next week.
There was a problem hiding this comment.
I agree with @ntBre's comment. I'd also be fine to split the docs out of this PR to unblock the implementation.
There was a problem hiding this comment.
I did move the docs below the #noqa docs in a previous update when adding the preview gating. If there's something else you think needs to be updated with the docs, I'm happy to do that.
I guess for me I'd feel more likely to want to suppress something for the whole block than just for the header, so I'd rather have: # ruff: ignore[...]
def foo():
stmt1
stm2
...have the semantics of: # ruff: disable[...]
def foo():
stmt1
stm2
...
# ruff: enable[...]instead of # ruff: disable[...]
def foo():
# ruff: enable[...]
stmt1
stm2
...So they both overlap with range suppressions (or am I missing something with range suppressions since you said "leave ruff with no way to cover just a multi-line header"), but the former seems more useful to me, although I can also see the argument for the latter since it's a narrower range. Functions might be a bit of a special case since many of their diagnostics are specific to the header. For classes, for example, you might want to suppress invalid-function-name (N802) for the whole class body. |
|
There's a couple problems with this example: # ruff:disable[code]
def foo():
# ruff:enable[code]
passFirst, it's a bit ugly and non-pythonic, and makes it harder to visually distinguish the function header. Second, the formatter will not let that stay that way — it will indent that second comment to match the indentation of the function body. Third, once that So currently, the only way to cover a multiline function/class/loop header, without also covering the body, is to use end-of-line Also, for the case of wanting to cover an entire class body, that's what the implicit block suppression was designed for: class Foo:
# ruff:disable[code] # implicitly ends with the class body
... |
|
Fair enough |
ecea25a to
6d8e7d1
Compare
6d8e7d1 to
c1fba96
Compare
|
Updated with preview gating to see what that would look like technically. The open question is if we want to warn when seeing suppression comments that aren't being utilized because ruff is running without preview mode, or if we should treat those as "invalid" expression comments. Treating them as invalid is perhaps the most "explicit" option, but might discourage experimentation with preview mode. I personally don't foresee any reason to alter the intent of the suppression comments once we land them, but I'm not against the idea of preview mode either. |
#ruff:ignore range suppressions#ruff:ignore logical-line suppressions
|
I like the idea of migrating away from There was broad user support for block-level/range suppressions for the linter, but I'm having trouble finding issues where users are needing the kind of suppressions implemented here. Moreover, there have been reports of users getting a little confused about the number of different kinds of suppression comments we support (and their different semantics). So I would hesitate to add a brand new kind of suppression comment without a clear request from users. It's true that there is an issue with the example # ruff:disable[code]
def foo():
# ruff:enable[code]
passwhen combined with the formatter, but I would view that as a bug or feature to request in the formatter - it's part of a broader issue where sometimes the formatter moves around directives, which I think it'd be good to do a better job at. Other edge cases might be addressed by changing the noqa-range for a rule. It's not clear to me that it's common enough of a situation where we need truly new functionality for a directive. All that to say, and I'm happy to be overruled by other maintainers or the community: I think if we are going to support Brent argued in favor of Rust-style suppressions, which I think are neat but it's not obvious that they are a good fit for the Python ecosystem. First because I'm not sure if there is precedent, so users might be unfamiliar with that kind of things, and second because Python scoping is a lot different than Rust's. Also, now that you've implemented block suppressions with disable/enable there would be a lot of overlap in functionality (as you point out). |
|
There were specific requests for a next-logical-line suppression format (I can try to dig it up) but it's also a very common pattern in other linters or type checkers (fixit, pyre, biome, eslint, etc). Biome and Fixit in particular also use Eg, since regular comments are almost exclusive used above the code they are documenting, an own-line suppression comment is most obviously going to cover the line below it, and by extension, increasing that scope to cover an entire logical line makes sense. Similarly, end-of-line suppression comments are clearly intended to cover the content they follow, so it makes sense to cover the physical line, and again covering an entire logical line when at the very end makes sense as an extension of that. I think perhaps the least intuitive part is allowing a trailing comment on the first line to affect an entire multi-line statement, and I'm alright with dropping that piece — I don't remember off hand if/what part of |
c1fba96 to
b6ffcf4
Compare
|
Simplified the trailing comments to only cover an entire multi-line statement if the trailing comment is on the very last line. This still allows suppressing multiline string literals like with |
c9df742 to
aef5860
Compare
|
I do think a dedicated I do agree with @dylwil3 that there's already a fair amount of confusion around the many suppression styles that Ruff supports, and adding yet another style without clear guidance and a migration strategy probably only makes this worse. This is why I feel we should wait and spend some more time to figure out some of the questions below before introducing
We might be able to skip some or all of those steps for now if we decide to only introduce own-line |
|
Some initial responses, though my position on them isn't super firm:
I think we should immediately promote
IMO we should document and consider
I'd probably suggest one of two options:
I like the transition story of the first (giving users a temporary choice based on which flag they use) but I like the simplicity and reduction of code paths from the second option.
If we're going to deprecate I would see this more as a requirement for deprecating
I would suggest "no", and that
We already do for
Other than renaming/aliasing flags that mention noqa, I wouldn't suggest any changes.
I don't think it needs to be a true replacement in that sense. We have the But I also think a |
|
This is probably an entirely separate design discussion, but I'm still curious about the preference for a The rest of the proposal around preferring |
If we recommend
I think we have to figure out a story for how to run Ruff alongside other linters before we can deprecate noqa. There are users that run multiple tools (e.g. #23780) and having to repeat the suppression comments seems annoying. After thinking about this a little more. I actually don't think that noqa is as big a deal for ruff's identity. Yes, it originates from another linter, but the name itself is very generic, the same way as Overall, I think there are enough wrinkles and open design questions that I don't see any urgency in landing this PR and we may want to take a step back and discuss what's the main goal: Is it to allow own-line suppression? Does this require a new IMO, human-readable names seem more impactful right now, which is why I'd focus on that first. Unless we think that a |
|
The main consideration to me is that as we transition users from rule codes to human readable names, especially in suppressions, then we lose cross-compatibility with other tools anyways. Would we want users mixing rule names into their |
aef5860 to
d03c94e
Compare
7089290 to
029f481
Compare
|
Simplified the trailing comment handling so it always just covers a single physical line (but still works for multi-line string literals). |
Follow-up to #23404 Add support for `#ruff:file-ignore[code]` style file-level suppressions as own-line comments at global module scope. The range covered by these suppressions is always the entirety of the file: ```py # ruff:file-ignore[ARG001] def foo( arg1, arg2, ): pass def bar( arg1, arg2, ): pass ``` This currently requires having preview mode enabled. Without preview mode, the comments are parsed and processed, but not materialized into active suppressions at runtime.
Adds support for
#ruff:ignore[code]style suppressions as either own-lineor end-of-line comments. The range covered by these suppressions is determined
by the comment's position relative to the associated logical line (statement
or suite header).
Standalone
ignorecomments apply to an entire multi-line statement/header if the comment appears above the first line:But will only apply to a single following line if it appears in the middle of a multi-line statement/header:
Trailing comments will only apply to a single physical line, similar to existing
#noqacomments:Intervening comments are allowed, which enables "stacking" of
#ruff:ignorecomments with other own-line pragma comments:Includes some refactoring of the structs to generalize the naming/terms used, otherwise the rest of the suppression system should be able to stay unchanged.
to do: