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

[red-knot] ruff_db: make diagnostic rendering prettier #15856

Merged
merged 1 commit into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

257 changes: 186 additions & 71 deletions crates/red_knot/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,21 @@ fn config_override() -> anyhow::Result<()> {
),
])?;

assert_cmd_snapshot!(case.command(), @r"
success: false
exit_code: 1
----- stdout -----
error[lint:unresolved-attribute] <temp_dir>/test.py:5:7 Type `<module 'sys'>` has no attribute `last_exc`
assert_cmd_snapshot!(case.command(), @r###"
success: false
exit_code: 1
----- stdout -----
error: lint:unresolved-attribute
--> <temp_dir>/test.py:5:7
|
4 | # Access `sys.last_exc` that was only added in Python 3.12
5 | print(sys.last_exc)
| ^^^^^^^^^^^^ Type `<module 'sys'>` has no attribute `last_exc`
|

----- stderr -----
");

----- stderr -----
"###);

assert_cmd_snapshot!(case.command().arg("--python-version").arg("3.12"), @r"
success: true
Expand Down Expand Up @@ -91,14 +98,22 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> {
])?;

// Make sure that the CLI fails when the `libs` directory is not in the search path.
assert_cmd_snapshot!(case.command().current_dir(case.project_dir().join("child")), @r#"
success: false
exit_code: 1
----- stdout -----
error[lint:unresolved-import] <temp_dir>/child/test.py:2:1 Cannot resolve import `utils`
assert_cmd_snapshot!(case.command().current_dir(case.project_dir().join("child")), @r###"
success: false
exit_code: 1
----- stdout -----
error: lint:unresolved-import
--> <temp_dir>/child/test.py:2:1
|
2 | from utils import add
| ^^^^^^^^^^^^^^^^^^^^^ Cannot resolve import `utils`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demonstrates that we should start capturing at least some full diagnostics in mdtests because highlighting the entire range here seems incorrect (compared to the import utils case below where it only highlights the name. I'll create an issue

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree. We need a fair bit of test coverage beyond what we have. I think a nice short-term target would be at least one test for every type of diagnostic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we need a lot more tests that capture the full diagnostic output, but I'm not convinced this should necessarily be an mdtest feature (#15867 (comment))

3 |
4 | stat = add(10, 15)
|

----- stderr -----
"#);

----- stderr -----
"###);

assert_cmd_snapshot!(case.command().current_dir(case.project_dir().join("child")).arg("--extra-search-path").arg("../libs"), @r"
success: true
Expand Down Expand Up @@ -180,15 +195,31 @@ fn configuration_rule_severity() -> anyhow::Result<()> {

// Assert that there's a possibly unresolved reference diagnostic
// and that division-by-zero has a severity of error by default.
assert_cmd_snapshot!(case.command(), @r"
success: false
exit_code: 1
----- stdout -----
error[lint:division-by-zero] <temp_dir>/test.py:2:5 Cannot divide object of type `Literal[4]` by zero
warning[lint:possibly-unresolved-reference] <temp_dir>/test.py:7:7 Name `x` used when possibly not defined
assert_cmd_snapshot!(case.command(), @r###"
success: false
exit_code: 1
----- stdout -----
error: lint:division-by-zero
--> <temp_dir>/test.py:2:5
|
2 | y = 4 / 0
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
3 |
4 | for a in range(0, y):
|

warning: lint:possibly-unresolved-reference
--> <temp_dir>/test.py:7:7
|
5 | x = a
6 |
7 | print(x) # possibly-unresolved-reference
| - Name `x` used when possibly not defined
|

----- stderr -----
");

----- stderr -----
"###);

case.write_file(
"pyproject.toml",
Expand All @@ -199,14 +230,22 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
"#,
)?;

assert_cmd_snapshot!(case.command(), @r"
success: false
exit_code: 1
----- stdout -----
warning[lint:division-by-zero] <temp_dir>/test.py:2:5 Cannot divide object of type `Literal[4]` by zero
assert_cmd_snapshot!(case.command(), @r###"
success: false
exit_code: 1
----- stdout -----
warning: lint:division-by-zero
--> <temp_dir>/test.py:2:5
|
2 | y = 4 / 0
| ----- Cannot divide object of type `Literal[4]` by zero
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok really dumb question, but why does annotate-snippets choose ^^^^ in some cases but ---- here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question actually! It chooses a different mark based on the severity level:

let mark = match annotation.annotation_type {
DisplayAnnotationType::Error => '^',
DisplayAnnotationType::Warning => '-',
DisplayAnnotationType::Info => '-',
DisplayAnnotationType::Note => '-',
DisplayAnnotationType::Help => '-',
DisplayAnnotationType::None => ' ',
};

Honestly kinda meh on this myself since it's somewhat non-obvious to me.

Copy link
Contributor

@sharkdp sharkdp Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense once you have e.g. (multiple) "Info" and "Error" annotations in a single diagnostic. Example from my own project (thinking about it, the function parameter annotation on top here should probably also be "info", but anyway):

image

I kind of like how the informational parts are just underlined with --- and the actual error is underlined with ^^^ as if to say: look, this is what you need to change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah that's a good point to consider too. I'll leave it as-is for now. This was just meant to get some decent rendering in. We can absolutely iterate on the exact format and what not.

3 |
4 | for a in range(0, y):
|

----- stderr -----
");

----- stderr -----
"###);

Ok(())
}
Expand All @@ -230,16 +269,42 @@ fn cli_rule_severity() -> anyhow::Result<()> {

// Assert that there's a possibly unresolved reference diagnostic
// and that division-by-zero has a severity of error by default.
assert_cmd_snapshot!(case.command(), @r"
success: false
exit_code: 1
----- stdout -----
error[lint:unresolved-import] <temp_dir>/test.py:2:8 Cannot resolve import `does_not_exit`
error[lint:division-by-zero] <temp_dir>/test.py:4:5 Cannot divide object of type `Literal[4]` by zero
warning[lint:possibly-unresolved-reference] <temp_dir>/test.py:9:7 Name `x` used when possibly not defined
assert_cmd_snapshot!(case.command(), @r###"
success: false
exit_code: 1
----- stdout -----
error: lint:unresolved-import
--> <temp_dir>/test.py:2:8
|
2 | import does_not_exit
| ^^^^^^^^^^^^^ Cannot resolve import `does_not_exit`
3 |
4 | y = 4 / 0
|

error: lint:division-by-zero
--> <temp_dir>/test.py:4:5
|
2 | import does_not_exit
3 |
4 | y = 4 / 0
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
5 |
6 | for a in range(0, y):
|

warning: lint:possibly-unresolved-reference
--> <temp_dir>/test.py:9:7
|
7 | x = a
8 |
9 | print(x) # possibly-unresolved-reference
| - Name `x` used when possibly not defined
|

----- stderr -----
");

----- stderr -----
"###);

assert_cmd_snapshot!(
case
Expand All @@ -250,15 +315,33 @@ fn cli_rule_severity() -> anyhow::Result<()> {
.arg("division-by-zero")
.arg("--warn")
.arg("unresolved-import"),
@r"
@r###"
success: false
exit_code: 1
----- stdout -----
warning[lint:unresolved-import] <temp_dir>/test.py:2:8 Cannot resolve import `does_not_exit`
warning[lint:division-by-zero] <temp_dir>/test.py:4:5 Cannot divide object of type `Literal[4]` by zero
warning: lint:unresolved-import
--> <temp_dir>/test.py:2:8
|
2 | import does_not_exit
| ------------- Cannot resolve import `does_not_exit`
3 |
4 | y = 4 / 0
|

warning: lint:division-by-zero
--> <temp_dir>/test.py:4:5
|
2 | import does_not_exit
3 |
4 | y = 4 / 0
| ----- Cannot divide object of type `Literal[4]` by zero
5 |
6 | for a in range(0, y):
|


----- stderr -----
"
"###
);

Ok(())
Expand All @@ -282,15 +365,31 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {

// Assert that there's a possibly unresolved reference diagnostic
// and that division-by-zero has a severity of error by default.
assert_cmd_snapshot!(case.command(), @r"
success: false
exit_code: 1
----- stdout -----
error[lint:division-by-zero] <temp_dir>/test.py:2:5 Cannot divide object of type `Literal[4]` by zero
warning[lint:possibly-unresolved-reference] <temp_dir>/test.py:7:7 Name `x` used when possibly not defined
assert_cmd_snapshot!(case.command(), @r###"
success: false
exit_code: 1
----- stdout -----
error: lint:division-by-zero
--> <temp_dir>/test.py:2:5
|
2 | y = 4 / 0
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
3 |
4 | for a in range(0, y):
|

warning: lint:possibly-unresolved-reference
--> <temp_dir>/test.py:7:7
|
5 | x = a
6 |
7 | print(x) # possibly-unresolved-reference
| - Name `x` used when possibly not defined
|

----- stderr -----
");

----- stderr -----
"###);

assert_cmd_snapshot!(
case
Expand All @@ -302,14 +401,22 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
// Override the error severity with warning
.arg("--ignore")
.arg("possibly-unresolved-reference"),
@r"
success: false
exit_code: 1
----- stdout -----
warning[lint:division-by-zero] <temp_dir>/test.py:2:5 Cannot divide object of type `Literal[4]` by zero
@r###"
success: false
exit_code: 1
----- stdout -----
warning: lint:division-by-zero
--> <temp_dir>/test.py:2:5
|
2 | y = 4 / 0
| ----- Cannot divide object of type `Literal[4]` by zero
3 |
4 | for a in range(0, y):
|

----- stderr -----
"

----- stderr -----
"###
);

Ok(())
Expand All @@ -329,14 +436,21 @@ fn configuration_unknown_rules() -> anyhow::Result<()> {
("test.py", "print(10)"),
])?;

assert_cmd_snapshot!(case.command(), @r"
success: false
exit_code: 1
----- stdout -----
warning[unknown-rule] <temp_dir>/pyproject.toml:3:1 Unknown lint rule `division-by-zer`
assert_cmd_snapshot!(case.command(), @r###"
success: false
exit_code: 1
----- stdout -----
warning: unknown-rule
--> <temp_dir>/pyproject.toml:3:1
|
2 | [tool.knot.rules]
3 | division-by-zer = "warn" # incorrect rule name
| --------------- Unknown lint rule `division-by-zer`
|

----- stderr -----
");

----- stderr -----
"###);

Ok(())
}
Expand All @@ -346,14 +460,15 @@ fn configuration_unknown_rules() -> anyhow::Result<()> {
fn cli_unknown_rules() -> anyhow::Result<()> {
let case = TestCase::with_file("test.py", "print(10)")?;

assert_cmd_snapshot!(case.command().arg("--ignore").arg("division-by-zer"), @r"
success: false
exit_code: 1
----- stdout -----
warning[unknown-rule] Unknown lint rule `division-by-zer`
assert_cmd_snapshot!(case.command().arg("--ignore").arg("division-by-zer"), @r###"
success: false
exit_code: 1
----- stdout -----
warning: unknown-rule: Unknown lint rule `division-by-zer`

----- stderr -----
");

----- stderr -----
"###);

Ok(())
}
Expand Down
11 changes: 10 additions & 1 deletion crates/red_knot_wasm/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ fn check() {

assert_eq!(
result,
vec!["error[lint:unresolved-import] /test.py:1:8 Cannot resolve import `random22`"]
vec![
"\
error: lint:unresolved-import
--> /test.py:1:8
|
1 | import random22
| ^^^^^^^^ Cannot resolve import `random22`
|
",
],
);
}
Loading
Loading