Skip to content
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

diagnostic rendering for import lint I001 is sub-optimal in some cases #15504

Closed
BurntSushi opened this issue Jan 15, 2025 · 3 comments · Fixed by #15518
Closed

diagnostic rendering for import lint I001 is sub-optimal in some cases #15504

BurntSushi opened this issue Jan 15, 2025 · 3 comments · Fixed by #15518
Assignees
Labels
bug Something isn't working diagnostics Related to reporting of diagnostics. help wanted Contributions especially welcome

Comments

@BurntSushi
Copy link
Member

On current main, here's what one example of a I001 diagnostic looks like:

lines_after_imports.pyi:1:1: I001 [*] Import block is un-sorted or un-formatted
   |
 1 | / from __future__ import annotations
 2 | |
 3 | | from typing import Any
 4 | |
 5 | | from requests import Session
 6 | |
 7 | | from my_first_party import my_first_party_object
 8 | |
 9 | | from . import my_local_folder_object
10 | |
11 | |
12 | |
13 | | class Thing(object):
   | |_^ I001
14 |     name: str
15 |     def __init__(self, name: str):
   |
   = help: Organize imports

In particular, the above comes from this snapshot:

https://github.com/astral-sh/ruff/blob/55a7f72035390cf1093e4da0cf2462e793bfbce1/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports.pyi.snap

In the above rendering, the ^ is pointing to a class definition, but it should be pointing to an import statement. After #15359 is merged, the rendering will improve somewhat to this:

lines_after_imports.pyi:1:1: I001 [*] Import block is un-sorted or un-formatted
   |
 1 | / from __future__ import annotations
 2 | |
 3 | | from typing import Any
 4 | |
 5 | | from requests import Session
 6 | |
 7 | | from my_first_party import my_first_party_object
 8 | |
 9 | | from . import my_local_folder_object
10 | |
11 | |
12 | |
   | |__^ I001
13 |   class Thing(object):
14 |     name: str
15 |     def __init__(self, name: str):
   |
   = help: Organize imports

This is better, but still not ideal. However, this isn't a problem in the diagnostic rendering itself (which is what #15359 didn't address it), but rather, in the spans generated by the I001 lint. To demonstrate, this is a minimal reproducer I made by debug printing the annotate-snippets::Message when running the above test:

use annotate_snippets::{Level, Renderer, Snippet};

fn main() {
    let source = "from __future__ import annotations\n\nfrom typing import Any\n\nfrom requests import Session\n\nfrom my_first_party import my_first_party_object\n\nfrom . import my_local_folder_object\n\n\n\nclass Thing(object):\n  name: str\n  def __init__(self, name: str):\n";
    let src_annotation = Level::Error.span(0..180).label("I001");
    let snippet =
        Snippet::source(source).line_start(1).annotation(src_annotation);
    let message = Level::Error.title("").snippet(snippet);

    println!("{}", Renderer::plain().render(message));
}

This outputs the same rendering as above. And the span, 0..180, corresponds to this substring:

from __future__ import annotations\n\nfrom typing import Any\n\nfrom requests import Session\n\nfrom my_first_party import my_first_party
_object\n\nfrom . import my_local_folder_object\n\n\n\n

So whatever is generating the spans here is including those empty lines at the end. We should fix the span generation to be tighter. (It's perhaps as simple as trimming the trailing lines from the range.)

@BurntSushi BurntSushi added bug Something isn't working help wanted Contributions especially welcome labels Jan 15, 2025
@AlexWaygood AlexWaygood added the diagnostics Related to reporting of diagnostics. label Jan 15, 2025
@dylwil3
Copy link
Collaborator

dylwil3 commented Jan 15, 2025

Edit: Ignore this, see comment below

This is confusing because if I add a debug print to display the line number, it seems to give the right answer for the range emitted from the lint:

> cargo run -p ruff -- check tmp.py --no-cache --preview --isolated --select I001
[crates/ruff_linter/src/rules/isort/rules/organize_imports.rs:96:5] &range = 0..176
[crates/ruff_linter/src/rules/isort/rules/organize_imports.rs:97:5] locator.compute_source_location(range.end()) = SourceLocation {
    row: 9,
    column: 37,
}
tmp.py:1:1: I001 [*] Import block is un-sorted or un-formatted
   |
 1 | / from __future__ import annotations
 2 | |
 3 | | from typing import Any
 4 | |
 5 | | from requests import Session
 6 | |
 7 | | from my_first_party import my_first_party_object
 8 | |
 9 | | from . import my_local_folder_object
10 | |
11 | |
12 | |
   | |__^ I001
13 |   class Foo:...
   |
   = help: Organize imports

Found 1 error.
[*] 1 fixable with the `--fix` option.

Is there some transformation done to the range before it gets to the diagnostic/user?

@MichaReiser
Copy link
Member

annotated-snippet determines where to draw the arrow. It's quiet possible that there's a bug if the range points to the end of the line?

@dylwil3
Copy link
Collaborator

dylwil3 commented Jan 15, 2025

ah, actually my mistake - I was printing the range from the "early return" diagnostic. there's additional logic in the lint that makes a different range, which explicitly includes trailing whitespace:

// Expand the span the entire range, including leading and trailing space.
let range = TextRange::new(locator.line_start(range.start()), trailing_line_end);

It looks like this choice is related to implementing the fix.

@dylwil3 dylwil3 self-assigned this Jan 15, 2025
dylwil3 added a commit that referenced this issue Jan 18, 2025
…5518)

## Summary
The fix range for sorting imports accounts for trailing whitespace, but
we should only show the trimmed range to the user when displaying the
diagnostic. So this PR changes the diagnostic range.

Closes #15504 

## Test Plan

Reviewed snapshot changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working diagnostics Related to reporting of diagnostics. help wanted Contributions especially welcome
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants