mirror of https://github.com/astral-sh/ruff
apply range suppressions to filter diagnostics (#21623)
Builds on range suppressions from https://github.com/astral-sh/ruff/pull/21441 Filters diagnostics based on parsed valid range suppressions. Issue: #3711
This commit is contained in:
parent
8ea18966cf
commit
4e67a219bb
|
|
@ -1440,6 +1440,78 @@ def function():
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ignore_noqa() -> Result<()> {
|
||||||
|
let fixture = CliTest::new()?;
|
||||||
|
fixture.write_file(
|
||||||
|
"ruff.toml",
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
select = ["F401"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fixture.write_file(
|
||||||
|
"noqa.py",
|
||||||
|
r#"
|
||||||
|
import os # noqa: F401
|
||||||
|
|
||||||
|
# ruff: disable[F401]
|
||||||
|
import sys
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// without --ignore-noqa
|
||||||
|
assert_cmd_snapshot!(fixture
|
||||||
|
.check_command()
|
||||||
|
.args(["--config", "ruff.toml"])
|
||||||
|
.arg("noqa.py"),
|
||||||
|
@r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
noqa.py:5:8: F401 [*] `sys` imported but unused
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(fixture
|
||||||
|
.check_command()
|
||||||
|
.args(["--config", "ruff.toml"])
|
||||||
|
.arg("noqa.py")
|
||||||
|
.args(["--preview"]),
|
||||||
|
@r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
// with --ignore-noqa --preview
|
||||||
|
assert_cmd_snapshot!(fixture
|
||||||
|
.check_command()
|
||||||
|
.args(["--config", "ruff.toml"])
|
||||||
|
.arg("noqa.py")
|
||||||
|
.args(["--ignore-noqa", "--preview"]),
|
||||||
|
@r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
noqa.py:2:8: F401 [*] `os` imported but unused
|
||||||
|
noqa.py:5:8: F401 [*] `sys` imported but unused
|
||||||
|
Found 2 errors.
|
||||||
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_noqa() -> Result<()> {
|
fn add_noqa() -> Result<()> {
|
||||||
let fixture = CliTest::new()?;
|
let fixture = CliTest::new()?;
|
||||||
|
|
@ -1632,6 +1704,100 @@ def unused(x): # noqa: ANN001, ARG001, D103
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_noqa_existing_file_level_noqa() -> Result<()> {
|
||||||
|
let fixture = CliTest::new()?;
|
||||||
|
fixture.write_file(
|
||||||
|
"ruff.toml",
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
select = ["F401"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fixture.write_file(
|
||||||
|
"noqa.py",
|
||||||
|
r#"
|
||||||
|
# ruff: noqa F401
|
||||||
|
import os
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(fixture
|
||||||
|
.check_command()
|
||||||
|
.args(["--config", "ruff.toml"])
|
||||||
|
.arg("noqa.py")
|
||||||
|
.arg("--preview")
|
||||||
|
.args(["--add-noqa"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
|
||||||
|
"#), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
let test_code =
|
||||||
|
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
|
||||||
|
|
||||||
|
insta::assert_snapshot!(test_code, @r"
|
||||||
|
# ruff: noqa F401
|
||||||
|
import os
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_noqa_existing_range_suppression() -> Result<()> {
|
||||||
|
let fixture = CliTest::new()?;
|
||||||
|
fixture.write_file(
|
||||||
|
"ruff.toml",
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
select = ["F401"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fixture.write_file(
|
||||||
|
"noqa.py",
|
||||||
|
r#"
|
||||||
|
# ruff: disable[F401]
|
||||||
|
import os
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(fixture
|
||||||
|
.check_command()
|
||||||
|
.args(["--config", "ruff.toml"])
|
||||||
|
.arg("noqa.py")
|
||||||
|
.arg("--preview")
|
||||||
|
.args(["--add-noqa"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
|
||||||
|
"#), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
let test_code =
|
||||||
|
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
|
||||||
|
|
||||||
|
insta::assert_snapshot!(test_code, @r"
|
||||||
|
# ruff: disable[F401]
|
||||||
|
import os
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_noqa_multiline_comment() -> Result<()> {
|
fn add_noqa_multiline_comment() -> Result<()> {
|
||||||
let fixture = CliTest::new()?;
|
let fixture = CliTest::new()?;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
def f():
|
||||||
|
# These should both be ignored by the range suppression.
|
||||||
|
# ruff: disable[E741, F841]
|
||||||
|
I = 1
|
||||||
|
# ruff: enable[E741, F841]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# These should both be ignored by the implicit range suppression.
|
||||||
|
# Should also generate an "unmatched suppression" warning.
|
||||||
|
# ruff:disable[E741,F841]
|
||||||
|
I = 1
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Neither warning is ignored, and an "unmatched suppression"
|
||||||
|
# should be generated.
|
||||||
|
I = 1
|
||||||
|
# ruff: enable[E741, F841]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# One should be ignored by the range suppression, and
|
||||||
|
# the other logged to the user.
|
||||||
|
# ruff: disable[E741]
|
||||||
|
I = 1
|
||||||
|
# ruff: enable[E741]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Test interleaved range suppressions. The first and last
|
||||||
|
# lines should each log a different warning, while the
|
||||||
|
# middle line should be completely silenced.
|
||||||
|
# ruff: disable[E741]
|
||||||
|
l = 0
|
||||||
|
# ruff: disable[F841]
|
||||||
|
O = 1
|
||||||
|
# ruff: enable[E741]
|
||||||
|
I = 2
|
||||||
|
# ruff: enable[F841]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# Neither of these are ignored and warnings are
|
||||||
|
# logged to user
|
||||||
|
# ruff: disable[E501]
|
||||||
|
I = 1
|
||||||
|
# ruff: enable[E501]
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# These should both be ignored by the range suppression,
|
||||||
|
# and an unusued noqa diagnostic should be logged.
|
||||||
|
# ruff:disable[E741,F841]
|
||||||
|
I = 1 # noqa: E741,F841
|
||||||
|
# ruff:enable[E741,F841]
|
||||||
|
|
@ -12,17 +12,20 @@ use crate::fix::edits::delete_comment;
|
||||||
use crate::noqa::{
|
use crate::noqa::{
|
||||||
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
|
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
|
||||||
};
|
};
|
||||||
|
use crate::preview::is_range_suppressions_enabled;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::rule_redirects::get_redirect_target;
|
use crate::rule_redirects::get_redirect_target;
|
||||||
use crate::rules::pygrep_hooks;
|
use crate::rules::pygrep_hooks;
|
||||||
use crate::rules::ruff;
|
use crate::rules::ruff;
|
||||||
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
|
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
|
||||||
use crate::settings::LinterSettings;
|
use crate::settings::LinterSettings;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
use crate::{Edit, Fix, Locator};
|
use crate::{Edit, Fix, Locator};
|
||||||
|
|
||||||
use super::ast::LintContext;
|
use super::ast::LintContext;
|
||||||
|
|
||||||
/// RUF100
|
/// RUF100
|
||||||
|
#[expect(clippy::too_many_arguments)]
|
||||||
pub(crate) fn check_noqa(
|
pub(crate) fn check_noqa(
|
||||||
context: &mut LintContext,
|
context: &mut LintContext,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
|
|
@ -31,6 +34,7 @@ pub(crate) fn check_noqa(
|
||||||
noqa_line_for: &NoqaMapping,
|
noqa_line_for: &NoqaMapping,
|
||||||
analyze_directives: bool,
|
analyze_directives: bool,
|
||||||
settings: &LinterSettings,
|
settings: &LinterSettings,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> Vec<usize> {
|
) -> Vec<usize> {
|
||||||
// Identify any codes that are globally exempted (within the current file).
|
// Identify any codes that are globally exempted (within the current file).
|
||||||
let file_noqa_directives =
|
let file_noqa_directives =
|
||||||
|
|
@ -40,7 +44,7 @@ pub(crate) fn check_noqa(
|
||||||
let mut noqa_directives =
|
let mut noqa_directives =
|
||||||
NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator);
|
NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator);
|
||||||
|
|
||||||
if file_noqa_directives.is_empty() && noqa_directives.is_empty() {
|
if file_noqa_directives.is_empty() && noqa_directives.is_empty() && suppressions.is_empty() {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,11 +64,19 @@ pub(crate) fn check_noqa(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply file-level suppressions first
|
||||||
if exemption.contains_secondary_code(code) {
|
if exemption.contains_secondary_code(code) {
|
||||||
ignored_diagnostics.push(index);
|
ignored_diagnostics.push(index);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply ranged suppressions next
|
||||||
|
if is_range_suppressions_enabled(settings) && suppressions.check_diagnostic(diagnostic) {
|
||||||
|
ignored_diagnostics.push(index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply end-of-line noqa suppressions last
|
||||||
let noqa_offsets = diagnostic
|
let noqa_offsets = diagnostic
|
||||||
.parent()
|
.parent()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ use crate::rules::ruff::rules::test_rules::{self, TEST_RULES, TestRule};
|
||||||
use crate::settings::types::UnsafeFixes;
|
use crate::settings::types::UnsafeFixes;
|
||||||
use crate::settings::{LinterSettings, TargetVersion, flags};
|
use crate::settings::{LinterSettings, TargetVersion, flags};
|
||||||
use crate::source_kind::SourceKind;
|
use crate::source_kind::SourceKind;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
use crate::{Locator, directives, fs};
|
use crate::{Locator, directives, fs};
|
||||||
|
|
||||||
pub(crate) mod float;
|
pub(crate) mod float;
|
||||||
|
|
@ -128,6 +129,7 @@ pub fn check_path(
|
||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
parsed: &Parsed<ModModule>,
|
parsed: &Parsed<ModModule>,
|
||||||
target_version: TargetVersion,
|
target_version: TargetVersion,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
// Aggregate all diagnostics.
|
// Aggregate all diagnostics.
|
||||||
let mut context = LintContext::new(path, locator.contents(), settings);
|
let mut context = LintContext::new(path, locator.contents(), settings);
|
||||||
|
|
@ -339,6 +341,7 @@ pub fn check_path(
|
||||||
&directives.noqa_line_for,
|
&directives.noqa_line_for,
|
||||||
parsed.has_valid_syntax(),
|
parsed.has_valid_syntax(),
|
||||||
settings,
|
settings,
|
||||||
|
suppressions,
|
||||||
);
|
);
|
||||||
if noqa.is_enabled() {
|
if noqa.is_enabled() {
|
||||||
for index in ignored.iter().rev() {
|
for index in ignored.iter().rev() {
|
||||||
|
|
@ -400,6 +403,9 @@ pub fn add_noqa_to_path(
|
||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse range suppression comments
|
||||||
|
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||||
|
|
||||||
// Generate diagnostics, ignoring any existing `noqa` directives.
|
// Generate diagnostics, ignoring any existing `noqa` directives.
|
||||||
let diagnostics = check_path(
|
let diagnostics = check_path(
|
||||||
path,
|
path,
|
||||||
|
|
@ -414,6 +420,7 @@ pub fn add_noqa_to_path(
|
||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add any missing `# noqa` pragmas.
|
// Add any missing `# noqa` pragmas.
|
||||||
|
|
@ -427,6 +434,7 @@ pub fn add_noqa_to_path(
|
||||||
&directives.noqa_line_for,
|
&directives.noqa_line_for,
|
||||||
stylist.line_ending(),
|
stylist.line_ending(),
|
||||||
reason,
|
reason,
|
||||||
|
&suppressions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -461,6 +469,9 @@ pub fn lint_only(
|
||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse range suppression comments
|
||||||
|
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||||
|
|
||||||
// Generate diagnostics.
|
// Generate diagnostics.
|
||||||
let diagnostics = check_path(
|
let diagnostics = check_path(
|
||||||
path,
|
path,
|
||||||
|
|
@ -475,6 +486,7 @@ pub fn lint_only(
|
||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
LinterResult {
|
LinterResult {
|
||||||
|
|
@ -566,6 +578,9 @@ pub fn lint_fix<'a>(
|
||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse range suppression comments
|
||||||
|
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||||
|
|
||||||
// Generate diagnostics.
|
// Generate diagnostics.
|
||||||
let diagnostics = check_path(
|
let diagnostics = check_path(
|
||||||
path,
|
path,
|
||||||
|
|
@ -580,6 +595,7 @@ pub fn lint_fix<'a>(
|
||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
if iterations == 0 {
|
if iterations == 0 {
|
||||||
|
|
@ -769,6 +785,7 @@ mod tests {
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::settings::LinterSettings;
|
use crate::settings::LinterSettings;
|
||||||
use crate::source_kind::SourceKind;
|
use crate::source_kind::SourceKind;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
use crate::test::{TestedNotebook, assert_notebook_path, test_contents, test_snippet};
|
use crate::test::{TestedNotebook, assert_notebook_path, test_contents, test_snippet};
|
||||||
use crate::{Locator, assert_diagnostics, directives, settings};
|
use crate::{Locator, assert_diagnostics, directives, settings};
|
||||||
|
|
||||||
|
|
@ -944,6 +961,7 @@ mod tests {
|
||||||
&locator,
|
&locator,
|
||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||||
let mut diagnostics = check_path(
|
let mut diagnostics = check_path(
|
||||||
path,
|
path,
|
||||||
None,
|
None,
|
||||||
|
|
@ -957,6 +975,7 @@ mod tests {
|
||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
diagnostics.sort_by(Diagnostic::ruff_start_ordering);
|
diagnostics.sort_by(Diagnostic::ruff_start_ordering);
|
||||||
diagnostics
|
diagnostics
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,14 @@ use crate::Locator;
|
||||||
use crate::fs::relativize_path;
|
use crate::fs::relativize_path;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::rule_redirects::get_redirect_target;
|
use crate::rule_redirects::get_redirect_target;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
|
|
||||||
/// Generates an array of edits that matches the length of `messages`.
|
/// Generates an array of edits that matches the length of `messages`.
|
||||||
/// Each potential edit in the array is paired, in order, with the associated diagnostic.
|
/// Each potential edit in the array is paired, in order, with the associated diagnostic.
|
||||||
/// Each edit will add a `noqa` comment to the appropriate line in the source to hide
|
/// Each edit will add a `noqa` comment to the appropriate line in the source to hide
|
||||||
/// the diagnostic. These edits may conflict with each other and should not be applied
|
/// the diagnostic. These edits may conflict with each other and should not be applied
|
||||||
/// simultaneously.
|
/// simultaneously.
|
||||||
|
#[expect(clippy::too_many_arguments)]
|
||||||
pub fn generate_noqa_edits(
|
pub fn generate_noqa_edits(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
diagnostics: &[Diagnostic],
|
diagnostics: &[Diagnostic],
|
||||||
|
|
@ -34,11 +36,19 @@ pub fn generate_noqa_edits(
|
||||||
external: &[String],
|
external: &[String],
|
||||||
noqa_line_for: &NoqaMapping,
|
noqa_line_for: &NoqaMapping,
|
||||||
line_ending: LineEnding,
|
line_ending: LineEnding,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> Vec<Option<Edit>> {
|
) -> Vec<Option<Edit>> {
|
||||||
let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path);
|
let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path);
|
||||||
let exemption = FileExemption::from(&file_directives);
|
let exemption = FileExemption::from(&file_directives);
|
||||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
||||||
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
|
let comments = find_noqa_comments(
|
||||||
|
diagnostics,
|
||||||
|
locator,
|
||||||
|
&exemption,
|
||||||
|
&directives,
|
||||||
|
noqa_line_for,
|
||||||
|
suppressions,
|
||||||
|
);
|
||||||
build_noqa_edits_by_diagnostic(comments, locator, line_ending, None)
|
build_noqa_edits_by_diagnostic(comments, locator, line_ending, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -725,6 +735,7 @@ pub(crate) fn add_noqa(
|
||||||
noqa_line_for: &NoqaMapping,
|
noqa_line_for: &NoqaMapping,
|
||||||
line_ending: LineEnding,
|
line_ending: LineEnding,
|
||||||
reason: Option<&str>,
|
reason: Option<&str>,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
let (count, output) = add_noqa_inner(
|
let (count, output) = add_noqa_inner(
|
||||||
path,
|
path,
|
||||||
|
|
@ -735,6 +746,7 @@ pub(crate) fn add_noqa(
|
||||||
noqa_line_for,
|
noqa_line_for,
|
||||||
line_ending,
|
line_ending,
|
||||||
reason,
|
reason,
|
||||||
|
suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
fs::write(path, output)?;
|
fs::write(path, output)?;
|
||||||
|
|
@ -751,6 +763,7 @@ fn add_noqa_inner(
|
||||||
noqa_line_for: &NoqaMapping,
|
noqa_line_for: &NoqaMapping,
|
||||||
line_ending: LineEnding,
|
line_ending: LineEnding,
|
||||||
reason: Option<&str>,
|
reason: Option<&str>,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> (usize, String) {
|
) -> (usize, String) {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
|
|
@ -760,7 +773,14 @@ fn add_noqa_inner(
|
||||||
|
|
||||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
||||||
|
|
||||||
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
|
let comments = find_noqa_comments(
|
||||||
|
diagnostics,
|
||||||
|
locator,
|
||||||
|
&exemption,
|
||||||
|
&directives,
|
||||||
|
noqa_line_for,
|
||||||
|
suppressions,
|
||||||
|
);
|
||||||
|
|
||||||
let edits = build_noqa_edits_by_line(comments, locator, line_ending, reason);
|
let edits = build_noqa_edits_by_line(comments, locator, line_ending, reason);
|
||||||
|
|
||||||
|
|
@ -859,6 +879,7 @@ fn find_noqa_comments<'a>(
|
||||||
exemption: &'a FileExemption,
|
exemption: &'a FileExemption,
|
||||||
directives: &'a NoqaDirectives,
|
directives: &'a NoqaDirectives,
|
||||||
noqa_line_for: &NoqaMapping,
|
noqa_line_for: &NoqaMapping,
|
||||||
|
suppressions: &Suppressions,
|
||||||
) -> Vec<Option<NoqaComment<'a>>> {
|
) -> Vec<Option<NoqaComment<'a>>> {
|
||||||
// List of noqa comments, ordered to match up with `messages`
|
// List of noqa comments, ordered to match up with `messages`
|
||||||
let mut comments_by_line: Vec<Option<NoqaComment<'a>>> = vec![];
|
let mut comments_by_line: Vec<Option<NoqaComment<'a>>> = vec![];
|
||||||
|
|
@ -875,6 +896,12 @@ fn find_noqa_comments<'a>(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply ranged suppressions next
|
||||||
|
if suppressions.check_diagnostic(message) {
|
||||||
|
comments_by_line.push(None);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Is the violation ignored by a `noqa` directive on the parent line?
|
// Is the violation ignored by a `noqa` directive on the parent line?
|
||||||
if let Some(parent) = message.parent() {
|
if let Some(parent) = message.parent() {
|
||||||
if let Some(directive_line) =
|
if let Some(directive_line) =
|
||||||
|
|
@ -1253,6 +1280,7 @@ mod tests {
|
||||||
use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon};
|
use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon};
|
||||||
use crate::rules::pyflakes::rules::UnusedVariable;
|
use crate::rules::pyflakes::rules::UnusedVariable;
|
||||||
use crate::rules::pyupgrade::rules::PrintfStringFormatting;
|
use crate::rules::pyupgrade::rules::PrintfStringFormatting;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
use crate::{Edit, Violation};
|
use crate::{Edit, Violation};
|
||||||
use crate::{Locator, generate_noqa_edits};
|
use crate::{Locator, generate_noqa_edits};
|
||||||
|
|
||||||
|
|
@ -2848,6 +2876,7 @@ mod tests {
|
||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
None,
|
None,
|
||||||
|
&Suppressions::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(count, 0);
|
assert_eq!(count, 0);
|
||||||
assert_eq!(output, format!("{contents}"));
|
assert_eq!(output, format!("{contents}"));
|
||||||
|
|
@ -2872,6 +2901,7 @@ mod tests {
|
||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
None,
|
None,
|
||||||
|
&Suppressions::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(count, 1);
|
assert_eq!(count, 1);
|
||||||
assert_eq!(output, "x = 1 # noqa: F841\n");
|
assert_eq!(output, "x = 1 # noqa: F841\n");
|
||||||
|
|
@ -2903,6 +2933,7 @@ mod tests {
|
||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
None,
|
None,
|
||||||
|
&Suppressions::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(count, 1);
|
assert_eq!(count, 1);
|
||||||
assert_eq!(output, "x = 1 # noqa: E741, F841\n");
|
assert_eq!(output, "x = 1 # noqa: E741, F841\n");
|
||||||
|
|
@ -2934,6 +2965,7 @@ mod tests {
|
||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
None,
|
None,
|
||||||
|
&Suppressions::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(count, 0);
|
assert_eq!(count, 0);
|
||||||
assert_eq!(output, "x = 1 # noqa");
|
assert_eq!(output, "x = 1 # noqa");
|
||||||
|
|
@ -2956,6 +2988,7 @@ print(
|
||||||
let messages = [PrintfStringFormatting
|
let messages = [PrintfStringFormatting
|
||||||
.into_diagnostic(TextRange::new(12.into(), 79.into()), &source_file)];
|
.into_diagnostic(TextRange::new(12.into(), 79.into()), &source_file)];
|
||||||
let comment_ranges = CommentRanges::default();
|
let comment_ranges = CommentRanges::default();
|
||||||
|
let suppressions = Suppressions::default();
|
||||||
let edits = generate_noqa_edits(
|
let edits = generate_noqa_edits(
|
||||||
path,
|
path,
|
||||||
&messages,
|
&messages,
|
||||||
|
|
@ -2964,6 +2997,7 @@ print(
|
||||||
&[],
|
&[],
|
||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits,
|
edits,
|
||||||
|
|
@ -2987,6 +3021,7 @@ bar =
|
||||||
[UselessSemicolon.into_diagnostic(TextRange::new(4.into(), 5.into()), &source_file)];
|
[UselessSemicolon.into_diagnostic(TextRange::new(4.into(), 5.into()), &source_file)];
|
||||||
let noqa_line_for = NoqaMapping::default();
|
let noqa_line_for = NoqaMapping::default();
|
||||||
let comment_ranges = CommentRanges::default();
|
let comment_ranges = CommentRanges::default();
|
||||||
|
let suppressions = Suppressions::default();
|
||||||
let edits = generate_noqa_edits(
|
let edits = generate_noqa_edits(
|
||||||
path,
|
path,
|
||||||
&messages,
|
&messages,
|
||||||
|
|
@ -2995,6 +3030,7 @@ bar =
|
||||||
&[],
|
&[],
|
||||||
&noqa_line_for,
|
&noqa_line_for,
|
||||||
LineEnding::Lf,
|
LineEnding::Lf,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits,
|
edits,
|
||||||
|
|
|
||||||
|
|
@ -286,3 +286,8 @@ pub(crate) const fn is_s310_resolve_string_literal_bindings_enabled(
|
||||||
) -> bool {
|
) -> bool {
|
||||||
settings.preview.is_enabled()
|
settings.preview.is_enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/astral-sh/ruff/pull/21623
|
||||||
|
pub(crate) const fn is_range_suppressions_enabled(settings: &LinterSettings) -> bool {
|
||||||
|
settings.preview.is_enabled()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ mod tests {
|
||||||
use crate::settings::types::PreviewMode;
|
use crate::settings::types::PreviewMode;
|
||||||
use crate::settings::{LinterSettings, flags};
|
use crate::settings::{LinterSettings, flags};
|
||||||
use crate::source_kind::SourceKind;
|
use crate::source_kind::SourceKind;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
use crate::test::{test_contents, test_path, test_snippet};
|
use crate::test::{test_contents, test_path, test_snippet};
|
||||||
use crate::{Locator, assert_diagnostics, assert_diagnostics_diff, directives};
|
use crate::{Locator, assert_diagnostics, assert_diagnostics_diff, directives};
|
||||||
|
|
||||||
|
|
@ -955,6 +956,8 @@ mod tests {
|
||||||
&locator,
|
&locator,
|
||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
let suppressions =
|
||||||
|
Suppressions::from_tokens(&settings, locator.contents(), parsed.tokens());
|
||||||
let mut messages = check_path(
|
let mut messages = check_path(
|
||||||
Path::new("<filename>"),
|
Path::new("<filename>"),
|
||||||
None,
|
None,
|
||||||
|
|
@ -968,6 +971,7 @@ mod tests {
|
||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
messages.sort_by(Diagnostic::ruff_start_ordering);
|
messages.sort_by(Diagnostic::ruff_start_ordering);
|
||||||
let actual = messages
|
let actual = messages
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,25 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn range_suppressions() -> Result<()> {
|
||||||
|
assert_diagnostics_diff!(
|
||||||
|
Path::new("ruff/suppressions.py"),
|
||||||
|
&settings::LinterSettings::for_rules(vec![
|
||||||
|
Rule::UnusedVariable,
|
||||||
|
Rule::AmbiguousVariableName,
|
||||||
|
Rule::UnusedNOQA,
|
||||||
|
]),
|
||||||
|
&settings::LinterSettings::for_rules(vec![
|
||||||
|
Rule::UnusedVariable,
|
||||||
|
Rule::AmbiguousVariableName,
|
||||||
|
Rule::UnusedNOQA,
|
||||||
|
])
|
||||||
|
.with_preview_mode(),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ruf100_0() -> Result<()> {
|
fn ruf100_0() -> Result<()> {
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||||
|
---
|
||||||
|
--- Linter settings ---
|
||||||
|
-linter.preview = disabled
|
||||||
|
+linter.preview = enabled
|
||||||
|
|
||||||
|
--- Summary ---
|
||||||
|
Removed: 9
|
||||||
|
Added: 1
|
||||||
|
|
||||||
|
--- Removed ---
|
||||||
|
E741 Ambiguous variable name: `I`
|
||||||
|
--> suppressions.py:4:5
|
||||||
|
|
|
||||||
|
2 | # These should both be ignored by the range suppression.
|
||||||
|
3 | # ruff: disable[E741, F841]
|
||||||
|
4 | I = 1
|
||||||
|
| ^
|
||||||
|
5 | # ruff: enable[E741, F841]
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
F841 [*] Local variable `I` is assigned to but never used
|
||||||
|
--> suppressions.py:4:5
|
||||||
|
|
|
||||||
|
2 | # These should both be ignored by the range suppression.
|
||||||
|
3 | # ruff: disable[E741, F841]
|
||||||
|
4 | I = 1
|
||||||
|
| ^
|
||||||
|
5 | # ruff: enable[E741, F841]
|
||||||
|
|
|
||||||
|
help: Remove assignment to unused variable `I`
|
||||||
|
1 | def f():
|
||||||
|
2 | # These should both be ignored by the range suppression.
|
||||||
|
3 | # ruff: disable[E741, F841]
|
||||||
|
- I = 1
|
||||||
|
4 + pass
|
||||||
|
5 | # ruff: enable[E741, F841]
|
||||||
|
6 |
|
||||||
|
7 |
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
|
||||||
|
E741 Ambiguous variable name: `I`
|
||||||
|
--> suppressions.py:12:5
|
||||||
|
|
|
||||||
|
10 | # Should also generate an "unmatched suppression" warning.
|
||||||
|
11 | # ruff:disable[E741,F841]
|
||||||
|
12 | I = 1
|
||||||
|
| ^
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
F841 [*] Local variable `I` is assigned to but never used
|
||||||
|
--> suppressions.py:12:5
|
||||||
|
|
|
||||||
|
10 | # Should also generate an "unmatched suppression" warning.
|
||||||
|
11 | # ruff:disable[E741,F841]
|
||||||
|
12 | I = 1
|
||||||
|
| ^
|
||||||
|
|
|
||||||
|
help: Remove assignment to unused variable `I`
|
||||||
|
9 | # These should both be ignored by the implicit range suppression.
|
||||||
|
10 | # Should also generate an "unmatched suppression" warning.
|
||||||
|
11 | # ruff:disable[E741,F841]
|
||||||
|
- I = 1
|
||||||
|
12 + pass
|
||||||
|
13 |
|
||||||
|
14 |
|
||||||
|
15 | def f():
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
|
||||||
|
E741 Ambiguous variable name: `I`
|
||||||
|
--> suppressions.py:26:5
|
||||||
|
|
|
||||||
|
24 | # the other logged to the user.
|
||||||
|
25 | # ruff: disable[E741]
|
||||||
|
26 | I = 1
|
||||||
|
| ^
|
||||||
|
27 | # ruff: enable[E741]
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
E741 Ambiguous variable name: `l`
|
||||||
|
--> suppressions.py:35:5
|
||||||
|
|
|
||||||
|
33 | # middle line should be completely silenced.
|
||||||
|
34 | # ruff: disable[E741]
|
||||||
|
35 | l = 0
|
||||||
|
| ^
|
||||||
|
36 | # ruff: disable[F841]
|
||||||
|
37 | O = 1
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
E741 Ambiguous variable name: `O`
|
||||||
|
--> suppressions.py:37:5
|
||||||
|
|
|
||||||
|
35 | l = 0
|
||||||
|
36 | # ruff: disable[F841]
|
||||||
|
37 | O = 1
|
||||||
|
| ^
|
||||||
|
38 | # ruff: enable[E741]
|
||||||
|
39 | I = 2
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
F841 [*] Local variable `O` is assigned to but never used
|
||||||
|
--> suppressions.py:37:5
|
||||||
|
|
|
||||||
|
35 | l = 0
|
||||||
|
36 | # ruff: disable[F841]
|
||||||
|
37 | O = 1
|
||||||
|
| ^
|
||||||
|
38 | # ruff: enable[E741]
|
||||||
|
39 | I = 2
|
||||||
|
|
|
||||||
|
help: Remove assignment to unused variable `O`
|
||||||
|
34 | # ruff: disable[E741]
|
||||||
|
35 | l = 0
|
||||||
|
36 | # ruff: disable[F841]
|
||||||
|
- O = 1
|
||||||
|
37 | # ruff: enable[E741]
|
||||||
|
38 | I = 2
|
||||||
|
39 | # ruff: enable[F841]
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
|
||||||
|
F841 [*] Local variable `I` is assigned to but never used
|
||||||
|
--> suppressions.py:39:5
|
||||||
|
|
|
||||||
|
37 | O = 1
|
||||||
|
38 | # ruff: enable[E741]
|
||||||
|
39 | I = 2
|
||||||
|
| ^
|
||||||
|
40 | # ruff: enable[F841]
|
||||||
|
|
|
||||||
|
help: Remove assignment to unused variable `I`
|
||||||
|
36 | # ruff: disable[F841]
|
||||||
|
37 | O = 1
|
||||||
|
38 | # ruff: enable[E741]
|
||||||
|
- I = 2
|
||||||
|
39 | # ruff: enable[F841]
|
||||||
|
40 |
|
||||||
|
41 |
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Added ---
|
||||||
|
RUF100 [*] Unused `noqa` directive (unused: `E741`, `F841`)
|
||||||
|
--> suppressions.py:55:12
|
||||||
|
|
|
||||||
|
53 | # and an unusued noqa diagnostic should be logged.
|
||||||
|
54 | # ruff:disable[E741,F841]
|
||||||
|
55 | I = 1 # noqa: E741,F841
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
56 | # ruff:enable[E741,F841]
|
||||||
|
|
|
||||||
|
help: Remove unused `noqa` directive
|
||||||
|
52 | # These should both be ignored by the range suppression,
|
||||||
|
53 | # and an unusued noqa diagnostic should be logged.
|
||||||
|
54 | # ruff:disable[E741,F841]
|
||||||
|
- I = 1 # noqa: E741,F841
|
||||||
|
55 + I = 1
|
||||||
|
56 | # ruff:enable[E741,F841]
|
||||||
|
|
@ -465,6 +465,12 @@ impl LinterSettings {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_preview_mode(mut self) -> Self {
|
||||||
|
self.preview = PreviewMode::Enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve the [`TargetVersion`] to use for linting.
|
/// Resolve the [`TargetVersion`] to use for linting.
|
||||||
///
|
///
|
||||||
/// This method respects the per-file version overrides in
|
/// This method respects the per-file version overrides in
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use compact_str::CompactString;
|
use compact_str::CompactString;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
use ruff_db::diagnostic::Diagnostic;
|
||||||
use ruff_python_ast::token::{TokenKind, Tokens};
|
use ruff_python_ast::token::{TokenKind, Tokens};
|
||||||
use ruff_python_ast::whitespace::indentation;
|
use ruff_python_ast::whitespace::indentation;
|
||||||
use std::{error::Error, fmt::Formatter};
|
use std::{error::Error, fmt::Formatter};
|
||||||
|
|
@ -9,6 +10,9 @@ use ruff_python_trivia::Cursor;
|
||||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize, TextSlice};
|
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize, TextSlice};
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
|
|
||||||
|
use crate::preview::is_range_suppressions_enabled;
|
||||||
|
use crate::settings::LinterSettings;
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
enum SuppressionAction {
|
enum SuppressionAction {
|
||||||
|
|
@ -98,8 +102,8 @@ pub(crate) struct InvalidSuppression {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Suppressions {
|
pub struct Suppressions {
|
||||||
/// Valid suppression ranges with associated comments
|
/// Valid suppression ranges with associated comments
|
||||||
valid: Vec<Suppression>,
|
valid: Vec<Suppression>,
|
||||||
|
|
||||||
|
|
@ -112,9 +116,41 @@ pub(crate) struct Suppressions {
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
impl Suppressions {
|
impl Suppressions {
|
||||||
pub(crate) fn from_tokens(source: &str, tokens: &Tokens) -> Suppressions {
|
pub fn from_tokens(settings: &LinterSettings, source: &str, tokens: &Tokens) -> Suppressions {
|
||||||
let builder = SuppressionsBuilder::new(source);
|
if is_range_suppressions_enabled(settings) {
|
||||||
builder.load_from_tokens(tokens)
|
let builder = SuppressionsBuilder::new(source);
|
||||||
|
builder.load_from_tokens(tokens)
|
||||||
|
} else {
|
||||||
|
Suppressions::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
|
self.valid.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a diagnostic is suppressed by any known range suppressions
|
||||||
|
pub(crate) fn check_diagnostic(&self, diagnostic: &Diagnostic) -> bool {
|
||||||
|
if self.valid.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(code) = diagnostic.secondary_code() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(span) = diagnostic.primary_span() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(range) = span.range() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
for suppression in &self.valid {
|
||||||
|
if *code == suppression.code.as_str() && suppression.range.contains_range(range) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -457,9 +493,12 @@ mod tests {
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
use similar::DiffableStr;
|
use similar::DiffableStr;
|
||||||
|
|
||||||
use crate::suppression::{
|
use crate::{
|
||||||
InvalidSuppression, ParseError, Suppression, SuppressionAction, SuppressionComment,
|
settings::LinterSettings,
|
||||||
SuppressionParser, Suppressions,
|
suppression::{
|
||||||
|
InvalidSuppression, ParseError, Suppression, SuppressionAction, SuppressionComment,
|
||||||
|
SuppressionParser, Suppressions,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1376,7 +1415,11 @@ def bar():
|
||||||
/// Parse all suppressions and errors in a module for testing
|
/// Parse all suppressions and errors in a module for testing
|
||||||
fn debug(source: &'_ str) -> DebugSuppressions<'_> {
|
fn debug(source: &'_ str) -> DebugSuppressions<'_> {
|
||||||
let parsed = parse(source, ParseOptions::from(Mode::Module)).unwrap();
|
let parsed = parse(source, ParseOptions::from(Mode::Module)).unwrap();
|
||||||
let suppressions = Suppressions::from_tokens(source, parsed.tokens());
|
let suppressions = Suppressions::from_tokens(
|
||||||
|
&LinterSettings::default().with_preview_mode(),
|
||||||
|
source,
|
||||||
|
parsed.tokens(),
|
||||||
|
);
|
||||||
DebugSuppressions {
|
DebugSuppressions {
|
||||||
source,
|
source,
|
||||||
suppressions,
|
suppressions,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ use crate::packaging::detect_package_root;
|
||||||
use crate::settings::types::UnsafeFixes;
|
use crate::settings::types::UnsafeFixes;
|
||||||
use crate::settings::{LinterSettings, flags};
|
use crate::settings::{LinterSettings, flags};
|
||||||
use crate::source_kind::SourceKind;
|
use crate::source_kind::SourceKind;
|
||||||
|
use crate::suppression::Suppressions;
|
||||||
use crate::{Applicability, FixAvailability};
|
use crate::{Applicability, FixAvailability};
|
||||||
use crate::{Locator, directives};
|
use crate::{Locator, directives};
|
||||||
|
|
||||||
|
|
@ -234,6 +235,7 @@ pub(crate) fn test_contents<'a>(
|
||||||
&locator,
|
&locator,
|
||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||||
let messages = check_path(
|
let messages = check_path(
|
||||||
path,
|
path,
|
||||||
path.parent()
|
path.parent()
|
||||||
|
|
@ -249,6 +251,7 @@ pub(crate) fn test_contents<'a>(
|
||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
let source_has_errors = parsed.has_invalid_syntax();
|
let source_has_errors = parsed.has_invalid_syntax();
|
||||||
|
|
@ -299,6 +302,8 @@ pub(crate) fn test_contents<'a>(
|
||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let suppressions =
|
||||||
|
Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||||
let fixed_messages = check_path(
|
let fixed_messages = check_path(
|
||||||
path,
|
path,
|
||||||
None,
|
None,
|
||||||
|
|
@ -312,6 +317,7 @@ pub(crate) fn test_contents<'a>(
|
||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
if parsed.has_invalid_syntax() && !source_has_errors {
|
if parsed.has_invalid_syntax() && !source_has_errors {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use ruff_linter::{
|
||||||
packaging::detect_package_root,
|
packaging::detect_package_root,
|
||||||
settings::flags,
|
settings::flags,
|
||||||
source_kind::SourceKind,
|
source_kind::SourceKind,
|
||||||
|
suppression::Suppressions,
|
||||||
};
|
};
|
||||||
use ruff_notebook::Notebook;
|
use ruff_notebook::Notebook;
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
|
|
@ -118,6 +119,10 @@ pub(crate) fn check(
|
||||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||||
let directives = extract_directives(parsed.tokens(), Flags::all(), &locator, &indexer);
|
let directives = extract_directives(parsed.tokens(), Flags::all(), &locator, &indexer);
|
||||||
|
|
||||||
|
// Parse range suppression comments
|
||||||
|
let suppressions =
|
||||||
|
Suppressions::from_tokens(&settings.linter, locator.contents(), parsed.tokens());
|
||||||
|
|
||||||
// Generate checks.
|
// Generate checks.
|
||||||
let diagnostics = check_path(
|
let diagnostics = check_path(
|
||||||
&document_path,
|
&document_path,
|
||||||
|
|
@ -132,6 +137,7 @@ pub(crate) fn check(
|
||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
let noqa_edits = generate_noqa_edits(
|
let noqa_edits = generate_noqa_edits(
|
||||||
|
|
@ -142,6 +148,7 @@ pub(crate) fn check(
|
||||||
&settings.linter.external,
|
&settings.linter.external,
|
||||||
&directives.noqa_line_for,
|
&directives.noqa_line_for,
|
||||||
stylist.line_ending(),
|
stylist.line_ending(),
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut diagnostics_map = DiagnosticsMap::default();
|
let mut diagnostics_map = DiagnosticsMap::default();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::path::Path;
|
||||||
|
|
||||||
use js_sys::Error;
|
use js_sys::Error;
|
||||||
use ruff_linter::settings::types::PythonVersion;
|
use ruff_linter::settings::types::PythonVersion;
|
||||||
|
use ruff_linter::suppression::Suppressions;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
|
@ -212,6 +213,9 @@ impl Workspace {
|
||||||
&indexer,
|
&indexer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let suppressions =
|
||||||
|
Suppressions::from_tokens(&self.settings.linter, locator.contents(), parsed.tokens());
|
||||||
|
|
||||||
// Generate checks.
|
// Generate checks.
|
||||||
let diagnostics = check_path(
|
let diagnostics = check_path(
|
||||||
Path::new("<filename>"),
|
Path::new("<filename>"),
|
||||||
|
|
@ -226,6 +230,7 @@ impl Workspace {
|
||||||
source_type,
|
source_type,
|
||||||
&parsed,
|
&parsed,
|
||||||
target_version,
|
target_version,
|
||||||
|
&suppressions,
|
||||||
);
|
);
|
||||||
|
|
||||||
let source_code = locator.to_source_code();
|
let source_code = locator.to_source_code();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue