Skip to content

Commit

Permalink
Support pyright: ignore comments
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Apr 12, 2023
1 parent 61200d2 commit 7b3b643
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 31 deletions.
7 changes: 6 additions & 1 deletion crates/ruff/resources/test/fixtures/pygrep-hooks/PGH003_0.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
x = 1 # type: ignore
x = 1 # type ignore
x = 1 # type:ignore
x = 1 # type: ignore[attr-defined] # type: ignore

x = 1
x = 1 # type ignore
x = 1 # type ignore # noqa
x = 1 # type: ignore[attr-defined]
x = 1 # type: ignore[attr-defined, name-defined]
x = 1 # type: ignore[attr-defined] # type: ignore[type-mismatch]
x = 1 # type: ignore[type-mismatch] # noqa
x = 1 # type: ignore [attr-defined]
x = 1 # type: ignore [attr-defined, name-defined]
x = 1 # type: ignore [type-mismatch] # noqa
x = 1 # type: Union[int, str]
x = 1 # type: ignoreme
16 changes: 16 additions & 0 deletions crates/ruff/resources/test/fixtures/pygrep-hooks/PGH003_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
x = 1 # pyright: ignore
x = 1 # pyright:ignore
x = 1 # pyright: ignore[attr-defined] # pyright: ignore

x = 1
x = 1 # pyright ignore
x = 1 # pyright ignore # noqa
x = 1 # pyright: ignore[attr-defined]
x = 1 # pyright: ignore[attr-defined, name-defined]
x = 1 # pyright: ignore[attr-defined] # pyright: ignore[type-mismatch]
x = 1 # pyright: ignore[type-mismatch] # noqa
x = 1 # pyright: ignore [attr-defined]
x = 1 # pyright: ignore [attr-defined, name-defined]
x = 1 # pyright: ignore [type-mismatch] # noqa
x = 1 # pyright: Union[int, str]
x = 1 # pyright: ignoreme
4 changes: 1 addition & 3 deletions crates/ruff/src/checkers/physical_lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ pub fn check_physical_lines(
}

if enforce_blanket_type_ignore {
if let Some(diagnostic) = blanket_type_ignore(index, line) {
diagnostics.push(diagnostic);
}
blanket_type_ignore(&mut diagnostics, index, line);
}

if enforce_blanket_noqa {
Expand Down
11 changes: 6 additions & 5 deletions crates/ruff/src/rules/flake8_pyi/rules/type_comment_in_stub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ impl Violation for TypeCommentInStub {
}
}

static TYPE_COMMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^#\s*type:\s*([^#]+)(\s*#.*?)?$").unwrap());
static TYPE_IGNORE_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^#\s*type:\s*ignore([^#]+)?(\s*#.*?)?$").unwrap());

/// PYI033
pub fn type_comment_in_stub(tokens: &[LexResult]) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
Expand All @@ -60,3 +55,9 @@ pub fn type_comment_in_stub(tokens: &[LexResult]) -> Vec<Diagnostic> {

diagnostics
}

static TYPE_COMMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^#\s*(type|pyright):\s*([^#]+)(\s*#.*?)?$").unwrap());

static TYPE_IGNORE_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^#\s*(type|pyright):\s*ignore([^#]+)?(\s*#.*?)?$").unwrap());
1 change: 1 addition & 0 deletions crates/ruff/src/rules/pygrep_hooks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod tests {
#[test_case(Rule::DeprecatedLogWarn, Path::new("PGH002_0.py"); "PGH002_0")]
#[test_case(Rule::DeprecatedLogWarn, Path::new("PGH002_1.py"); "PGH002_1")]
#[test_case(Rule::BlanketTypeIgnore, Path::new("PGH003_0.py"); "PGH003_0")]
#[test_case(Rule::BlanketTypeIgnore, Path::new("PGH003_1.py"); "PGH003_1")]
#[test_case(Rule::BlanketNOQA, Path::new("PGH004_0.py"); "PGH004_0")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
Expand Down
94 changes: 80 additions & 14 deletions crates/ruff/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use anyhow::{anyhow, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_parser::ast::Location;
Expand All @@ -16,18 +17,83 @@ impl Violation for BlanketTypeIgnore {
}
}

static BLANKET_TYPE_IGNORE_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"# type:? *ignore($|\s)").unwrap());

