diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF101.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF101_0.py
similarity index 100%
rename from crates/ruff_linter/resources/test/fixtures/ruff/RUF101.py
rename to crates/ruff_linter/resources/test/fixtures/ruff/RUF101_0.py
diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF101_1.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF101_1.py
new file mode 100644
index 0000000000000..7d620f9c6e8a2
--- /dev/null
+++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF101_1.py
@@ -0,0 +1,13 @@
+"""Regression test for #14531.
+
+RUF101 should trigger here because the TCH rules have been recoded to TC.
+"""
+# ruff: noqa: TCH002
+
+from __future__ import annotations
+
+import local_module
+
+
+def func(sized: local_module.Container) -> int:
+ return len(sized)
diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs
index a2ecc7a34161f..c48ab99ad57e1 100644
--- a/crates/ruff_linter/src/checkers/noqa.rs
+++ b/crates/ruff_linter/src/checkers/noqa.rs
@@ -211,6 +211,7 @@ pub(crate) fn check_noqa(
&& !exemption.includes(Rule::RedirectedNOQA)
{
ruff::rules::redirected_noqa(diagnostics, &noqa_directives);
+ ruff::rules::redirected_file_noqa(diagnostics, &file_noqa_directives);
}
if settings.rules.enabled(Rule::BlanketNOQA)
diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs
index 0b51ac3008df4..ce670225b89c9 100644
--- a/crates/ruff_linter/src/noqa.rs
+++ b/crates/ruff_linter/src/noqa.rs
@@ -319,7 +319,7 @@ impl<'a> From<&'a FileNoqaDirectives<'a>> for FileExemption<'a> {
if directives
.lines()
.iter()
- .any(|line| ParsedFileExemption::All == line.parsed_file_exemption)
+ .any(|line| matches!(line.parsed_file_exemption, ParsedFileExemption::All))
{
FileExemption::All(codes)
} else {
@@ -362,7 +362,7 @@ impl<'a> FileNoqaDirectives<'a> {
let mut lines = vec![];
for range in comment_ranges {
- match ParsedFileExemption::try_extract(&locator.contents()[range]) {
+ match ParsedFileExemption::try_extract(range, locator.contents()) {
Err(err) => {
#[allow(deprecated)]
let line = locator.compute_line_index(range.start());
@@ -384,6 +384,7 @@ impl<'a> FileNoqaDirectives<'a> {
}
ParsedFileExemption::Codes(codes) => {
codes.iter().filter_map(|code| {
+ let code = code.as_str();
// Ignore externally-defined rules.
if external.iter().any(|external| code.starts_with(external)) {
return None;
@@ -424,21 +425,26 @@ impl<'a> FileNoqaDirectives<'a> {
/// An individual file-level exemption (e.g., `# ruff: noqa` or `# ruff: noqa: F401, F841`). Like
/// [`FileNoqaDirectives`], but only for a single line, as opposed to an aggregated set of exemptions
/// across a source file.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug)]
pub(crate) enum ParsedFileExemption<'a> {
/// The file-level exemption ignores all rules (e.g., `# ruff: noqa`).
All,
/// The file-level exemption ignores specific rules (e.g., `# ruff: noqa: F401, F841`).
- Codes(Vec<&'a str>),
+ Codes(Codes<'a>),
}
impl<'a> ParsedFileExemption<'a> {
- /// Return a [`ParsedFileExemption`] for a given comment line.
- fn try_extract(line: &'a str) -> Result