Skip to content
Open
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
62 changes: 41 additions & 21 deletions crates/pyrefly_python/src/ignore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
//! You can also use the name of the linter, e.g. `# pyright: ignore`,
//! `# pyrefly: ignore`.
//!
//! You can specify a specific error code, e.g. `# type: ignore[invalid-type]`.
//! You can specify a specific error code, e.g. `# pyrefly: ignore[bad-return]`.
//! For `# type: ignore[...]`, Pyrefly only honors codes prefixed with
//! `pyrefly:`, e.g. `# type: ignore[pyrefly:bad-return]`.
//! Note that Pyright will only honor such codes after `# pyright: ignore[code]`.
//!
//! You can also use `# mypy: ignore-errors`, `# pyrefly: ignore-errors`
Expand Down Expand Up @@ -266,10 +268,23 @@ impl Suppression {
pub fn tool(&self) -> Tool {
self.tool
}

fn matches_kind(&self, kind: &str) -> bool {
match self.tool {
Tool::Pyrefly => self.kind.is_empty() || self.kind.iter().any(|x| x == kind),
Tool::Type => {
self.kind.is_empty()
|| self
.kind
.iter()
.any(|x| x.strip_prefix("pyrefly:") == Some(kind))
}
_ => true,
}
}
}

/// Record the position of lines affected by `# type: ignore[valid-type]` suppressions.
/// For now we don't record the content of the ignore, but we could.
/// Record the position of lines affected by ignore suppressions.
#[derive(Debug, Clone, Default)]
pub struct Ignore {
/// The line number here represents the line that the suppression applies to,
Expand Down Expand Up @@ -368,16 +383,9 @@ impl Ignore {
enabled_ignores: &SmallSet<Tool>,
) -> bool {
if let Some(suppressions) = self.ignores.get(&start_line)
&& suppressions.iter().any(|supp| {
enabled_ignores.contains(&supp.tool)
&& match supp.tool {
// We only check the subkind if they do `# pyrefly: ignore`
Tool::Pyrefly => {
supp.kind.is_empty() || supp.kind.iter().any(|x| x == kind)
}
_ => true,
}
})
&& suppressions
.iter()
.any(|supp| enabled_ignores.contains(&supp.tool) && supp.matches_kind(kind))
{
return true;
}
Expand All @@ -401,14 +409,10 @@ impl Ignore {
let Some(suppressions) = self.ignores.get(&suppression_line) else {
return false;
};
if suppressions.iter().any(|supp| {
enabled_ignores.contains(&supp.tool)
&& match supp.tool {
// We only check the subkind if they do `# pyrefly: ignore`
Tool::Pyrefly => supp.kind.is_empty() || supp.kind.iter().any(|x| x == kind),
_ => true,
}
}) {
if suppressions
.iter()
.any(|supp| enabled_ignores.contains(&supp.tool) && supp.matches_kind(kind))
{
return true;
}
false
Expand Down Expand Up @@ -671,6 +675,22 @@ x = """
f("type: ignore[hello", Some(Tool::Type), &["hello"]);
}

#[test]
fn test_type_ignore_specific_codes_require_pyrefly_prefix() {
let enabled = Tool::default_enabled();
let line = LineNumber::from_zero_indexed(0);

let blanket = Ignore::new("x: int = '' # type: ignore");
assert!(blanket.is_ignored(line, "bad-assignment", &enabled));

let mypy_code = Ignore::new("x: int = '' # type: ignore[assignment]");
assert!(!mypy_code.is_ignored(line, "bad-assignment", &enabled));

let pyrefly_code = Ignore::new("x: int = '' # type: ignore[pyrefly:bad-assignment]");
assert!(pyrefly_code.is_ignored(line, "bad-assignment", &enabled));
assert!(!pyrefly_code.is_ignored(line, "bad-return", &enabled));
}

#[test]
fn test_find_comment_start_in_line() {
// Test basic comment finding
Expand Down
30 changes: 30 additions & 0 deletions pyrefly/lib/test/suppression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,36 @@ z: int = "3" # E: `Literal['3']` is not assignable to `int`
"#,
);

testcase!(
test_type_ignore_unrecognized_code_does_not_suppress,
r#"
def func(x: int) -> None:
pass

func(x="hello") # type: ignore[this-is-not-a-real-error] # E: Argument `Literal['hello']` is not assignable to parameter `x` with type `int`
"#,
);

testcase!(
test_type_ignore_unprefixed_pyrefly_code_does_not_suppress,
r#"
def func(x: int) -> None:
pass

func(x="hello") # type: ignore[bad-argument-type] # E: Argument `Literal['hello']` is not assignable to parameter `x` with type `int`
"#,
);

testcase!(
test_type_ignore_pyrefly_prefixed_code_suppresses,
r#"
def func(x: int) -> None:
pass

func(x="hello") # type: ignore[pyrefly:bad-argument-type]
"#,
);

testcase!(
test_ignore_attachment,
r#"
Expand Down
Loading