/// PGH003 - use of blanket type ignore comments
pub fn blanket_type_ignore(lineno: usize, line: &str) -> Option<Diagnostic> {
BLANKET_TYPE_IGNORE_REGEX.find(line).map(|m| {
Diagnostic::new(
BlanketTypeIgnore,
Range::new(
Location::new(lineno + 1, m.start()),
Location::new(lineno + 1, m.end()),
),
)
})
/// PGH003
pub fn blanket_type_ignore(diagnostics: &mut Vec<Diagnostic>, lineno: usize, line: &str) {
for match_ in TYPE_IGNORE_PATTERN.find_iter(line) {
if let Ok(codes) = parse_type_ignore_tag(line[match_.end()..].trim()) {
if codes.is_empty() {
let start = line[..match_.start()].chars().count();
let end = start + line[match_.start()..match_.end()].chars().count();
diagnostics.push(Diagnostic::new(
BlanketTypeIgnore,
Range::new(
Location::new(lineno + 1, start),
Location::new(lineno + 1, end),
),
));
}
}
}
}

// See: https://github.com/python/mypy/blob/b43e0d34247a6d1b3b9d9094d184bbfcb9808bb9/mypy/fastparse.py#L248
static TYPE_IGNORE_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r"#\s*(type|pyright):\s*ignore\s*").unwrap());

// See: https://github.com/python/mypy/blob/b43e0d34247a6d1b3b9d9094d184bbfcb9808bb9/mypy/fastparse.py#L327
static TYPE_IGNORE_TAG_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\s*\[(?P<codes>[^]#]*)]\s*(#.*)?$").unwrap());

/// Parse the optional `[...]` tag in a `# type: ignore[...]` comment.
///
/// Returns a list of error codes to ignore, or an empty list if the tag is
/// a blanket ignore.
fn parse_type_ignore_tag(tag: &str) -> Result<Vec<&str>> {
// See: https://github.com/python/mypy/blob/b43e0d34247a6d1b3b9d9094d184bbfcb9808bb9/mypy/fastparse.py#L316
// No tag -- ignore all errors.
let trimmed = tag.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
return Ok(vec![]);
}

// Parse comma-separated list of error codes.
TYPE_IGNORE_TAG_PATTERN
.captures(tag)
.map(|captures| {
captures
.name("codes")
.unwrap()
.as_str()
.split(',')
.map(str::trim)
.collect()
})
.ok_or_else(|| anyhow!("Invalid type ignore tag: {tag}"))
}

#[cfg(test)]
mod tests {

#[test]
fn type_ignore_tag() {
let tag = "";
let result = super::parse_type_ignore_tag(tag);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Vec::<&str>::new());

let tag = "[attr-defined]";
let result = super::parse_type_ignore_tag(tag);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec!["attr-defined"]);

let tag = " [attr-defined]";
let result = super::parse_type_ignore_tag(tag);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec!["attr-defined"]);

let tag = "[attr-defined, misc]";
let result = super::parse_type_ignore_tag(tag);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec!["attr-defined", "misc"]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ PGH003_0.py:1:8: PGH003 Use specific rule codes when ignoring type issues
|
1 | x = 1 # type: ignore
| ^^^^^^^^^^^^^^ PGH003
2 | x = 1 # type ignore
3 | x = 1 # type:ignore
2 | x = 1 # type:ignore
3 | x = 1 # type: ignore[attr-defined] # type: ignore
|

PGH003_0.py:2:8: PGH003 Use specific rule codes when ignoring type issues
|
2 | x = 1 # type: ignore
3 | x = 1 # type ignore
3 | x = 1 # type:ignore
| ^^^^^^^^^^^^^ PGH003
4 | x = 1 # type:ignore
4 | x = 1 # type: ignore[attr-defined] # type: ignore
|

PGH003_0.py:3:8: PGH003 Use specific rule codes when ignoring type issues
PGH003_0.py:3:38: PGH003 Use specific rule codes when ignoring type issues
|
3 | x = 1 # type: ignore
4 | x = 1 # type ignore
5 | x = 1 # type:ignore
| ^^^^^^^^^^^^^ PGH003
4 | x = 1 # type:ignore
5 | x = 1 # type: ignore[attr-defined] # type: ignore
| ^^^^^^^^^^^^^^ PGH003
6 |
7 | x = 1
|
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
source: crates/ruff/src/rules/pygrep_hooks/mod.rs
---
PGH003_1.py:1:8: PGH003 Use specific rule codes when ignoring type issues
|
1 | x = 1 # pyright: ignore
| ^^^^^^^^^^^^^^^^^ PGH003
2 | x = 1 # pyright:ignore
3 | x = 1 # pyright: ignore[attr-defined] # pyright: ignore
|

PGH003_1.py:2:8: PGH003 Use specific rule codes when ignoring type issues
|
2 | x = 1 # pyright: ignore
3 | x = 1 # pyright:ignore
| ^^^^^^^^^^^^^^^^ PGH003
4 | x = 1 # pyright: ignore[attr-defined] # pyright: ignore
|

PGH003_1.py:3:41: PGH003 Use specific rule codes when ignoring type issues
|
3 | x = 1 # pyright: ignore
4 | x = 1 # pyright:ignore
5 | x = 1 # pyright: ignore[attr-defined] # pyright: ignore
| ^^^^^^^^^^^^^^^^^ PGH003
6 |
7 | x = 1
|


0 comments on commit 7b3b643

Please sign in to comment.