-
Notifications
You must be signed in to change notification settings - Fork 161
feat(code-action): add ignore comment to suppress error #956
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -283,15 +283,24 @@ impl Ignore { | |
false | ||
} | ||
|
||
/// Get all pyrefly ignores. | ||
#[inline] | ||
/// Get pyrefly ignores that are global, and do not have an error code. | ||
pub fn get_pyrefly_all_ignores(&self) -> SmallSet<LineNumber> { | ||
self._get_pyrefly_ignores(true) | ||
} | ||
|
||
#[inline] | ||
pub fn get_pyrefly_ignores(&self) -> SmallSet<LineNumber> { | ||
self._get_pyrefly_ignores(false) | ||
} | ||
fn _get_pyrefly_ignores(&self, only_all: bool) -> SmallSet<LineNumber> { | ||
self.ignores | ||
.iter() | ||
.filter(|ignore| { | ||
ignore | ||
.1 | ||
.iter() | ||
.any(|s| s.tool == Tool::Pyrefly && s.kind.is_empty()) | ||
.any(|s| s.tool == Tool::Pyrefly && (!only_all || s.kind.is_empty())) | ||
}) | ||
.map(|(line, _)| *line) | ||
.collect() | ||
|
@@ -306,6 +315,8 @@ mod tests { | |
|
||
#[test] | ||
fn test_parse_ignores() { | ||
// TODO: The second component of `expect` (the error code) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you add a test for the difference in only_all true vs false? (I'm still confused as to why it's necessary) |
||
// is not actually tested. | ||
fn f(x: &str, expect: &[(Tool, u32)]) { | ||
assert_eq!( | ||
&Ignore::parse_ignores(x) | ||
|
@@ -325,6 +336,7 @@ mod tests { | |
"code # mypy: ignore\n# pyre-fixme\nmore code", | ||
&[(Tool::Mypy, 1), (Tool::Pyre, 3)], | ||
); | ||
f("# pyrefly: ignore[bad-]\n", &[(Tool::Pyrefly, 2)]); | ||
f( | ||
"# type: ignore\n# mypy: ignore\n# bad\n\ncode", | ||
&[(Tool::Any, 4), (Tool::Mypy, 4)], | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,12 +25,16 @@ fn apply_patch(info: &ModuleInfo, range: TextRange, patch: String) -> (String, S | |
(before, after) | ||
} | ||
|
||
fn get_test_report(state: &State, handle: &Handle, position: TextSize) -> String { | ||
fn get_test_report_insert_import(state: &State, handle: &Handle, position: TextSize) -> String { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need insert_import? if we need it for all of these below, could you add a comment clarifying why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Upshot is I do think get_test_report needs updating to allow us to selectively apply code actions to the associated module. |
||
let mut report = "Code Actions Results:\n".to_owned(); | ||
let transaction = state.transaction(); | ||
for (title, info, range, patch) in transaction | ||
.local_quickfix_code_actions(handle, TextRange::new(position, position)) | ||
.unwrap_or_default() | ||
.into_iter() | ||
.flatten() | ||
// NOTE: We do NOT patch buffer with ignore comment code action that is | ||
// tested separately | ||
.filter(|(title, _, _, _)| !title.contains("ignore")) | ||
{ | ||
let (before, after) = apply_patch(&info, range, patch); | ||
report.push_str("# Title: "); | ||
|
@@ -45,6 +49,128 @@ fn get_test_report(state: &State, handle: &Handle, position: TextSize) -> String | |
report | ||
} | ||
|
||
fn get_test_report_ignore_code_action( | ||
state: &State, | ||
handle: &Handle, | ||
position: TextSize, | ||
) -> String { | ||
let mut report = "Code Actions Results:\n".to_owned(); | ||
let transaction = state.transaction(); | ||
for (title, info, range, patch) in transaction | ||
.local_quickfix_code_actions(handle, TextRange::new(position, position)) | ||
.into_iter() | ||
.flatten() | ||
// NOTE: We ONLY patch buffer with ignore comment code action so | ||
// we can test it separately | ||
.filter(|(title, _, _, _)| title.contains("ignore")) | ||
{ | ||
let (before, after) = apply_patch(&info, range, patch); | ||
report.push_str("# Title: "); | ||
report.push_str(&title); | ||
report.push('\n'); | ||
report.push_str("\n## Before:\n"); | ||
report.push_str(&before); | ||
report.push_str("\n## After:\n"); | ||
report.push_str(&after); | ||
report.push('\n'); | ||
} | ||
report | ||
} | ||
|
||
#[test] | ||
fn add_ignore_comment() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could this test instead be multiple smaller tests that each test something different? I don't know if you need multiple files either for any of these
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Certainly! |
||
let report = get_batched_lsp_operations_report_allow_error( | ||
&[ | ||
("a", "my_export = 3\nanother_thing = 4"), | ||
("b", "from a import another_thing\nmy_export\n# ^"), | ||
( | ||
"c", | ||
r#" | ||
def func(x: int) -> int: | ||
pass | ||
func(y) | ||
# ^"#, | ||
), | ||
( | ||
"d", | ||
r#" | ||
def func(x: int) -> int: | ||
# ^ | ||
pass | ||
func(y)"#, | ||
), | ||
], | ||
get_test_report_ignore_code_action, | ||
); | ||
assert_eq!( | ||
r#" | ||
# a.py | ||
|
||
# b.py | ||
2 | my_export | ||
^ | ||
Code Actions Results: | ||
# Title: Suppress UnknownName with ignore comment | ||
|
||
## Before: | ||
from a import another_thing | ||
my_export | ||
# ^ | ||
## After: | ||
from a import another_thing | ||
# pyrefly: ignore[unknown-name] | ||
my_export | ||
# ^ | ||
|
||
|
||
|
||
# c.py | ||
4 | func(y) | ||
^ | ||
Code Actions Results: | ||
# Title: Suppress UnknownName with ignore comment | ||
|
||
## Before: | ||
|
||
def func(x: int) -> int: | ||
pass | ||
func(y) | ||
# ^ | ||
## After: | ||
|
||
def func(x: int) -> int: | ||
pass | ||
# pyrefly: ignore[unknown-name] | ||
func(y) | ||
# ^ | ||
|
||
|
||
|
||
# d.py | ||
2 | def func(x: int) -> int: | ||
^ | ||
Code Actions Results: | ||
# Title: Suppress BadReturn with ignore comment | ||
|
||
## Before: | ||
|
||
def func(x: int) -> int: | ||
# ^ | ||
pass | ||
func(y) | ||
## After: | ||
|
||
# pyrefly: ignore[bad-return] | ||
def func(x: int) -> int: | ||
# ^ | ||
pass | ||
func(y) | ||
"# | ||
.trim(), | ||
report.trim() | ||
); | ||
} | ||
|
||
#[test] | ||
fn basic_test() { | ||
let report = get_batched_lsp_operations_report_allow_error( | ||
|
@@ -54,7 +180,7 @@ fn basic_test() { | |
("c", "my_export\n# ^"), | ||
("d", "my_export = 3\n"), | ||
], | ||
get_test_report, | ||
get_test_report_insert_import, | ||
); | ||
// We should suggest imports from both a and d, but not b. | ||
assert_eq!( | ||
|
@@ -102,7 +228,7 @@ fn insertion_test_comments() { | |
("a", "my_export = 3\n"), | ||
("b", "# i am a comment\nmy_export\n# ^"), | ||
], | ||
get_test_report, | ||
get_test_report_insert_import, | ||
); | ||
// We will insert the import after a comment, which might not be the intended target of the | ||
// comment. This is not ideal, but we cannot do much better without sophisticated comment | ||
|
@@ -139,7 +265,7 @@ fn insertion_test_existing_imports() { | |
("a", "my_export = 3\n"), | ||
("b", "from typing import List\nmy_export\n# ^"), | ||
], | ||
get_test_report, | ||
get_test_report_insert_import, | ||
); | ||
// Insert before all imports. This might not adhere to existing import sorting code style. | ||
assert_eq!( | ||
|
@@ -174,7 +300,7 @@ fn insertion_test_duplicate_imports() { | |
("a", "my_export = 3\nanother_thing = 4"), | ||
("b", "from a import another_thing\nmy_export\n# ^"), | ||
], | ||
get_test_report, | ||
get_test_report_insert_import, | ||
); | ||
// The insertion won't attempt to merge imports from the same module. | ||
// It's not illegal, but it would be nice if we do merge. | ||
|
Uh oh!
There was an error while loading. Please reload this page.
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 argument name is kinda confusing: could we name it something else?
and could you add a comment for why we ned to include ones that are non-empty in the call?