diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 17c241e7def6cd..e6a6c9b008a884 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -163,7 +163,7 @@ jobs: # Mapping of base image followed by a comma followed by one or more base tags (comma separated) # Note, org.opencontainers.image.version label will use the first base tag (use the most specific tag first) image-mapping: - - alpine:3.20,alpine3.20,alpine + - alpine:3.21,alpine3.21,alpine - debian:bookworm-slim,bookworm-slim,debian-slim - buildpack-deps:bookworm,bookworm,debian steps: diff --git a/crates/ruff/tests/lint.rs b/crates/ruff/tests/lint.rs index c826cd861c2757..702723d54b8f34 100644 --- a/crates/ruff/tests/lint.rs +++ b/crates/ruff/tests/lint.rs @@ -2470,7 +2470,7 @@ fn create_a005_module_structure(tempdir: &TempDir) -> Result<()> { Ok(()) } -/// Test A005 with `builtins-strict-checking = true` +/// Test A005 with `strict-checking = true` #[test] fn a005_module_shadowing_strict() -> Result<()> { let tempdir = TempDir::new()?; @@ -2482,7 +2482,7 @@ fn a005_module_shadowing_strict() -> Result<()> { assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) .arg("--config") - .arg(r#"lint.flake8-builtins.builtins-strict-checking = true"#) + .arg(r#"lint.flake8-builtins.strict-checking = true"#) .args(["--select", "A005"]) .current_dir(tempdir.path()), @r" @@ -2504,7 +2504,7 @@ fn a005_module_shadowing_strict() -> Result<()> { Ok(()) } -/// Test A005 with `builtins-strict-checking = false` +/// Test A005 with `strict-checking = false` #[test] fn a005_module_shadowing_non_strict() -> Result<()> { let tempdir = TempDir::new()?; @@ -2516,7 +2516,7 @@ fn a005_module_shadowing_non_strict() -> Result<()> { assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) .arg("--config") - .arg(r#"lint.flake8-builtins.builtins-strict-checking = false"#) + .arg(r#"lint.flake8-builtins.strict-checking = false"#) .args(["--select", "A005"]) .current_dir(tempdir.path()), @r" @@ -2535,7 +2535,7 @@ fn a005_module_shadowing_non_strict() -> Result<()> { Ok(()) } -/// Test A005 with `builtins-strict-checking` unset +/// Test A005 with `strict-checking` unset /// TODO(brent) This should currently match the strict version, but after the next minor /// release it will match the non-strict version directly above #[test] @@ -2556,11 +2556,7 @@ fn a005_module_shadowing_strict_default() -> Result<()> { ----- stdout ----- abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module collections/__init__.py:1:1: A005 Module `collections` shadows a Python standard-library module - collections/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module - foobar/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module - foobar/collections/__init__.py:1:1: A005 Module `collections` shadows a Python standard-library module - foobar/collections/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module - Found 6 errors. + Found 2 errors. ----- stderr ----- "); diff --git a/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap b/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap index a4669d8b15c284..7fe041a00d69b7 100644 --- a/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap +++ b/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap @@ -226,10 +226,12 @@ linter.flake8_bandit.hardcoded_tmp_directory = [ /dev/shm, ] linter.flake8_bandit.check_typed_exception = false +linter.flake8_bandit.extend_markup_names = [] +linter.flake8_bandit.allowed_markup_calls = [] linter.flake8_bugbear.extend_immutable_calls = [] -linter.flake8_builtins.builtins_allowed_modules = [] -linter.flake8_builtins.builtins_ignorelist = [] -linter.flake8_builtins.builtins_strict_checking = true +linter.flake8_builtins.allowed_modules = [] +linter.flake8_builtins.ignorelist = [] +linter.flake8_builtins.strict_checking = false linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|,\s)\d{4})* linter.flake8_copyright.author = none @@ -369,8 +371,6 @@ linter.pylint.max_public_methods = 20 linter.pylint.max_locals = 15 linter.pyupgrade.keep_runtime_typing = false linter.ruff.parenthesize_tuple_in_subscript = false -linter.ruff.extend_markup_names = [] -linter.ruff.allowed_markup_calls = [] # Formatter Settings formatter.exclude = [] diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF035.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704.py similarity index 62% rename from crates/ruff_linter/resources/test/fixtures/ruff/RUF035.py rename to crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704.py index 35cf7c8b8ad91c..7748a0ac40c236 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF035.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704.py @@ -2,17 +2,17 @@ from markupsafe import Markup, escape content = "" -Markup(f"unsafe {content}") # RUF035 -flask.Markup("unsafe {}".format(content)) # RUF035 +Markup(f"unsafe {content}") # S704 +flask.Markup("unsafe {}".format(content)) # S704 Markup("safe {}").format(content) flask.Markup(b"safe {}", encoding='utf-8').format(content) escape(content) -Markup(content) # RUF035 -flask.Markup("unsafe %s" % content) # RUF035 +Markup(content) # S704 +flask.Markup("unsafe %s" % content) # S704 Markup(object="safe") Markup(object="unsafe {}".format(content)) # Not currently detected # NOTE: We may be able to get rid of these false positives with red-knot # if it includes comprehensive constant expression detection/evaluation. -Markup("*" * 8) # RUF035 (false positive) -flask.Markup("hello {}".format("world")) # RUF035 (false positive) +Markup("*" * 8) # S704 (false positive) +flask.Markup("hello {}".format("world")) # S704 (false positive) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF035_extend_markup_names.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704_extend_markup_names.py similarity index 60% rename from crates/ruff_linter/resources/test/fixtures/ruff/RUF035_extend_markup_names.py rename to crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704_extend_markup_names.py index 2412badcbbc891..61612d4d02c134 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF035_extend_markup_names.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704_extend_markup_names.py @@ -2,5 +2,5 @@ from webhelpers.html import literal content = "" -Markup(f"unsafe {content}") # RUF035 -literal(f"unsafe {content}") # RUF035 +Markup(f"unsafe {content}") # S704 +literal(f"unsafe {content}") # S704 diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF035_skip_early_out.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704_skip_early_out.py similarity index 87% rename from crates/ruff_linter/resources/test/fixtures/ruff/RUF035_skip_early_out.py rename to crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704_skip_early_out.py index ce01813fee89b5..6bedf7441134f9 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF035_skip_early_out.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704_skip_early_out.py @@ -4,4 +4,4 @@ # additional markup names to be skipped if we don't import either # markupsafe or flask first. content = "" -literal(f"unsafe {content}") # RUF035 +literal(f"unsafe {content}") # S704 diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF035_whitelisted_markup_calls.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704_whitelisted_markup_calls.py similarity index 88% rename from crates/ruff_linter/resources/test/fixtures/ruff/RUF035_whitelisted_markup_calls.py rename to crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704_whitelisted_markup_calls.py index 424306a2e7726d..146517a1a1d013 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF035_whitelisted_markup_calls.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S704_whitelisted_markup_calls.py @@ -6,4 +6,4 @@ # indirect assignments are currently not supported cleaned = clean(content) -Markup(cleaned) # RUF035 +Markup(cleaned) # S704 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC005.py b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC005.py index dc5ce7c75e60ef..1030f6aaf54d40 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC005.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC005.py @@ -4,13 +4,6 @@ pass # TC005 -if False: - pass # TC005 - -if 0: - pass # TC005 - - def example(): if TYPE_CHECKING: pass # TC005 @@ -32,13 +25,6 @@ class Test: x: List -if False: - x: List - -if 0: - x: List - - from typing_extensions import TYPE_CHECKING if TYPE_CHECKING: diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/noqa.py b/crates/ruff_linter/resources/test/fixtures/ruff/noqa.py index 30e59400c6f2f3..f91ab752ac6d25 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/noqa.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/noqa.py @@ -19,5 +19,6 @@ def f(): def f(): - # Only `E741` should be ignored by the `noqa`. + # Neither of these are ignored and warning is + # logged to user I = 1 # noqa: E741.F841 diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 011902eb34911e..4d276f656f7fcf 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -428,7 +428,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { flake8_2020::rules::name_or_attribute(checker, expr); } if checker.enabled(Rule::DatetimeMinMax) { - flake8_datetimez::rules::datetime_max_min(checker, expr); + flake8_datetimez::rules::datetime_min_max(checker, expr); } if checker.enabled(Rule::BannedApi) { flake8_tidy_imports::rules::banned_attribute_access(checker, expr); @@ -1129,7 +1129,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { refurb::rules::int_on_sliced_str(checker, call); } if checker.enabled(Rule::UnsafeMarkupUse) { - ruff::rules::unsafe_markup_call(checker, call); + flake8_bandit::rules::unsafe_markup_call(checker, call); } if checker.enabled(Rule::MapIntVersionParsing) { ruff::rules::map_int_version_parsing(checker, call); diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index c4f98433bdd17a..fc72c1d1a3871d 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -246,11 +246,7 @@ impl<'a> Checker<'a> { notebook_index: Option<&'a NotebookIndex>, target_version: PythonVersion, ) -> Checker<'a> { - let mut semantic = SemanticModel::new(&settings.typing_modules, path, module); - if settings.preview.is_enabled() { - // Set the feature flag to test `TYPE_CHECKING` semantic changes - semantic.flags |= SemanticModelFlags::NEW_TYPE_CHECKING_BLOCK_DETECTION; - } + let semantic = SemanticModel::new(&settings.typing_modules, path, module); Self { parsed, parsed_type_annotation: None, @@ -293,7 +289,14 @@ impl<'a> Checker<'a> { if !self.noqa.is_enabled() { return false; } - noqa::rule_is_ignored(code, offset, self.noqa_line_for, self.locator) + + noqa::rule_is_ignored( + code, + offset, + self.noqa_line_for, + self.comment_ranges(), + self.locator, + ) } /// Create a [`Generator`] to generate source code based on the current AST state. diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index c48ab99ad57e14..4d3015eadda81a 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -38,7 +38,8 @@ pub(crate) fn check_noqa( let exemption = FileExemption::from(&file_noqa_directives); // Extract all `noqa` directives. - let mut noqa_directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator); + let mut noqa_directives = + NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator); // Indices of diagnostics that were ignored by a `noqa` directive. let mut ignored_diagnostics = vec![]; diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index c0e541fe573215..4a7e49b70d5a0d 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -192,7 +192,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet), (Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias), (Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel), - (Pylint, "C1802") => (RuleGroup::Preview, rules::pylint::rules::LenTest), + (Pylint, "C1802") => (RuleGroup::Stable, rules::pylint::rules::LenTest), (Pylint, "C1901") => (RuleGroup::Preview, rules::pylint::rules::CompareToEmptyString), (Pylint, "C2401") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiName), (Pylint, "C2403") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiImportName), @@ -288,7 +288,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "W0642") => (RuleGroup::Stable, rules::pylint::rules::SelfOrClsAssignment), (Pylint, "W0711") => (RuleGroup::Stable, rules::pylint::rules::BinaryOpException), (Pylint, "W1501") => (RuleGroup::Stable, rules::pylint::rules::BadOpenMode), - (Pylint, "W1507") => (RuleGroup::Preview, rules::pylint::rules::ShallowCopyEnviron), + (Pylint, "W1507") => (RuleGroup::Stable, rules::pylint::rules::ShallowCopyEnviron), (Pylint, "W1508") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarDefault), (Pylint, "W1509") => (RuleGroup::Stable, rules::pylint::rules::SubprocessPopenPreexecFn), (Pylint, "W1510") => (RuleGroup::Stable, rules::pylint::rules::SubprocessRunWithoutCheck), @@ -362,7 +362,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Bugbear, "904") => (RuleGroup::Stable, rules::flake8_bugbear::rules::RaiseWithoutFromInsideExcept), (Flake8Bugbear, "905") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict), (Flake8Bugbear, "909") => (RuleGroup::Preview, rules::flake8_bugbear::rules::LoopIteratorMutation), - (Flake8Bugbear, "911") => (RuleGroup::Preview, rules::flake8_bugbear::rules::BatchedWithoutExplicitStrict), + (Flake8Bugbear, "911") => (RuleGroup::Stable, rules::flake8_bugbear::rules::BatchedWithoutExplicitStrict), // flake8-blind-except (Flake8BlindExcept, "001") => (RuleGroup::Stable, rules::flake8_blind_except::rules::BlindExcept), @@ -386,7 +386,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Comprehensions, "17") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryMap), (Flake8Comprehensions, "18") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinDictCall), (Flake8Comprehensions, "19") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryComprehensionInCall), - (Flake8Comprehensions, "20") => (RuleGroup::Preview, rules::flake8_comprehensions::rules::UnnecessaryDictComprehensionForIterable), + (Flake8Comprehensions, "20") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryDictComprehensionForIterable), // flake8-debugger (Flake8Debugger, "0") => (RuleGroup::Stable, rules::flake8_debugger::rules::Debugger), @@ -489,7 +489,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Simplify, "223") => (RuleGroup::Stable, rules::flake8_simplify::rules::ExprAndFalse), (Flake8Simplify, "300") => (RuleGroup::Stable, rules::flake8_simplify::rules::YodaConditions), (Flake8Simplify, "401") => (RuleGroup::Stable, rules::flake8_simplify::rules::IfElseBlockInsteadOfDictGet), - (Flake8Simplify, "905") => (RuleGroup::Preview, rules::flake8_simplify::rules::SplitStaticString), + (Flake8Simplify, "905") => (RuleGroup::Stable, rules::flake8_simplify::rules::SplitStaticString), (Flake8Simplify, "910") => (RuleGroup::Stable, rules::flake8_simplify::rules::DictGetWithNoneDefault), (Flake8Simplify, "911") => (RuleGroup::Stable, rules::flake8_simplify::rules::ZipDictKeysAndValues), @@ -532,13 +532,13 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pyupgrade, "035") => (RuleGroup::Stable, rules::pyupgrade::rules::DeprecatedImport), (Pyupgrade, "036") => (RuleGroup::Stable, rules::pyupgrade::rules::OutdatedVersionBlock), (Pyupgrade, "037") => (RuleGroup::Stable, rules::pyupgrade::rules::QuotedAnnotation), - (Pyupgrade, "038") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604Isinstance), + (Pyupgrade, "038") => (RuleGroup::Deprecated, rules::pyupgrade::rules::NonPEP604Isinstance), (Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses), (Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias), (Pyupgrade, "041") => (RuleGroup::Stable, rules::pyupgrade::rules::TimeoutErrorAlias), (Pyupgrade, "042") => (RuleGroup::Preview, rules::pyupgrade::rules::ReplaceStrEnum), (Pyupgrade, "043") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryDefaultTypeArgs), - (Pyupgrade, "044") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP646Unpack), + (Pyupgrade, "044") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP646Unpack), (Pyupgrade, "045") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP604AnnotationOptional), (Pyupgrade, "046") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericClass), (Pyupgrade, "047") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericFunction), @@ -649,7 +649,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Bandit, "317") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousXMLSaxUsage), (Flake8Bandit, "318") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousXMLMiniDOMUsage), (Flake8Bandit, "319") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousXMLPullDOMUsage), - (Flake8Bandit, "320") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousXMLETreeUsage), + (Flake8Bandit, "320") => (RuleGroup::Deprecated, rules::flake8_bandit::rules::SuspiciousXMLETreeUsage), (Flake8Bandit, "321") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousFTPLibUsage), (Flake8Bandit, "323") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousUnverifiedContextUsage), (Flake8Bandit, "324") => (RuleGroup::Stable, rules::flake8_bandit::rules::HashlibInsecureHashFunction), @@ -690,6 +690,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Bandit, "612") => (RuleGroup::Stable, rules::flake8_bandit::rules::LoggingConfigInsecureListen), (Flake8Bandit, "701") => (RuleGroup::Stable, rules::flake8_bandit::rules::Jinja2AutoescapeFalse), (Flake8Bandit, "702") => (RuleGroup::Stable, rules::flake8_bandit::rules::MakoTemplates), + (Flake8Bandit, "704") => (RuleGroup::Stable, rules::flake8_bandit::rules::UnsafeMarkupUse), // flake8-boolean-trap (Flake8BooleanTrap, "001") => (RuleGroup::Stable, rules::flake8_boolean_trap::rules::BooleanTypeHintPositionalArgument), @@ -718,7 +719,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Datetimez, "007") => (RuleGroup::Stable, rules::flake8_datetimez::rules::CallDatetimeStrptimeWithoutZone), (Flake8Datetimez, "011") => (RuleGroup::Stable, rules::flake8_datetimez::rules::CallDateToday), (Flake8Datetimez, "012") => (RuleGroup::Stable, rules::flake8_datetimez::rules::CallDateFromtimestamp), - (Flake8Datetimez, "901") => (RuleGroup::Preview, rules::flake8_datetimez::rules::DatetimeMinMax), + (Flake8Datetimez, "901") => (RuleGroup::Stable, rules::flake8_datetimez::rules::DatetimeMinMax), // pygrep-hooks (PygrepHooks, "001") => (RuleGroup::Removed, rules::pygrep_hooks::rules::Eval), @@ -870,8 +871,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8TypeChecking, "003") => (RuleGroup::Stable, rules::flake8_type_checking::rules::TypingOnlyStandardLibraryImport), (Flake8TypeChecking, "004") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock), (Flake8TypeChecking, "005") => (RuleGroup::Stable, rules::flake8_type_checking::rules::EmptyTypeCheckingBlock), - (Flake8TypeChecking, "006") => (RuleGroup::Preview, rules::flake8_type_checking::rules::RuntimeCastValue), - (Flake8TypeChecking, "007") => (RuleGroup::Preview, rules::flake8_type_checking::rules::UnquotedTypeAlias), + (Flake8TypeChecking, "006") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeCastValue), + (Flake8TypeChecking, "007") => (RuleGroup::Stable, rules::flake8_type_checking::rules::UnquotedTypeAlias), (Flake8TypeChecking, "008") => (RuleGroup::Preview, rules::flake8_type_checking::rules::QuotedTypeAlias), (Flake8TypeChecking, "010") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeStringUnion), @@ -921,8 +922,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8UsePathlib, "205") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathGetctime), (Flake8UsePathlib, "206") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsSepSplit), (Flake8UsePathlib, "207") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::Glob), - (Flake8UsePathlib, "208") => (RuleGroup::Preview, rules::flake8_use_pathlib::violations::OsListdir), - (Flake8UsePathlib, "210") => (RuleGroup::Preview, rules::flake8_use_pathlib::rules::InvalidPathlibWithSuffix), + (Flake8UsePathlib, "208") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsListdir), + (Flake8UsePathlib, "210") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::InvalidPathlibWithSuffix), // flake8-logging-format (Flake8LoggingFormat, "001") => (RuleGroup::Stable, rules::flake8_logging_format::violations::LoggingStringFormat), @@ -949,7 +950,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { // fastapi (FastApi, "001") => (RuleGroup::Stable, rules::fastapi::rules::FastApiRedundantResponseModel), (FastApi, "002") => (RuleGroup::Stable, rules::fastapi::rules::FastApiNonAnnotatedDependency), - (FastApi, "003") => (RuleGroup::Preview, rules::fastapi::rules::FastApiUnusedPathParameter), + (FastApi, "003") => (RuleGroup::Stable, rules::fastapi::rules::FastApiUnusedPathParameter), // pydoclint (Pydoclint, "201") => (RuleGroup::Preview, rules::pydoclint::rules::DocstringMissingReturns), @@ -991,20 +992,20 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "032") => (RuleGroup::Stable, rules::ruff::rules::DecimalFromFloatLiteral), (Ruff, "033") => (RuleGroup::Stable, rules::ruff::rules::PostInitDefault), (Ruff, "034") => (RuleGroup::Stable, rules::ruff::rules::UselessIfElse), - (Ruff, "035") => (RuleGroup::Preview, rules::ruff::rules::UnsafeMarkupUse), + (Ruff, "035") => (RuleGroup::Removed, rules::ruff::rules::RuffUnsafeMarkupUse), (Ruff, "036") => (RuleGroup::Preview, rules::ruff::rules::NoneNotAtEndOfUnion), (Ruff, "037") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryEmptyIterableWithinDequeCall), (Ruff, "038") => (RuleGroup::Preview, rules::ruff::rules::RedundantBoolLiteral), (Ruff, "039") => (RuleGroup::Preview, rules::ruff::rules::UnrawRePattern), - (Ruff, "040") => (RuleGroup::Preview, rules::ruff::rules::InvalidAssertMessageLiteralArgument), - (Ruff, "041") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryNestedLiteral), + (Ruff, "040") => (RuleGroup::Stable, rules::ruff::rules::InvalidAssertMessageLiteralArgument), + (Ruff, "041") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryNestedLiteral), (Ruff, "043") => (RuleGroup::Preview, rules::ruff::rules::PytestRaisesAmbiguousPattern), (Ruff, "045") => (RuleGroup::Preview, rules::ruff::rules::ImplicitClassVarInDataclass), - (Ruff, "046") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryCastToInt), + (Ruff, "046") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryCastToInt), (Ruff, "047") => (RuleGroup::Preview, rules::ruff::rules::NeedlessElse), - (Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing), + (Ruff, "048") => (RuleGroup::Stable, rules::ruff::rules::MapIntVersionParsing), (Ruff, "049") => (RuleGroup::Preview, rules::ruff::rules::DataclassEnum), - (Ruff, "051") => (RuleGroup::Preview, rules::ruff::rules::IfKeyInDictDel), + (Ruff, "051") => (RuleGroup::Stable, rules::ruff::rules::IfKeyInDictDel), (Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable), (Ruff, "053") => (RuleGroup::Preview, rules::ruff::rules::ClassWithMixedTypeVars), (Ruff, "054") => (RuleGroup::Preview, rules::ruff::rules::IndentedFormFeed), @@ -1135,7 +1136,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Logging, "007") => (RuleGroup::Stable, rules::flake8_logging::rules::ExceptionWithoutExcInfo), (Flake8Logging, "009") => (RuleGroup::Stable, rules::flake8_logging::rules::UndocumentedWarn), (Flake8Logging, "014") => (RuleGroup::Preview, rules::flake8_logging::rules::ExcInfoOutsideExceptHandler), - (Flake8Logging, "015") => (RuleGroup::Preview, rules::flake8_logging::rules::RootLoggerCall), + (Flake8Logging, "015") => (RuleGroup::Stable, rules::flake8_logging::rules::RootLoggerCall), _ => return None, }) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index ac8d2cf5526759..57845b17a57fd2 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use log::warn; use ruff_diagnostics::{Diagnostic, Edit}; -use ruff_python_trivia::{indentation_at_offset, CommentRanges}; +use ruff_python_trivia::{indentation_at_offset, CommentRanges, Cursor}; use ruff_source_file::{LineEnding, LineRanges}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -36,13 +36,13 @@ pub fn generate_noqa_edits( ) -> Vec> { let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path); let exemption = FileExemption::from(&file_directives); - let directives = NoqaDirectives::from_commented_ranges(comment_ranges, 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); build_noqa_edits_by_diagnostic(comments, locator, line_ending) } -/// A directive to ignore a set of rules for a given line of Python source code (e.g., -/// `# noqa: F401, F841`). +/// A directive to ignore a set of rules either for a given line of Python source code or an entire file (e.g., +/// `# noqa: F401, F841` or `#ruff: noqa: F401, F841`). #[derive(Debug)] pub(crate) enum Directive<'a> { /// The `noqa` directive ignores all rules (e.g., `# noqa`). @@ -51,146 +51,6 @@ pub(crate) enum Directive<'a> { Codes(Codes<'a>), } -impl<'a> Directive<'a> { - /// Extract the noqa `Directive` from a line of Python source code. - pub(crate) fn try_extract(text: &'a str, offset: TextSize) -> Result, ParseError> { - for (char_index, char) in text.char_indices() { - // Only bother checking for the `noqa` literal if the character is `n` or `N`. - if !matches!(char, 'n' | 'N') { - continue; - } - - // Determine the start of the `noqa` literal. - if !matches!( - text[char_index..].as_bytes(), - [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] - ) { - continue; - } - - let noqa_literal_start = char_index; - let noqa_literal_end = noqa_literal_start + "noqa".len(); - - // Determine the start of the comment. - let mut comment_start = noqa_literal_start; - - // Trim any whitespace between the `#` character and the `noqa` literal. - comment_start = text[..comment_start].trim_end().len(); - - // The next character has to be the `#` character. - if !text[..comment_start].ends_with('#') { - continue; - } - comment_start -= '#'.len_utf8(); - - // If the next character is `:`, then it's a list of codes. Otherwise, it's a directive - // to ignore all rules. - let directive = match text[noqa_literal_end..].chars().next() { - Some(':') => { - // E.g., `# noqa: F401, F841`. - let mut codes_start = noqa_literal_end; - - // Skip the `:` character. - codes_start += ':'.len_utf8(); - - // Skip any whitespace between the `:` and the codes. - codes_start += text[codes_start..] - .find(|c: char| !c.is_whitespace()) - .unwrap_or(0); - - // Extract the comma-separated list of codes. - let mut codes = vec![]; - let mut codes_end = codes_start; - let mut leading_space = 0; - while let Some(code) = Self::lex_code(&text[codes_end + leading_space..]) { - codes_end += leading_space; - codes.push(Code { - code, - range: TextRange::at( - TextSize::try_from(codes_end).unwrap(), - code.text_len(), - ) - .add(offset), - }); - - codes_end += code.len(); - - // Codes can be comma- or whitespace-delimited. Compute the length of the - // delimiter, but only add it in the next iteration, once we find the next - // code. - if let Some(space_between) = - text[codes_end..].find(|c: char| !(c.is_whitespace() || c == ',')) - { - leading_space = space_between; - } else { - break; - } - } - - // If we didn't identify any codes, warn. - if codes.is_empty() { - return Err(ParseError::MissingCodes); - } - - let range = TextRange::new( - TextSize::try_from(comment_start).unwrap(), - TextSize::try_from(codes_end).unwrap(), - ); - - Self::Codes(Codes { - range: range.add(offset), - codes, - }) - } - None | Some('#') => { - // E.g., `# noqa` or `# noqa# ignore`. - let range = TextRange::new( - TextSize::try_from(comment_start).unwrap(), - TextSize::try_from(noqa_literal_end).unwrap(), - ); - Self::All(All { - range: range.add(offset), - }) - } - Some(c) if c.is_whitespace() => { - // E.g., `# noqa # ignore`. - let range = TextRange::new( - TextSize::try_from(comment_start).unwrap(), - TextSize::try_from(noqa_literal_end).unwrap(), - ); - Self::All(All { - range: range.add(offset), - }) - } - _ => return Err(ParseError::InvalidSuffix), - }; - - return Ok(Some(directive)); - } - - Ok(None) - } - - /// Lex an individual rule code (e.g., `F401`). - #[inline] - pub(crate) fn lex_code(line: &str) -> Option<&str> { - // Extract, e.g., the `F` in `F401`. - let prefix = line.chars().take_while(char::is_ascii_uppercase).count(); - // Extract, e.g., the `401` in `F401`. - let suffix = line[prefix..] - .chars() - .take_while(char::is_ascii_digit) - .count(); - if prefix > 0 && suffix > 0 { - // SAFETY: we can use `prefix` and `suffix` to index into `line` because we know that - // all characters in `line` are ASCII, i.e., a single byte. - Some(&line[..prefix + suffix]) - } else { - None - } - } -} - #[derive(Debug)] pub(crate) struct All { range: TextRange, @@ -263,13 +123,23 @@ pub(crate) fn rule_is_ignored( code: Rule, offset: TextSize, noqa_line_for: &NoqaMapping, + comment_ranges: &CommentRanges, locator: &Locator, ) -> bool { let offset = noqa_line_for.resolve(offset); let line_range = locator.line_range(offset); - match Directive::try_extract(locator.slice(line_range), line_range.start()) { - Ok(Some(Directive::All(_))) => true, - Ok(Some(Directive::Codes(codes))) => codes.includes(code), + let &[comment_range] = comment_ranges.comments_in_range(line_range) else { + return false; + }; + match lex_inline_noqa(comment_range, locator.contents()) { + Ok(Some(NoqaLexerOutput { + directive: Directive::All(_), + .. + })) => true, + Ok(Some(NoqaLexerOutput { + directive: Directive::Codes(codes), + .. + })) => codes.includes(code), _ => false, } } @@ -315,7 +185,7 @@ impl<'a> From<&'a FileNoqaDirectives<'a>> for FileExemption<'a> { if directives .lines() .iter() - .any(|line| matches!(line.parsed_file_exemption, ParsedFileExemption::All)) + .any(|line| matches!(line.parsed_file_exemption, Directive::All(_))) { FileExemption::All(codes) } else { @@ -330,7 +200,7 @@ pub(crate) struct FileNoqaDirectiveLine<'a> { /// The range of the text line for which the noqa directive applies. pub(crate) range: TextRange, /// The blanket noqa directive. - pub(crate) parsed_file_exemption: ParsedFileExemption<'a>, + pub(crate) parsed_file_exemption: Directive<'a>, /// The codes that are ignored by the parsed exemptions. pub(crate) matches: Vec, } @@ -358,27 +228,34 @@ impl<'a> FileNoqaDirectives<'a> { let mut lines = vec![]; for range in comment_ranges { - match ParsedFileExemption::try_extract(range, locator.contents()) { - Err(err) => { - #[allow(deprecated)] - let line = locator.compute_line_index(range.start()); - let path_display = relativize_path(path); - warn!("Invalid `# ruff: noqa` directive at {path_display}:{line}: {err}"); - } - Ok(Some(exemption)) => { - if indentation_at_offset(range.start(), locator.contents()).is_none() { + let lexed = lex_file_exemption(range, locator.contents()); + match lexed { + Ok(Some(NoqaLexerOutput { + warnings, + directive, + })) => { + let no_indentation_at_offset = + indentation_at_offset(range.start(), locator.contents()).is_none(); + if !warnings.is_empty() || no_indentation_at_offset { #[allow(deprecated)] let line = locator.compute_line_index(range.start()); let path_display = relativize_path(path); - warn!("Unexpected `# ruff: noqa` directive at {path_display}:{line}. File-level suppression comments must appear on their own line. For line-level suppression, omit the `ruff:` prefix."); - continue; + + for warning in warnings { + warn!("Missing or joined rule code(s) at {path_display}:{line}: {warning}"); + } + + if no_indentation_at_offset { + warn!("Unexpected `# ruff: noqa` directive at {path_display}:{line}. File-level suppression comments must appear on their own line. For line-level suppression, omit the `ruff:` prefix."); + continue; + } } - let matches = match &exemption { - ParsedFileExemption::All => { + let matches = match &directive { + Directive::All(_) => { vec![] } - ParsedFileExemption::Codes(codes) => { + Directive::Codes(codes) => { codes.iter().filter_map(|code| { let code = code.as_str(); // Ignore externally-defined rules. @@ -402,14 +279,19 @@ impl<'a> FileNoqaDirectives<'a> { lines.push(FileNoqaDirectiveLine { range, - parsed_file_exemption: exemption, + parsed_file_exemption: directive, matches, }); } + Err(err) => { + #[allow(deprecated)] + let line = locator.compute_line_index(range.start()); + let path_display = relativize_path(path); + warn!("Invalid `# ruff: noqa` directive at {path_display}:{line}: {err}"); + } Ok(None) => {} - } + }; } - Self(lines) } @@ -418,182 +300,404 @@ 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. +/// Output of lexing a `noqa` directive. #[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(Codes<'a>), +pub(crate) struct NoqaLexerOutput<'a> { + warnings: Vec, + directive: Directive<'a>, } -impl<'a> ParsedFileExemption<'a> { - /// Return a [`ParsedFileExemption`] for a given `comment_range` in `source`. - fn try_extract(comment_range: TextRange, source: &'a str) -> Result, ParseError> { - let line = &source[comment_range]; - let offset = comment_range.start(); - let init_line_len = line.text_len(); +/// Lexes in-line `noqa` comment, e.g. `# noqa: F401` +fn lex_inline_noqa( + comment_range: TextRange, + source: &str, +) -> Result>, LexicalError> { + let lexer = NoqaLexer::in_range(comment_range, source); + lexer.lex_inline_noqa() +} - let line = Self::lex_whitespace(line); - let Some(line) = Self::lex_char(line, '#') else { - return Ok(None); - }; - let comment_start = init_line_len - line.text_len() - '#'.text_len(); - let line = Self::lex_whitespace(line); +/// Lexes file-level exemption comment, e.g. `# ruff: noqa: F401` +fn lex_file_exemption( + comment_range: TextRange, + source: &str, +) -> Result>, LexicalError> { + let lexer = NoqaLexer::in_range(comment_range, source); + lexer.lex_file_exemption() +} - let Some(line) = Self::lex_flake8(line).or_else(|| Self::lex_ruff(line)) else { - return Ok(None); - }; +pub(crate) fn lex_codes(text: &str) -> Result>, LexicalError> { + let mut lexer = NoqaLexer::in_range(TextRange::new(TextSize::new(0), text.text_len()), text); + lexer.lex_codes()?; + Ok(lexer.codes) +} - let line = Self::lex_whitespace(line); - let Some(line) = Self::lex_char(line, ':') else { - return Ok(None); - }; - let line = Self::lex_whitespace(line); - let Some(line) = Self::lex_noqa(line) else { - return Ok(None); - }; - let line = Self::lex_whitespace(line); +/// Lexer for `noqa` comment lines. +/// +/// Assumed to be initialized with range or offset starting +/// at the `#` at the beginning of a `noqa` comment. +#[derive(Debug)] +struct NoqaLexer<'a> { + /// Length between offset and end of comment range + line_length: TextSize, + /// Contains convenience methods for lexing + cursor: Cursor<'a>, + /// Byte offset of the start of putative `noqa` comment + /// + /// Note: This is updated in the course of lexing in the case + /// where there are multiple comments in a comment range. + /// Ex) `# comment # noqa: F401` + /// start^ ^-- changed to here during lexing + offset: TextSize, + /// Non-fatal warnings collected during lexing + warnings: Vec, + /// Tracks whether we are lexing in a context with a missing delimiter + /// e.g. at `C` in `F401C402`. + missing_delimiter: bool, + /// Rule codes collected during lexing + codes: Vec>, +} - Ok(Some(if line.is_empty() { - // Ex) `# ruff: noqa` - Self::All - } else { - // Ex) `# ruff: noqa: F401, F841` - let Some(line) = Self::lex_char(line, ':') else { - return Err(ParseError::InvalidSuffix); - }; - let line = Self::lex_whitespace(line); - - // Extract the codes from the line (e.g., `F401, F841`). - let mut codes = vec![]; - let mut line = line; - while let Some(code) = Self::lex_code(line) { - let codes_end = init_line_len - line.text_len(); - codes.push(Code { - code, - range: TextRange::at(codes_end, code.text_len()).add(offset), - }); - line = &line[code.len()..]; +impl<'a> NoqaLexer<'a> { + /// Initialize [`NoqaLexer`] in the given range of source text. + fn in_range(range: TextRange, source: &'a str) -> Self { + Self { + line_length: range.len(), + offset: range.start(), + cursor: Cursor::new(&source[range]), + warnings: Vec::new(), + missing_delimiter: false, + codes: Vec::new(), + } + } - // Codes can be comma- or whitespace-delimited. - if let Some(rest) = Self::lex_delimiter(line).map(Self::lex_whitespace) { - line = rest; - } else { - break; - } + fn lex_inline_noqa(mut self) -> Result>, LexicalError> { + while !self.cursor.is_eof() { + // Skip over any leading content. + self.cursor.eat_while(|c| c != '#'); + + // This updates the offset and line length in the case of + // multiple comments in a range, e.g. + // `# First # Second # noqa` + self.offset += self.cursor.token_len(); + self.line_length -= self.cursor.token_len(); + self.cursor.start_token(); + + if !self.cursor.eat_char('#') { + continue; } - // If we didn't identify any codes, warn. - if codes.is_empty() { - return Err(ParseError::MissingCodes); + self.eat_whitespace(); + + if !is_noqa_uncased(self.cursor.as_str()) { + continue; } - let codes_end = init_line_len - line.text_len(); - let range = TextRange::new(comment_start, codes_end); - Self::Codes(Codes { - range: range.add(offset), - codes, - }) - })) + self.cursor.skip_bytes("noqa".len()); + + return Ok(Some(self.lex_directive()?)); + } + + Ok(None) } - /// Lex optional leading whitespace. - #[inline] - fn lex_whitespace(line: &str) -> &str { - line.trim_start() + fn lex_file_exemption(mut self) -> Result>, LexicalError> { + while !self.cursor.is_eof() { + // Skip over any leading content + self.cursor.eat_while(|c| c != '#'); + + // This updates the offset and line length in the case of + // multiple comments in a range, e.g. + // # First # Second # noqa + self.offset += self.cursor.token_len(); + self.line_length -= self.cursor.token_len(); + self.cursor.start_token(); + + // The remaining logic implements a simple regex + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^ + if !self.cursor.eat_char('#') { + continue; + } + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^ + self.eat_whitespace(); + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^^^^^^^^^^^ + if self.cursor.as_str().starts_with("ruff") { + self.cursor.skip_bytes("ruff".len()); + } else if self.cursor.as_str().starts_with("flake8") { + self.cursor.skip_bytes("flake8".len()); + } else { + continue; + } + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^ + self.eat_whitespace(); + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^ + if !self.cursor.eat_char(':') { + continue; + } + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^ + self.eat_whitespace(); + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^^^^^ + if !is_noqa_uncased(self.cursor.as_str()) { + continue; + } + self.cursor.skip_bytes("noqa".len()); + + return Ok(Some(self.lex_directive()?)); + } + Ok(None) } - /// Lex a specific character, or return `None` if the character is not the first character in - /// the line. - #[inline] - fn lex_char(line: &str, c: char) -> Option<&str> { - let mut chars = line.chars(); - if chars.next() == Some(c) { - Some(chars.as_str()) - } else { - None + /// Collect codes in `noqa` comment. + fn lex_directive(mut self) -> Result, LexicalError> { + let range = TextRange::at(self.offset, self.position()); + + match self.cursor.bump() { + // End of comment + // Ex) # noqa# A comment + None | Some('#') => { + return Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::All(All { range }), + }); + } + // Ex) # noqa: F401,F842 + Some(':') => self.lex_codes()?, + // Ex) # noqa A comment + // Ex) # noqa : F401 + Some(c) if c.is_whitespace() => { + self.eat_whitespace(); + match self.cursor.bump() { + Some(':') => self.lex_codes()?, + _ => { + return Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::All(All { range }), + }); + } + } + } + // Ex) #noqaA comment + // Ex) #noqaF401 + _ => { + return Err(LexicalError::InvalidSuffix); + } } + + let Some(last) = self.codes.last() else { + return Err(LexicalError::MissingCodes); + }; + + Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::Codes(Codes { + range: TextRange::new(self.offset, last.range.end()), + codes: self.codes, + }), + }) } - /// Lex the "flake8" prefix of a `noqa` directive. - #[inline] - fn lex_flake8(line: &str) -> Option<&str> { - line.strip_prefix("flake8") + fn lex_codes(&mut self) -> Result<(), LexicalError> { + // SAFETY: Every call to `lex_code` advances the cursor at least once. + while !self.cursor.is_eof() { + self.lex_code()?; + } + Ok(()) } - /// Lex the "ruff" prefix of a `noqa` directive. + fn lex_code(&mut self) -> Result<(), LexicalError> { + self.cursor.start_token(); + self.eat_whitespace(); + + // Ex) # noqa: F401, ,F841 + // ^^^^ + if self.cursor.first() == ',' { + self.warnings.push(LexicalWarning::MissingItem( + self.token_range().add(self.offset), + )); + self.cursor.eat_char(','); + return Ok(()); + } + + let before_code = self.cursor.as_str(); + // Reset start of token so it does not include whitespace + self.cursor.start_token(); + match self.cursor.bump() { + // Ex) # noqa: F401 + // ^ + Some(c) if c.is_ascii_uppercase() => { + self.cursor.eat_while(|chr| chr.is_ascii_uppercase()); + if !self.cursor.eat_if(|c| c.is_ascii_digit()) { + // Fail hard if we're already attempting + // to lex squashed codes, e.g. `F401Fabc` + if self.missing_delimiter { + return Err(LexicalError::InvalidCodeSuffix); + } + // Otherwise we've reached the first invalid code, + // so it could be a comment and we just stop parsing. + // Ex) #noqa: F401 A comment + // we're here^ + self.cursor.skip_bytes(self.cursor.as_str().len()); + return Ok(()); + } + self.cursor.eat_while(|chr| chr.is_ascii_digit()); + + let code = &before_code[..self.cursor.token_len().to_usize()]; + + self.push_code(code); + + if self.cursor.is_eof() { + return Ok(()); + } + + self.missing_delimiter = match self.cursor.first() { + ',' => { + self.cursor.eat_char(','); + false + } + + // Whitespace is an allowed delimiter or the end of the `noqa`. + c if c.is_whitespace() => false, + + // e.g. #noqa:F401F842 + // ^ + // Push a warning and update the `missing_delimiter` + // state but don't consume the character since it's + // part of the next code. + c if c.is_ascii_uppercase() => { + self.warnings + .push(LexicalWarning::MissingDelimiter(TextRange::empty( + self.offset + self.position(), + ))); + true + } + // Start of a new comment + // e.g. #noqa: F401#A comment + // ^ + '#' => false, + _ => return Err(LexicalError::InvalidCodeSuffix), + }; + } + Some(_) => { + // The first time we hit an evidently invalid code, + // it's probably a trailing comment. So stop lexing, + // but don't push an error. + // Ex) + // # noqa: F401 we import this for a reason + // ^----- stop lexing but no error + self.cursor.skip_bytes(self.cursor.as_str().len()); + } + None => {} + } + Ok(()) + } + + /// Push current token to stack of [`Code`] objects + fn push_code(&mut self, code: &'a str) { + self.codes.push(Code { + code, + range: self.token_range().add(self.offset), + }); + } + + /// Consume whitespace #[inline] - fn lex_ruff(line: &str) -> Option<&str> { - line.strip_prefix("ruff") + fn eat_whitespace(&mut self) { + self.cursor.eat_while(char::is_whitespace); } - /// Lex a `noqa` directive with case-insensitive matching. + /// Current token range relative to offset #[inline] - fn lex_noqa(line: &str) -> Option<&str> { - match line.as_bytes() { - [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] => Some(&line["noqa".len()..]), - _ => None, - } + fn token_range(&self) -> TextRange { + let end = self.position(); + let len = self.cursor.token_len(); + + TextRange::at(end - len, len) } - /// Lex a code delimiter, which can either be a comma or whitespace. + /// Retrieves the current position of the cursor within the line. #[inline] - fn lex_delimiter(line: &str) -> Option<&str> { - let mut chars = line.chars(); - if let Some(c) = chars.next() { - if c == ',' || c.is_whitespace() { - Some(chars.as_str()) - } else { - None + fn position(&self) -> TextSize { + self.line_length - self.cursor.text_len() + } +} + +/// Helper to check if "noqa" (case-insensitive) appears at the given position +#[inline] +fn is_noqa_uncased(text: &str) -> bool { + matches!( + text.as_bytes(), + [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] + ) +} + +/// Indicates recoverable error encountered while lexing with [`NoqaLexer`] +#[derive(Debug, Clone, Copy)] +enum LexicalWarning { + MissingItem(TextRange), + MissingDelimiter(TextRange), +} + +impl Display for LexicalWarning { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::MissingItem(_) => f.write_str("expected rule code between commas"), + Self::MissingDelimiter(_) => { + f.write_str("expected comma or space delimiter between codes") } - } else { - None } } +} - /// Lex an individual rule code (e.g., `F401`). - #[inline] - fn lex_code(line: &str) -> Option<&str> { - // Extract, e.g., the `F` in `F401`. - let prefix = line.chars().take_while(char::is_ascii_uppercase).count(); - // Extract, e.g., the `401` in `F401`. - let suffix = line[prefix..] - .chars() - .take_while(char::is_ascii_alphanumeric) - .count(); - if prefix > 0 && suffix > 0 { - Some(&line[..prefix + suffix]) - } else { - None +impl Ranged for LexicalWarning { + fn range(&self) -> TextRange { + match *self { + LexicalWarning::MissingItem(text_range) => text_range, + LexicalWarning::MissingDelimiter(text_range) => text_range, } } } -/// The result of an [`Importer::get_or_import_symbol`] call. +/// Fatal error occurring while lexing a `noqa` comment as in [`NoqaLexer`] #[derive(Debug)] -pub(crate) enum ParseError { +pub(crate) enum LexicalError { /// The `noqa` directive was missing valid codes (e.g., `# noqa: unused-import` instead of `# noqa: F401`). MissingCodes, /// The `noqa` directive used an invalid suffix (e.g., `# noqa; F401` instead of `# noqa: F401`). InvalidSuffix, + /// The `noqa` code matched the regex "[A-Z]+[0-9]+" with text remaining + /// (e.g. `# noqa: F401abc`) + InvalidCodeSuffix, } -impl Display for ParseError { +impl Display for LexicalError { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ParseError::MissingCodes => fmt.write_str("expected a comma-separated list of codes (e.g., `# noqa: F401, F841`)."), - ParseError::InvalidSuffix => { + LexicalError::MissingCodes => fmt.write_str("expected a comma-separated list of codes (e.g., `# noqa: F401, F841`)."), + LexicalError::InvalidSuffix => { fmt.write_str("expected `:` followed by a comma-separated list of codes (e.g., `# noqa: F401, F841`).") } + LexicalError::InvalidCodeSuffix => { + fmt.write_str("expected code to consist of uppercase letters followed by digits only (e.g. `F401`)") + } } } } -impl Error for ParseError {} +impl Error for LexicalError {} /// Adds noqa comments to suppress all diagnostics of a file. pub(crate) fn add_noqa( @@ -634,7 +738,7 @@ fn add_noqa_inner( let directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path); let exemption = FileExemption::from(&directives); - let directives = NoqaDirectives::from_commented_ranges(comment_ranges, 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); @@ -927,20 +1031,49 @@ pub(crate) struct NoqaDirectives<'a> { impl<'a> NoqaDirectives<'a> { pub(crate) fn from_commented_ranges( comment_ranges: &CommentRanges, + external: &[String], path: &Path, locator: &'a Locator<'a>, ) -> Self { let mut directives = Vec::new(); for range in comment_ranges { - match Directive::try_extract(locator.slice(range), range.start()) { - Err(err) => { - #[allow(deprecated)] - let line = locator.compute_line_index(range.start()); - let path_display = relativize_path(path); - warn!("Invalid `# noqa` directive on {path_display}:{line}: {err}"); - } - Ok(Some(directive)) => { + let lexed = lex_inline_noqa(range, locator.contents()); + + match lexed { + Ok(Some(NoqaLexerOutput { + warnings, + directive, + })) => { + if !warnings.is_empty() { + #[allow(deprecated)] + let line = locator.compute_line_index(range.start()); + let path_display = relativize_path(path); + for warning in warnings { + warn!("Missing or joined rule code(s) at {path_display}:{line}: {warning}"); + } + } + if let Directive::Codes(codes) = &directive { + // Warn on invalid rule codes. + for code in &codes.codes { + // Ignore externally-defined rules. + if !external + .iter() + .any(|external| code.as_str().starts_with(external)) + { + if Rule::from_code( + get_redirect_target(code.as_str()).unwrap_or(code.as_str()), + ) + .is_err() + { + #[allow(deprecated)] + let line = locator.compute_line_index(range.start()); + let path_display = relativize_path(path); + warn!("Invalid rule code provided to `# noqa` at {path_display}:{line}: {code}"); + } + } + } + } // noqa comments are guaranteed to be single line. let range = locator.line_range(range.start()); directives.push(NoqaDirectiveLine { @@ -950,6 +1083,12 @@ impl<'a> NoqaDirectives<'a> { includes_end: range.end() == locator.contents().text_len(), }); } + Err(err) => { + #[allow(deprecated)] + let line = locator.compute_line_index(range.start()); + let path_display = relativize_path(path); + warn!("Invalid `# noqa` directive on {path_display}:{line}: {err}"); + } Ok(None) => {} } } @@ -1063,6 +1202,7 @@ impl FromIterator for NoqaMapping { #[cfg(test)] mod tests { + use std::path::Path; use insta::assert_debug_snapshot; @@ -1072,239 +1212,1590 @@ mod tests { use ruff_source_file::LineEnding; use ruff_text_size::{TextLen, TextRange, TextSize}; - use crate::noqa::{add_noqa_inner, Directive, NoqaMapping, ParsedFileExemption}; + use crate::noqa::{ + add_noqa_inner, lex_codes, lex_file_exemption, lex_inline_noqa, Directive, LexicalError, + NoqaLexerOutput, NoqaMapping, + }; use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon}; use crate::rules::pyflakes::rules::UnusedVariable; use crate::rules::pyupgrade::rules::PrintfStringFormatting; use crate::{generate_noqa_edits, Locator}; + fn assert_lexed_ranges_match_slices( + directive: Result, LexicalError>, + source: &str, + ) { + if let Ok(Some(NoqaLexerOutput { + warnings: _, + directive: Directive::Codes(codes), + })) = directive + { + for code in codes.iter() { + assert_eq!(&source[code.range], code.code); + } + } + } + + #[test] + fn noqa_lex_codes() { + let source = " F401,,F402F403 # and so on"; + assert_debug_snapshot!(lex_codes(source), @r#" + Ok( + [ + Code { + code: "F401", + range: 1..5, + }, + Code { + code: "F402", + range: 7..11, + }, + Code { + code: "F403", + range: 11..15, + }, + ], + ) + "#); + } + #[test] fn noqa_all() { let source = "# noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..6, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn noqa_no_code() { + let source = "# noqa:"; + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn noqa_no_code_invalid_suffix() { + let source = "# noqa: foo"; + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn noqa_no_code_trailing_content() { + let source = "# noqa: # Foo"; + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn malformed_code_1() { + let source = "# noqa: F"; + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn malformed_code_2() { + let source = "# noqa: RUF001, F"; + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..14, + codes: [ + Code { + code: "RUF001", + range: 8..14, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn malformed_code_3() { + let source = "# noqa: RUF001F"; + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + InvalidCodeSuffix, + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_code() { let source = "# noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_codes() { let source = "# noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 14..18, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_all_case_insensitive() { let source = "# NOQA"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..6, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_code_case_insensitive() { let source = "# NOQA: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_codes_case_insensitive() { let source = "# NOQA: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 14..18, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_leading_space() { let source = "# # noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 4..16, + codes: [ + Code { + code: "F401", + range: 12..16, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_trailing_space() { let source = "# noqa: F401 #"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_all_no_space() { let source = "#noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..5, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_code_no_space() { let source = "#noqa:F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..10, + codes: [ + Code { + code: "F401", + range: 6..10, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_codes_no_space() { let source = "#noqa:F401,F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..15, + codes: [ + Code { + code: "F401", + range: 6..10, + }, + Code { + code: "F841", + range: 11..15, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_all_multi_space() { let source = "# noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..7, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_code_multi_space() { let source = "# noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..13, + codes: [ + Code { + code: "F401", + range: 9..13, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_codes_multi_space() { let source = "# noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..20, + codes: [ + Code { + code: "F401", + range: 9..13, + }, + Code { + code: "F841", + range: 16..20, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn noqa_code_leading_hashes() { + let source = "###noqa: F401"; + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 2..13, + codes: [ + Code { + code: "F401", + range: 9..13, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn noqa_code_leading_hashes_with_spaces() { + let source = "# # # noqa: F401"; + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 6..18, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_all_leading_comment() { let source = "# Some comment describing the noqa # noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 35..41, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_code_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 35..47, + codes: [ + Code { + code: "F401", + range: 43..47, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_codes_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 35..53, + codes: [ + Code { + code: "F401", + range: 43..47, + }, + Code { + code: "F841", + range: 49..53, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_all_trailing_comment() { let source = "# noqa # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..6, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_code_trailing_comment() { let source = "# noqa: F401 # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_codes_trailing_comment() { let source = "# noqa: F401, F841 # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 14..18, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_invalid_codes() { let source = "# noqa: unused-import, F401, some other code"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_squashed_codes() { let source = "# noqa: F401F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [ + MissingDelimiter( + 12..12, + ), + ], + directive: Codes( + Codes { + range: 0..16, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 12..16, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_empty_comma() { let source = "# noqa: F401,,F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [ + MissingItem( + 13..13, + ), + ], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 14..18, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_empty_comma_space() { let source = "# noqa: F401, ,F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [ + MissingItem( + 13..14, + ), + ], + directive: Codes( + Codes { + range: 0..19, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 15..19, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_non_code() { let source = "# noqa: F401 We're ignoring an import"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn noqa_code_invalid_code_suffix() { + let source = "# noqa: F401abc"; + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + InvalidCodeSuffix, + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn noqa_invalid_suffix() { let source = "# noqa[F401]"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + InvalidSuffix, + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn flake8_exemption_all() { let source = "# flake8: noqa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..14, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn flake8_noqa_no_code() { + let source = "# flake8: noqa:"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn flake8_noqa_no_code_invalid_suffix() { + let source = "# flake8: noqa: foo"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn flake8_noqa_no_code_trailing_content() { + let source = "# flake8: noqa: # Foo"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn flake8_malformed_code_1() { + let source = "# flake8: noqa: F"; + let directive = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn flake8_malformed_code_2() { + let source = "# flake8: noqa: RUF001, F"; + let directive = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..22, + codes: [ + Code { + code: "RUF001", + range: 16..22, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn flake8_malformed_code_3() { + let source = "# flake8: noqa: RUF001F"; + let directive = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + InvalidCodeSuffix, + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn ruff_exemption_all() { let source = "# ruff: noqa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_noqa_no_code() { + let source = "# ruff: noqa:"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_noqa_no_code_invalid_suffix() { + let source = "# ruff: noqa: foo"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_noqa_no_code_trailing_content() { + let source = "# ruff: noqa: # Foo"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_malformed_code_1() { + let source = "# ruff: noqa: F"; + let directive = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + MissingCodes, + ) + "); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn ruff_malformed_code_2() { + let source = "# ruff: noqa: RUF001, F"; + let directive = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..20, + codes: [ + Code { + code: "RUF001", + range: 14..20, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(directive, source); + } + + #[test] + fn ruff_malformed_code_3() { + let source = "# ruff: noqa: RUF001F"; + let directive = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive, @r" + Err( + InvalidCodeSuffix, + ) + "); + assert_lexed_ranges_match_slices(directive, source); } #[test] fn flake8_exemption_all_no_space() { let source = "#flake8:noqa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(exemption, source); } #[test] fn ruff_exemption_all_no_space() { let source = "#ruff:noqa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..10, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(exemption, source); } #[test] fn flake8_exemption_codes() { // Note: Flake8 doesn't support this; it's treated as a blanket exemption. let source = "# flake8: noqa: F401, F841"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..26, + codes: [ + Code { + code: "F401", + range: 16..20, + }, + Code { + code: "F841", + range: 22..26, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(exemption, source); } #[test] fn ruff_exemption_codes() { let source = "# ruff: noqa: F401, F841"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..24, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + Code { + code: "F841", + range: 20..24, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(exemption, source); + } + #[test] + fn ruff_exemption_codes_leading_hashes() { + let source = "#### ruff: noqa: F401, F841"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 3..27, + codes: [ + Code { + code: "F401", + range: 17..21, + }, + Code { + code: "F841", + range: 23..27, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_squashed_codes() { + let source = "# ruff: noqa: F401F841"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [ + MissingDelimiter( + 18..18, + ), + ], + directive: Codes( + Codes { + range: 0..22, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + Code { + code: "F841", + range: 18..22, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_empty_comma() { + let source = "# ruff: noqa: F401,,F841"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [ + MissingItem( + 19..19, + ), + ], + directive: Codes( + Codes { + range: 0..24, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + Code { + code: "F841", + range: 20..24, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_empty_comma_space() { + let source = "# ruff: noqa: F401, ,F841"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [ + MissingItem( + 19..20, + ), + ], + directive: Codes( + Codes { + range: 0..25, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + Code { + code: "F841", + range: 21..25, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_invalid_code_suffix() { + let source = "# ruff: noqa: F401abc"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Err( + InvalidCodeSuffix, + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_code_leading_comment() { + let source = "# Leading comment # ruff: noqa: F401"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 18..36, + codes: [ + Code { + code: "F401", + range: 32..36, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_code_trailing_comment() { + let source = "# ruff: noqa: F401 # Trailing comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_all_leading_comment() { + let source = "# Leading comment # ruff: noqa"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 18..30, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_all_trailing_comment() { + let source = "# ruff: noqa # Trailing comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_code_trailing_comment_no_space() { + let source = "# ruff: noqa: F401# And another comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_all_trailing_comment_no_space() { + let source = "# ruff: noqa# Trailing comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_all_trailing_comment_no_hash() { + let source = "# ruff: noqa Trailing comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_code_trailing_comment_no_hash() { + let source = "# ruff: noqa: F401 Trailing comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r#" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + }, + ), + ) + "#); + assert_lexed_ranges_match_slices(exemption, source); } #[test] fn flake8_exemption_all_case_insensitive() { let source = "# flake8: NoQa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..14, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(exemption, source); } #[test] fn ruff_exemption_all_case_insensitive() { let source = "# ruff: NoQa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption, @r" + Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, + ), + ) + "); + assert_lexed_ranges_match_slices(exemption, source); } #[test] diff --git a/crates/ruff_linter/src/rule_redirects.rs b/crates/ruff_linter/src/rule_redirects.rs index a209c4616a94c4..953074e10d2b58 100644 --- a/crates/ruff_linter/src/rule_redirects.rs +++ b/crates/ruff_linter/src/rule_redirects.rs @@ -134,6 +134,7 @@ static REDIRECTS: LazyLock> = LazyLock::new( ("TCH005", "TC005"), ("TCH006", "TC010"), ("TCH010", "TC010"), + ("RUF035", "S704"), ]) }); diff --git a/crates/ruff_linter/src/rules/flake8_bandit/mod.rs b/crates/ruff_linter/src/rules/flake8_bandit/mod.rs index 480f6250c21536..3eaab135b601bb 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/mod.rs @@ -87,6 +87,7 @@ mod tests { #[test_case(Rule::DjangoExtra, Path::new("S610.py"))] #[test_case(Rule::DjangoRawSql, Path::new("S611.py"))] #[test_case(Rule::TarfileUnsafeMembers, Path::new("S202.py"))] + #[test_case(Rule::UnsafeMarkupUse, Path::new("S704.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( @@ -120,6 +121,49 @@ mod tests { Ok(()) } + #[test_case(Rule::UnsafeMarkupUse, Path::new("S704_extend_markup_names.py"))] + #[test_case(Rule::UnsafeMarkupUse, Path::new("S704_skip_early_out.py"))] + fn extend_allowed_callable(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "extend_allow_callables__{}_{}", + rule_code.noqa_code(), + path.to_string_lossy() + ); + let diagnostics = test_path( + Path::new("flake8_bandit").join(path).as_path(), + &LinterSettings { + flake8_bandit: super::settings::Settings { + extend_markup_names: vec!["webhelpers.html.literal".to_string()], + ..Default::default() + }, + ..LinterSettings::for_rule(rule_code) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + + #[test_case(Rule::UnsafeMarkupUse, Path::new("S704_whitelisted_markup_calls.py"))] + fn whitelisted_markup_calls(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "whitelisted_markup_calls__{}_{}", + rule_code.noqa_code(), + path.to_string_lossy() + ); + let diagnostics = test_path( + Path::new("flake8_bandit").join(path).as_path(), + &LinterSettings { + flake8_bandit: super::settings::Settings { + allowed_markup_calls: vec!["bleach.clean".to_string()], + ..Default::default() + }, + ..LinterSettings::for_rule(rule_code) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + #[test] fn check_hardcoded_tmp_additional_dirs() -> Result<()> { let diagnostics = test_path( @@ -132,7 +176,7 @@ mod tests { "/dev/shm".to_string(), "/foo".to_string(), ], - check_typed_exception: false, + ..Default::default() }, ..LinterSettings::for_rule(Rule::HardcodedTempFile) }, diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/mod.rs index 0c9a5953e0b5fd..163913b3526adb 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/mod.rs @@ -29,6 +29,7 @@ pub(crate) use suspicious_imports::*; pub(crate) use tarfile_unsafe_members::*; pub(crate) use try_except_continue::*; pub(crate) use try_except_pass::*; +pub(crate) use unsafe_markup_use::*; pub(crate) use unsafe_yaml_load::*; pub(crate) use weak_cryptographic_key::*; @@ -63,5 +64,6 @@ mod suspicious_imports; mod tarfile_unsafe_members; mod try_except_continue; mod try_except_pass; +mod unsafe_markup_use; mod unsafe_yaml_load; mod weak_cryptographic_key; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs index bc4e50d1e585cc..08b20a1fdf2c04 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs @@ -779,6 +779,13 @@ impl Violation for SuspiciousXMLPullDOMUsage { } } +/// ## Deprecation +/// +/// This rule was deprecated as the `lxml` library has been modified to address +/// known vulnerabilities and unsafe defaults. As such, the `defusedxml` +/// library is no longer necessary, `defusedxml` has [deprecated] its `lxml` +/// module. +/// /// ## What it does /// Checks for uses of insecure XML parsers. /// @@ -802,6 +809,7 @@ impl Violation for SuspiciousXMLPullDOMUsage { /// - [Common Weakness Enumeration: CWE-776](https://cwe.mitre.org/data/definitions/776.html) /// /// [preview]: https://docs.astral.sh/ruff/preview/ +/// [deprecated]: https://pypi.org/project/defusedxml/0.8.0rc2/#defusedxml-lxml #[derive(ViolationMetadata)] pub(crate) struct SuspiciousXMLETreeUsage; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs new file mode 100644 index 00000000000000..2d0f383eb7acf1 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs @@ -0,0 +1,160 @@ +use ruff_python_ast::{Expr, ExprCall}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_python_ast::name::QualifiedName; +use ruff_python_semantic::{Modules, SemanticModel}; +use ruff_text_size::Ranged; + +use crate::{checkers::ast::Checker, settings::LinterSettings}; + +/// ## What it does +/// Checks for non-literal strings being passed to [`markupsafe.Markup`][markupsafe-markup]. +/// +/// ## Why is this bad? +/// [`markupsafe.Markup`] does not perform any escaping, so passing dynamic +/// content, like f-strings, variables or interpolated strings will potentially +/// lead to XSS vulnerabilities. +/// +/// Instead you should interpolate the `Markup` object. +/// +/// Using [`lint.flake8-bandit.extend-markup-names`] additional objects can be +/// treated like `Markup`. +/// +/// This rule was originally inspired by [flake8-markupsafe] but doesn't carve +/// out any exceptions for i18n related calls by default. +/// +/// You can use [`lint.flake8-bandit.allowed-markup-calls`] to specify exceptions. +/// +/// ## Example +/// Given: +/// ```python +/// from markupsafe import Markup +/// +/// content = "" +/// html = Markup(f"{content}") # XSS +/// ``` +/// +/// Use instead: +/// ```python +/// from markupsafe import Markup +/// +/// content = "" +/// html = Markup("{}").format(content) # Safe +/// ``` +/// +/// Given: +/// ```python +/// from markupsafe import Markup +/// +/// lines = [ +/// Markup("heading"), +/// "", +/// ] +/// html = Markup("
".join(lines)) # XSS +/// ``` +/// +/// Use instead: +/// ```python +/// from markupsafe import Markup +/// +/// lines = [ +/// Markup("heading"), +/// "", +/// ] +/// html = Markup("
").join(lines) # Safe +/// ``` +/// ## Options +/// - `lint.flake8-bandit.extend-markup-names` +/// - `lint.flake8-bandit.allowed-markup-calls` +/// +/// ## References +/// - [MarkupSafe on PyPI](https://pypi.org/project/MarkupSafe/) +/// - [`markupsafe.Markup` API documentation](https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup) +/// +/// [markupsafe-markup]: https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup +/// [flake8-markupsafe]: https://github.com/vmagamedov/flake8-markupsafe +#[derive(ViolationMetadata)] +pub(crate) struct UnsafeMarkupUse { + name: String, +} + +impl Violation for UnsafeMarkupUse { + #[derive_message_formats] + fn message(&self) -> String { + let UnsafeMarkupUse { name } = self; + format!("Unsafe use of `{name}` detected") + } +} + +/// S704 +pub(crate) fn unsafe_markup_call(checker: &Checker, call: &ExprCall) { + if checker + .settings + .flake8_bandit + .extend_markup_names + .is_empty() + && !(checker.semantic().seen_module(Modules::MARKUPSAFE) + || checker.semantic().seen_module(Modules::FLASK)) + { + return; + } + + if !is_unsafe_call(call, checker.semantic(), checker.settings) { + return; + } + + let Some(qualified_name) = checker.semantic().resolve_qualified_name(&call.func) else { + return; + }; + + if !is_markup_call(&qualified_name, checker.settings) { + return; + } + + checker.report_diagnostic(Diagnostic::new( + UnsafeMarkupUse { + name: qualified_name.to_string(), + }, + call.range(), + )); +} + +fn is_markup_call(qualified_name: &QualifiedName, settings: &LinterSettings) -> bool { + matches!( + qualified_name.segments(), + ["markupsafe" | "flask", "Markup"] + ) || settings + .flake8_bandit + .extend_markup_names + .iter() + .map(|target| QualifiedName::from_dotted_name(target)) + .any(|target| *qualified_name == target) +} + +fn is_unsafe_call(call: &ExprCall, semantic: &SemanticModel, settings: &LinterSettings) -> bool { + // technically this could be circumvented by using a keyword argument + // but without type-inference we can't really know which keyword argument + // corresponds to the first positional argument and either way it is + // unlikely that someone will actually use a keyword argument here + // TODO: Eventually we may want to allow dynamic values, as long as they + // have a __html__ attribute, since that is part of the API + matches!(&*call.arguments.args, [first] if !first.is_string_literal_expr() && !first.is_bytes_literal_expr() && !is_whitelisted_call(first, semantic, settings)) +} + +fn is_whitelisted_call(expr: &Expr, semantic: &SemanticModel, settings: &LinterSettings) -> bool { + let Expr::Call(ExprCall { func, .. }) = expr else { + return false; + }; + + let Some(qualified_name) = semantic.resolve_qualified_name(func) else { + return false; + }; + + settings + .flake8_bandit + .allowed_markup_calls + .iter() + .map(|target| QualifiedName::from_dotted_name(target)) + .any(|target| qualified_name == target) +} diff --git a/crates/ruff_linter/src/rules/flake8_bandit/settings.rs b/crates/ruff_linter/src/rules/flake8_bandit/settings.rs index ee96e6ee667d2b..b8e447c1725827 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/settings.rs @@ -14,6 +14,8 @@ pub fn default_tmp_dirs() -> Vec { pub struct Settings { pub hardcoded_tmp_directory: Vec, pub check_typed_exception: bool, + pub extend_markup_names: Vec, + pub allowed_markup_calls: Vec, } impl Default for Settings { @@ -21,6 +23,8 @@ impl Default for Settings { Self { hardcoded_tmp_directory: default_tmp_dirs(), check_typed_exception: false, + extend_markup_names: vec![], + allowed_markup_calls: vec![], } } } @@ -32,7 +36,9 @@ impl Display for Settings { namespace = "linter.flake8_bandit", fields = [ self.hardcoded_tmp_directory | array, - self.check_typed_exception + self.check_typed_exception, + self.extend_markup_names | array, + self.allowed_markup_calls | array, ] } Ok(()) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S704_S704.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S704_S704.py.snap new file mode 100644 index 00000000000000..8ee7eb09b6c35a --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S704_S704.py.snap @@ -0,0 +1,58 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs +--- +S704.py:5:1: S704 Unsafe use of `markupsafe.Markup` detected + | +4 | content = "" +5 | Markup(f"unsafe {content}") # S704 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ S704 +6 | flask.Markup("unsafe {}".format(content)) # S704 +7 | Markup("safe {}").format(content) + | + +S704.py:6:1: S704 Unsafe use of `flask.Markup` detected + | +4 | content = "" +5 | Markup(f"unsafe {content}") # S704 +6 | flask.Markup("unsafe {}".format(content)) # S704 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S704 +7 | Markup("safe {}").format(content) +8 | flask.Markup(b"safe {}", encoding='utf-8').format(content) + | + +S704.py:10:1: S704 Unsafe use of `markupsafe.Markup` detected + | + 8 | flask.Markup(b"safe {}", encoding='utf-8').format(content) + 9 | escape(content) +10 | Markup(content) # S704 + | ^^^^^^^^^^^^^^^ S704 +11 | flask.Markup("unsafe %s" % content) # S704 +12 | Markup(object="safe") + | + +S704.py:11:1: S704 Unsafe use of `flask.Markup` detected + | + 9 | escape(content) +10 | Markup(content) # S704 +11 | flask.Markup("unsafe %s" % content) # S704 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S704 +12 | Markup(object="safe") +13 | Markup(object="unsafe {}".format(content)) # Not currently detected + | + +S704.py:17:1: S704 Unsafe use of `markupsafe.Markup` detected + | +15 | # NOTE: We may be able to get rid of these false positives with red-knot +16 | # if it includes comprehensive constant expression detection/evaluation. +17 | Markup("*" * 8) # S704 (false positive) + | ^^^^^^^^^^^^^^^ S704 +18 | flask.Markup("hello {}".format("world")) # S704 (false positive) + | + +S704.py:18:1: S704 Unsafe use of `flask.Markup` detected + | +16 | # if it includes comprehensive constant expression detection/evaluation. +17 | Markup("*" * 8) # S704 (false positive) +18 | flask.Markup("hello {}".format("world")) # S704 (false positive) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S704 + | diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__extend_allow_callables__S704_S704_extend_markup_names.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__extend_allow_callables__S704_S704_extend_markup_names.py.snap new file mode 100644 index 00000000000000..924e5f96232bc1 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__extend_allow_callables__S704_S704_extend_markup_names.py.snap @@ -0,0 +1,18 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs +--- +S704_extend_markup_names.py:5:1: S704 Unsafe use of `markupsafe.Markup` detected + | +4 | content = "" +5 | Markup(f"unsafe {content}") # S704 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ S704 +6 | literal(f"unsafe {content}") # S704 + | + +S704_extend_markup_names.py:6:1: S704 Unsafe use of `webhelpers.html.literal` detected + | +4 | content = "" +5 | Markup(f"unsafe {content}") # S704 +6 | literal(f"unsafe {content}") # S704 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S704 + | diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__extend_allow_callables__S704_S704_skip_early_out.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__extend_allow_callables__S704_S704_skip_early_out.py.snap new file mode 100644 index 00000000000000..8c04fdf49fb6d1 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__extend_allow_callables__S704_S704_skip_early_out.py.snap @@ -0,0 +1,10 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs +--- +S704_skip_early_out.py:7:1: S704 Unsafe use of `webhelpers.html.literal` detected + | +5 | # markupsafe or flask first. +6 | content = "" +7 | literal(f"unsafe {content}") # S704 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S704 + | diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__whitelisted_markup_calls__S704_S704_whitelisted_markup_calls.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__whitelisted_markup_calls__S704_S704_whitelisted_markup_calls.py.snap new file mode 100644 index 00000000000000..e326335411e9e3 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__whitelisted_markup_calls__S704_S704_whitelisted_markup_calls.py.snap @@ -0,0 +1,10 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs +--- +S704_whitelisted_markup_calls.py:9:1: S704 Unsafe use of `markupsafe.Markup` detected + | +7 | # indirect assignments are currently not supported +8 | cleaned = clean(content) +9 | Markup(cleaned) # S704 + | ^^^^^^^^^^^^^^^ S704 + | diff --git a/crates/ruff_linter/src/rules/flake8_builtins/mod.rs b/crates/ruff_linter/src/rules/flake8_builtins/mod.rs index aedec5cb231f9f..8d0583d87578a6 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/mod.rs @@ -53,7 +53,7 @@ mod tests { Path::new("flake8_builtins").join(path).as_path(), &LinterSettings { flake8_builtins: flake8_builtins::settings::Settings { - builtins_strict_checking: true, + strict_checking: true, ..Default::default() }, ..LinterSettings::for_rule(rule_code) @@ -83,7 +83,7 @@ mod tests { Path::new("flake8_builtins").join(path).as_path(), &LinterSettings { flake8_builtins: flake8_builtins::settings::Settings { - builtins_strict_checking: strict, + strict_checking: strict, ..Default::default() }, ..LinterSettings::for_rule(rule_code) @@ -106,7 +106,7 @@ mod tests { &LinterSettings { src: vec![test_resource_path(src.join(path.parent().unwrap()))], flake8_builtins: flake8_builtins::settings::Settings { - builtins_strict_checking: false, + strict_checking: false, ..Default::default() }, ..LinterSettings::for_rule(rule_code) @@ -130,7 +130,7 @@ mod tests { &LinterSettings { project_root: test_resource_path(src.join(path.parent().unwrap())), flake8_builtins: flake8_builtins::settings::Settings { - builtins_strict_checking: false, + strict_checking: false, ..Default::default() }, ..LinterSettings::for_rule(rule_code) @@ -156,7 +156,7 @@ mod tests { Path::new("flake8_builtins").join(path).as_path(), &LinterSettings { flake8_builtins: super::settings::Settings { - builtins_ignorelist: vec!["id".to_string(), "dir".to_string()], + ignorelist: vec!["id".to_string(), "dir".to_string()], ..Default::default() }, ..LinterSettings::for_rules(vec![rule_code]) @@ -199,8 +199,8 @@ mod tests { Path::new("flake8_builtins").join(path).as_path(), &LinterSettings { flake8_builtins: super::settings::Settings { - builtins_allowed_modules: vec!["xml".to_string(), "logging".to_string()], - builtins_strict_checking: true, + allowed_modules: vec!["xml".to_string(), "logging".to_string()], + strict_checking: true, ..Default::default() }, ..LinterSettings::for_rules(vec![rule_code]) diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs index b0d5014de962e4..7340489e72fca6 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs @@ -19,7 +19,7 @@ use super::super::helpers::shadows_builtin; /// builtin and vice versa. /// /// Builtins can be marked as exceptions to this rule via the -/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option. +/// [`lint.flake8-builtins.ignorelist`] configuration option. /// /// ## Example /// ```python @@ -44,7 +44,7 @@ use super::super::helpers::shadows_builtin; /// ``` /// /// ## Options -/// - `lint.flake8-builtins.builtins-ignorelist` +/// - `lint.flake8-builtins.ignorelist` /// /// ## References /// - [_Is it bad practice to use a built-in function name as an attribute or method identifier?_](https://stackoverflow.com/questions/9109333/is-it-bad-practice-to-use-a-built-in-function-name-as-an-attribute-or-method-ide) @@ -67,7 +67,7 @@ pub(crate) fn builtin_argument_shadowing(checker: &Checker, parameter: &Paramete if shadows_builtin( parameter.name(), checker.source_type, - &checker.settings.flake8_builtins.builtins_ignorelist, + &checker.settings.flake8_builtins.ignorelist, checker.target_version(), ) { // Ignore parameters in lambda expressions. diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs index 79f0f0ded34ee3..f337bd214f6f47 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs @@ -37,7 +37,7 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin; /// ``` /// /// Builtins can be marked as exceptions to this rule via the -/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option, or +/// [`lint.flake8-builtins.ignorelist`] configuration option, or /// converted to the appropriate dunder method. Methods decorated with /// `@typing.override` or `@typing_extensions.override` are also /// ignored. @@ -55,7 +55,7 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin; /// ``` /// /// ## Options -/// - `lint.flake8-builtins.builtins-ignorelist` +/// - `lint.flake8-builtins.ignorelist` #[derive(ViolationMetadata)] pub(crate) struct BuiltinAttributeShadowing { kind: Kind, @@ -98,7 +98,7 @@ pub(crate) fn builtin_attribute_shadowing( if shadows_builtin( name, checker.source_type, - &checker.settings.flake8_builtins.builtins_ignorelist, + &checker.settings.flake8_builtins.ignorelist, checker.target_version(), ) { // Ignore explicit overrides. diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs index 83332397b47122..508ce4f0e6ac5f 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs @@ -14,7 +14,7 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin; /// as readers may mistake the variable for the builtin and vice versa. /// /// Builtins can be marked as exceptions to this rule via the -/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option. +/// [`lint.flake8-builtins.ignorelist`] configuration option. /// /// ## Example /// ```python @@ -38,7 +38,7 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin; /// ``` /// /// ## Options -/// - `lint.flake8-builtins.builtins-ignorelist` +/// - `lint.flake8-builtins.ignorelist` /// - `target-version` /// #[derive(ViolationMetadata)] @@ -60,7 +60,7 @@ pub(crate) fn builtin_import_shadowing(checker: &Checker, alias: &Alias) { if shadows_builtin( name.as_str(), checker.source_type, - &checker.settings.flake8_builtins.builtins_ignorelist, + &checker.settings.flake8_builtins.ignorelist, checker.target_version(), ) { checker.report_diagnostic(Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs index 1e778fa0cea06a..45a4f003f5af6f 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs @@ -16,10 +16,10 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin; /// builtin, and vice versa. /// /// Builtins can be marked as exceptions to this rule via the -/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option. +/// [`lint.flake8-builtins.ignorelist`] configuration option. /// /// ## Options -/// - `lint.flake8-builtins.builtins-ignorelist` +/// - `lint.flake8-builtins.ignorelist` #[derive(ViolationMetadata)] pub(crate) struct BuiltinLambdaArgumentShadowing { name: String, @@ -43,7 +43,7 @@ pub(crate) fn builtin_lambda_argument_shadowing(checker: &Checker, lambda: &Expr if shadows_builtin( name, checker.source_type, - &checker.settings.flake8_builtins.builtins_ignorelist, + &checker.settings.flake8_builtins.ignorelist, checker.target_version(), ) { checker.report_diagnostic(Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs index 4f73cb1da03a45..a10a0cd3ac53dc 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs @@ -17,7 +17,7 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin; /// builtin and vice versa. /// /// Builtins can be marked as exceptions to this rule via the -/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option. +/// [`lint.flake8-builtins.ignorelist`] configuration option. /// /// ## Example /// ```python @@ -40,7 +40,7 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin; /// ``` /// /// ## Options -/// - `lint.flake8-builtins.builtins-ignorelist` +/// - `lint.flake8-builtins.ignorelist` /// /// ## References /// - [_Why is it a bad idea to name a variable `id` in Python?_](https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python) @@ -71,7 +71,7 @@ pub(crate) fn builtin_variable_shadowing(checker: &Checker, name: &str, range: T if shadows_builtin( name, checker.source_type, - &checker.settings.flake8_builtins.builtins_ignorelist, + &checker.settings.flake8_builtins.ignorelist, checker.target_version(), ) { checker.report_diagnostic(Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs index 4b6942a8509cf7..42cf7c5c070745 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs @@ -21,15 +21,14 @@ use crate::settings::LinterSettings; /// standard-library module and vice versa. /// /// Standard-library modules can be marked as exceptions to this rule via the -/// [`lint.flake8-builtins.builtins-allowed-modules`] configuration option. +/// [`lint.flake8-builtins.allowed-modules`] configuration option. /// -/// By default, only the last component of the module name is considered, so `logging.py`, -/// `utils/logging.py`, and `utils/logging/__init__.py` would all clash with the builtin `logging` -/// module. With the [`lint.flake8-builtins.builtins-strict-checking`] option set to `false`, the -/// module path is considered, so only a top-level `logging.py` or `logging/__init__.py` will -/// trigger the rule and `utils/logging.py`, for example, would not. In preview mode, the default -/// value of [`lint.flake8-builtins.builtins-strict-checking`] is `false` rather than `true` in -/// stable mode. +/// By default, the module path relative to the project root or [`src`] directories is considered, +/// so a top-level `logging.py` or `logging/__init__.py` will clash with the builtin `logging` +/// module, but `utils/logging.py`, for example, will not. With the +/// [`lint.flake8-builtins.strict-checking`] option set to `true`, only the last component +/// of the module name is considered, so `logging.py`, `utils/logging.py`, and +/// `utils/logging/__init__.py` will all trigger the rule. /// /// This rule is not applied to stub files, as the name of a stub module is out /// of the control of the author of the stub file. Instead, a stub should aim to @@ -50,8 +49,8 @@ use crate::settings::LinterSettings; /// ``` /// /// ## Options -/// - `lint.flake8-builtins.builtins-allowed-modules` -/// - `lint.flake8-builtins.builtins-strict-checking` +/// - `lint.flake8-builtins.allowed-modules` +/// - `lint.flake8-builtins.strict-checking` #[derive(ViolationMetadata)] pub(crate) struct StdlibModuleShadowing { name: String, @@ -104,7 +103,7 @@ pub(crate) fn stdlib_module_shadowing( } // not allowed generally, but check for a parent in non-strict mode - if !settings.flake8_builtins.builtins_strict_checking && components.next().is_some() { + if !settings.flake8_builtins.strict_checking && components.next().is_some() { return None; } @@ -136,7 +135,7 @@ fn is_allowed_module(settings: &LinterSettings, version: PythonVersion, module: if settings .flake8_builtins - .builtins_allowed_modules + .allowed_modules .iter() .any(|allowed_module| allowed_module == module) { diff --git a/crates/ruff_linter/src/rules/flake8_builtins/settings.rs b/crates/ruff_linter/src/rules/flake8_builtins/settings.rs index d22c6717695233..7be3222326cb81 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/settings.rs @@ -1,24 +1,14 @@ //! Settings for the `flake8-builtins` plugin. -use crate::{display_settings, settings::types::PreviewMode}; +use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; #[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { - pub builtins_ignorelist: Vec, - pub builtins_allowed_modules: Vec, - pub builtins_strict_checking: bool, -} - -impl Settings { - pub fn new(preview: PreviewMode) -> Self { - Self { - builtins_ignorelist: Vec::new(), - builtins_allowed_modules: Vec::new(), - builtins_strict_checking: preview.is_disabled(), - } - } + pub ignorelist: Vec, + pub allowed_modules: Vec, + pub strict_checking: bool, } impl Display for Settings { @@ -27,9 +17,9 @@ impl Display for Settings { formatter = f, namespace = "linter.flake8_builtins", fields = [ - self.builtins_allowed_modules | array, - self.builtins_ignorelist | array, - self.builtins_strict_checking, + self.allowed_modules | array, + self.ignorelist | array, + self.strict_checking, ] } Ok(()) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/mod.rs b/crates/ruff_linter/src/rules/flake8_datetimez/mod.rs index 5489e4a31b3fa2..ef37ee0d732924 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/mod.rs @@ -9,7 +9,6 @@ mod tests { use test_case::test_case; use crate::registry::Rule; - use crate::settings::types::PreviewMode; use crate::test::test_path; use crate::{assert_messages, settings}; @@ -22,6 +21,7 @@ mod tests { #[test_case(Rule::CallDatetimeStrptimeWithoutZone, Path::new("DTZ007.py"))] #[test_case(Rule::CallDateToday, Path::new("DTZ011.py"))] #[test_case(Rule::CallDateFromtimestamp, Path::new("DTZ012.py"))] + #[test_case(Rule::DatetimeMinMax, Path::new("DTZ901.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( @@ -31,18 +31,4 @@ mod tests { assert_messages!(snapshot, diagnostics); Ok(()) } - - #[test_case(Rule::DatetimeMinMax, Path::new("DTZ901.py"))] - fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); - let diagnostics = test_path( - Path::new("flake8_datetimez").join(path).as_path(), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs index 0038a3ed080bee..ab4e22830e6f17 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs @@ -9,18 +9,18 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; /// ## What it does -/// Checks for uses of `datetime.datetime.max` and `datetime.datetime.min`. +/// Checks for uses of `datetime.datetime.min` and `datetime.datetime.max`. /// /// ## Why is this bad? -/// `datetime.max` and `datetime.min` are non-timezone-aware datetime objects. +/// `datetime.min` and `datetime.max` are non-timezone-aware datetime objects. /// -/// As such, operations on `datetime.max` and `datetime.min` may behave +/// As such, operations on `datetime.min` and `datetime.max` may behave /// unexpectedly, as in: /// /// ```python /// # Timezone: UTC-14 -/// datetime.max.timestamp() # ValueError: year 10000 is out of range /// datetime.min.timestamp() # ValueError: year 0 is out of range +/// datetime.max.timestamp() # ValueError: year 10000 is out of range /// ``` /// /// ## Example @@ -53,7 +53,7 @@ impl Violation for DatetimeMinMax { } /// DTZ901 -pub(crate) fn datetime_max_min(checker: &Checker, expr: &Expr) { +pub(crate) fn datetime_min_max(checker: &Checker, expr: &Expr) { let semantic = checker.semantic(); if !semantic.seen_module(Modules::DATETIME) { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index c6edcd739dac90..eef87b404aefcc 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -156,30 +156,6 @@ mod tests { Ok(()) } - #[test_case(Rule::CustomTypeVarForSelf, Path::new("PYI019_0.py"))] - #[test_case(Rule::CustomTypeVarForSelf, Path::new("PYI019_0.pyi"))] - #[test_case(Rule::CustomTypeVarForSelf, Path::new("PYI019_1.pyi"))] - fn custom_classmethod_rules_preview(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "preview_{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("flake8_pyi").join(path).as_path(), - &settings::LinterSettings { - pep8_naming: pep8_naming::settings::Settings { - classmethod_decorators: vec!["foo_classmethod".to_string()], - ..pep8_naming::settings::Settings::default() - }, - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } - #[test_case(Rule::TypeAliasWithoutAnnotation, Path::new("PYI026.py"))] #[test_case(Rule::TypeAliasWithoutAnnotation, Path::new("PYI026.pyi"))] #[test_case(Rule::RedundantNoneLiteral, Path::new("PYI061.py"))] @@ -198,8 +174,6 @@ mod tests { } #[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.pyi"))] - #[test_case(Rule::UnusedPrivateTypeVar, Path::new("PYI018.py"))] - #[test_case(Rule::UnusedPrivateTypeVar, Path::new("PYI018.pyi"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs index 8d89b29a82c207..84546f130af742 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs @@ -71,20 +71,8 @@ use ruff_python_ast::PythonVersion; /// The fix is only marked as unsafe if there is the possibility that it might delete a comment /// from your code. /// -/// ## Preview-mode behaviour -/// This rule's behaviour has several differences when [`preview`] mode is enabled: -/// 1. The fix for this rule is currently only available if `preview` mode is enabled. -/// 2. By default, this rule is only applied to methods that have return-type annotations, -/// and the range of the diagnostic is the range of the return-type annotation. -/// In preview mode, this rule is also applied to some methods that do not have -/// return-type annotations. The range of the diagnostic is the range of the function -/// header (from the end of the function name to the end of the parameters). -/// 3. In `preview` mode, the rule uses different logic to determine whether an annotation -/// refers to a type variable. The `preview`-mode logic is more accurate, but may lead -/// to more methods being flagged than if `preview` mode is disabled. -/// /// [PEP 673]: https://peps.python.org/pep-0673/#motivation -/// [PEP 695]: https://peps.python.org/pep-0695/ +/// [PEP-695]: https://peps.python.org/pep-0695/ /// [PYI018]: https://docs.astral.sh/ruff/rules/unused-private-type-var/ /// [type parameter list]: https://docs.python.org/3/reference/compound_stmts.html#type-params /// [Self]: https://docs.python.org/3/library/typing.html#typing.Self @@ -162,73 +150,33 @@ pub(crate) fn custom_type_var_instead_of_self( &checker.settings.pep8_naming.staticmethod_decorators, ); - let function_header_end = returns - .as_deref() - .map(Ranged::end) - .unwrap_or_else(|| parameters.end()); - - // In stable mode, we only emit the diagnostic on methods that have a return type annotation. - // In preview mode, we have a more principled approach to determine if an annotation refers - // to a type variable, and we emit the diagnostic on some methods that do not have return - // annotations. - let (method, diagnostic_range) = match function_kind { - FunctionType::ClassMethod | FunctionType::NewMethod => { - if checker.settings.preview.is_enabled() { - ( - Method::PreviewClass(PreviewClassMethod { - cls_annotation: self_or_cls_annotation, - type_params, - }), - TextRange::new(function_name.end(), function_header_end), - ) - } else { - returns.as_deref().map(|returns| { - ( - Method::Class(ClassMethod { - cls_annotation: self_or_cls_annotation, - returns, - type_params, - }), - returns.range(), - ) - })? - } - } - FunctionType::Method => { - if checker.settings.preview.is_enabled() { - ( - Method::PreviewInstance(PreviewInstanceMethod { - self_annotation: self_or_cls_annotation, - type_params, - }), - TextRange::new(function_name.end(), function_header_end), - ) - } else { - returns.as_deref().map(|returns| { - ( - Method::Instance(InstanceMethod { - self_annotation: self_or_cls_annotation, - returns, - type_params, - }), - returns.range(), - ) - })? - } - } + let method = match function_kind { + FunctionType::ClassMethod | FunctionType::NewMethod => Method::Class(ClassMethod { + cls_annotation: self_or_cls_annotation, + type_params, + }), + FunctionType::Method => Method::Instance(InstanceMethod { + self_annotation: self_or_cls_annotation, + type_params, + }), FunctionType::Function | FunctionType::StaticMethod => return None, }; let custom_typevar = method.custom_typevar(semantic, binding.scope)?; + let function_header_end = returns + .as_deref() + .map(Ranged::end) + .unwrap_or_else(|| parameters.end()); + let mut diagnostic = Diagnostic::new( CustomTypeVarForSelf { typevar_name: custom_typevar.name(checker.source()).to_string(), }, - diagnostic_range, + TextRange::new(function_name.end(), function_header_end), ); - diagnostic.try_set_optional_fix(|| { + diagnostic.try_set_fix(|| { replace_custom_typevar_with_self( checker, function_def, @@ -244,9 +192,7 @@ pub(crate) fn custom_type_var_instead_of_self( #[derive(Debug)] enum Method<'a> { Class(ClassMethod<'a>), - PreviewClass(PreviewClassMethod<'a>), Instance(InstanceMethod<'a>), - PreviewInstance(PreviewInstanceMethod<'a>), } impl Method<'_> { @@ -257,9 +203,7 @@ impl Method<'_> { ) -> Option> { match self { Self::Class(class_method) => class_method.custom_typevar(semantic, scope), - Self::PreviewClass(class_method) => class_method.custom_typevar(semantic, scope), Self::Instance(instance_method) => instance_method.custom_typevar(semantic), - Self::PreviewInstance(instance_method) => instance_method.custom_typevar(semantic), } } } @@ -267,76 +211,10 @@ impl Method<'_> { #[derive(Debug)] struct ClassMethod<'a> { cls_annotation: &'a ast::Expr, - returns: &'a ast::Expr, type_params: Option<&'a ast::TypeParams>, } impl ClassMethod<'_> { - /// Returns `Some(typevar)` if the class method is annotated with - /// a custom `TypeVar` that is likely private. - fn custom_typevar<'a>( - &'a self, - semantic: &'a SemanticModel<'a>, - scope: ScopeId, - ) -> Option> { - let ast::ExprSubscript { - value: cls_annotation_value, - slice: cls_annotation_typevar, - .. - } = self.cls_annotation.as_subscript_expr()?; - - let cls_annotation_typevar = cls_annotation_typevar.as_name_expr()?; - let cls_annotation_typevar_name = &cls_annotation_typevar.id; - let ast::ExprName { id, .. } = cls_annotation_value.as_name_expr()?; - - if id != "type" { - return None; - } - - if !semantic.has_builtin_binding_in_scope("type", scope) { - return None; - } - - let return_annotation_typevar = match self.returns { - ast::Expr::Name(ast::ExprName { id, .. }) => id, - ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => { - let return_annotation_typevar = slice.as_name_expr()?; - let ast::ExprName { id, .. } = value.as_name_expr()?; - if id != "type" { - return None; - } - &return_annotation_typevar.id - } - _ => return None, - }; - - if cls_annotation_typevar_name != return_annotation_typevar { - return None; - } - - if !is_likely_private_typevar(cls_annotation_typevar_name, self.type_params) { - return None; - } - - semantic - .resolve_name(cls_annotation_typevar) - .map(|binding_id| TypeVar(semantic.binding(binding_id))) - } -} - -/// Struct for implementing this rule as applied to classmethods in preview mode. -/// -/// In stable mode, we only emit this diagnostic on methods that have return annotations, -/// so the stable-mode version of this struct has a `returns: &ast::Expr` field. In preview -/// mode, we also emit this diagnostic on methods that do not have return annotations, so -/// the preview-mode version of this struct does not have a `returns` field. -#[derive(Debug)] -struct PreviewClassMethod<'a> { - cls_annotation: &'a ast::Expr, - type_params: Option<&'a ast::TypeParams>, -} - -impl PreviewClassMethod<'_> { /// Returns `Some(typevar)` if the class method is annotated with /// a custom `TypeVar` for the `cls` parameter fn custom_typevar<'a>( @@ -360,59 +238,21 @@ impl PreviewClassMethod<'_> { return None; } - custom_typevar_preview(cls_annotation_typevar, self.type_params, semantic) + custom_typevar(cls_annotation_typevar, self.type_params, semantic) } } #[derive(Debug)] struct InstanceMethod<'a> { self_annotation: &'a ast::Expr, - returns: &'a ast::Expr, type_params: Option<&'a ast::TypeParams>, } impl InstanceMethod<'_> { - /// Returns `Some(typevar)` if the instance method is annotated with - /// a custom `TypeVar` that is likely private. - fn custom_typevar<'a>(&'a self, semantic: &'a SemanticModel<'a>) -> Option> { - let self_annotation = self.self_annotation.as_name_expr()?; - let first_arg_type = &self_annotation.id; - - let ast::ExprName { - id: return_type, .. - } = self.returns.as_name_expr()?; - - if first_arg_type != return_type { - return None; - } - - if !is_likely_private_typevar(first_arg_type, self.type_params) { - return None; - } - - semantic - .resolve_name(self_annotation) - .map(|binding_id| TypeVar(semantic.binding(binding_id))) - } -} - -/// Struct for implementing this rule as applied to instance methods in preview mode. -/// -/// In stable mode, we only emit this diagnostic on methods that have return annotations, -/// so the stable-mode version of this struct has a `returns: &ast::Expr` field. In preview -/// mode, we also emit this diagnostic on methods that do not have return annotations, so -/// the preview-mode version of this struct does not have a `returns` field. -#[derive(Debug)] -struct PreviewInstanceMethod<'a> { - self_annotation: &'a ast::Expr, - type_params: Option<&'a ast::TypeParams>, -} - -impl PreviewInstanceMethod<'_> { /// Returns `Some(typevar)` if the instance method is annotated with /// a custom `TypeVar` for the `self` parameter fn custom_typevar<'a>(&'a self, semantic: &'a SemanticModel<'a>) -> Option> { - custom_typevar_preview( + custom_typevar( self.self_annotation.as_name_expr()?, self.type_params, semantic, @@ -420,30 +260,8 @@ impl PreviewInstanceMethod<'_> { } } -/// Returns `true` if the type variable is likely private. -/// -/// This routine is only used if `--preview` is not enabled, -/// as it uses heuristics to determine if an annotation uses a type variable. -/// In preview mode, we apply a more principled approach. -fn is_likely_private_typevar(type_var_name: &str, type_params: Option<&ast::TypeParams>) -> bool { - // Ex) `_T` - if type_var_name.starts_with('_') { - return true; - } - // Ex) `class Foo[T]: ...` - type_params.is_some_and(|type_params| { - type_params.iter().any(|type_param| { - if let ast::TypeParam::TypeVar(ast::TypeParamTypeVar { name, .. }) = type_param { - name == type_var_name - } else { - false - } - }) - }) -} - /// Returns `Some(TypeVar)` if `typevar_expr` refers to a `TypeVar` binding -fn custom_typevar_preview<'a>( +fn custom_typevar<'a>( typevar_expr: &'a ast::ExprName, type_params: Option<&ast::TypeParams>, semantic: &'a SemanticModel<'a>, @@ -497,11 +315,7 @@ fn replace_custom_typevar_with_self( custom_typevar: TypeVar, self_or_cls_parameter: &ast::ParameterWithDefault, self_or_cls_annotation: &ast::Expr, -) -> anyhow::Result> { - if checker.settings.preview.is_disabled() { - return Ok(None); - } - +) -> anyhow::Result { // (1) Import `Self` (if necessary) let (import_edit, self_symbol_binding) = import_self(checker, function_def.start())?; @@ -513,9 +327,9 @@ fn replace_custom_typevar_with_self( // (3) If it was a PEP-695 type variable, remove that `TypeVar` from the PEP-695 type-parameter list if custom_typevar.is_pep695_typevar() { - let Some(type_params) = function_def.type_params.as_deref() else { - bail!("Should not be possible to have a type parameter without a type parameter list"); - }; + let type_params = function_def.type_params.as_deref().context( + "Should not be possible to have a type parameter without a type parameter list", + )?; let deletion_edit = remove_pep695_typevar_declaration(type_params, custom_typevar) .context("Failed to find a `TypeVar` in the type params that matches the binding")?; other_edits.push(deletion_edit); @@ -546,11 +360,11 @@ fn replace_custom_typevar_with_self( Applicability::Safe }; - Ok(Some(Fix::applicable_edits( + Ok(Fix::applicable_edits( import_edit, other_edits, applicability, - ))) + )) } /// Attempt to create an [`Edit`] that imports `Self`. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs index 47575512308eec..4b99c3998d71f3 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs @@ -26,9 +26,8 @@ use crate::fix; /// _Ts = typing_extensions.TypeVarTuple("_Ts") /// ``` /// -/// ## Fix safety and availability -/// This rule's fix is available when [`preview`] mode is enabled. -/// It is always marked as unsafe, as it would break your code if the type +/// ## Fix safety +/// The fix is always marked as unsafe, as it would break your code if the type /// variable is imported by another module. #[derive(ViolationMetadata)] pub(crate) struct UnusedPrivateTypeVar { @@ -225,18 +224,19 @@ pub(crate) fn unused_private_type_var(checker: &Checker, scope: &Scope) { continue; }; - let mut diagnostic = Diagnostic::new( + let diagnostic = Diagnostic::new( UnusedPrivateTypeVar { type_var_like_name: id.to_string(), type_var_like_kind: type_var_like_kind.to_string(), }, binding.range(), - ); - - if checker.settings.preview.is_enabled() { - let edit = fix::edits::delete_stmt(stmt, None, checker.locator(), checker.indexer()); - diagnostic.set_fix(Fix::unsafe_edit(edit)); - } + ) + .with_fix(Fix::unsafe_edit(fix::edits::delete_stmt( + stmt, + None, + checker.locator(), + checker.indexer(), + ))); checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI018_PYI018.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI018_PYI018.py.snap index 3d8c84bc13690f..19ab98788364a5 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI018_PYI018.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI018_PYI018.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI018.py:6:1: PYI018 Private TypeVar `_T` is never used +PYI018.py:6:1: PYI018 [*] Private TypeVar `_T` is never used | 4 | from typing_extensions import ParamSpec, TypeVarTuple 5 | @@ -12,7 +12,16 @@ PYI018.py:6:1: PYI018 Private TypeVar `_T` is never used | = help: Remove unused private TypeVar `_T` -PYI018.py:7:1: PYI018 Private TypeVarTuple `_Ts` is never used +ℹ Unsafe fix +3 3 | from typing import TypeVar +4 4 | from typing_extensions import ParamSpec, TypeVarTuple +5 5 | +6 |-_T = typing.TypeVar("_T") +7 6 | _Ts = typing_extensions.TypeVarTuple("_Ts") +8 7 | _P = ParamSpec("_P") +9 8 | _P2 = typing.ParamSpec("_P2") + +PYI018.py:7:1: PYI018 [*] Private TypeVarTuple `_Ts` is never used | 6 | _T = typing.TypeVar("_T") 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") @@ -22,7 +31,16 @@ PYI018.py:7:1: PYI018 Private TypeVarTuple `_Ts` is never used | = help: Remove unused private TypeVarTuple `_Ts` -PYI018.py:8:1: PYI018 Private ParamSpec `_P` is never used +ℹ Unsafe fix +4 4 | from typing_extensions import ParamSpec, TypeVarTuple +5 5 | +6 6 | _T = typing.TypeVar("_T") +7 |-_Ts = typing_extensions.TypeVarTuple("_Ts") +8 7 | _P = ParamSpec("_P") +9 8 | _P2 = typing.ParamSpec("_P2") +10 9 | _Ts2 = TypeVarTuple("_Ts2") + +PYI018.py:8:1: PYI018 [*] Private ParamSpec `_P` is never used | 6 | _T = typing.TypeVar("_T") 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") @@ -33,7 +51,16 @@ PYI018.py:8:1: PYI018 Private ParamSpec `_P` is never used | = help: Remove unused private ParamSpec `_P` -PYI018.py:9:1: PYI018 Private ParamSpec `_P2` is never used +ℹ Unsafe fix +5 5 | +6 6 | _T = typing.TypeVar("_T") +7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") +8 |-_P = ParamSpec("_P") +9 8 | _P2 = typing.ParamSpec("_P2") +10 9 | _Ts2 = TypeVarTuple("_Ts2") +11 10 | + +PYI018.py:9:1: PYI018 [*] Private ParamSpec `_P2` is never used | 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") 8 | _P = ParamSpec("_P") @@ -43,7 +70,16 @@ PYI018.py:9:1: PYI018 Private ParamSpec `_P2` is never used | = help: Remove unused private ParamSpec `_P2` -PYI018.py:10:1: PYI018 Private TypeVarTuple `_Ts2` is never used +ℹ Unsafe fix +6 6 | _T = typing.TypeVar("_T") +7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") +8 8 | _P = ParamSpec("_P") +9 |-_P2 = typing.ParamSpec("_P2") +10 9 | _Ts2 = TypeVarTuple("_Ts2") +11 10 | +12 11 | # OK + +PYI018.py:10:1: PYI018 [*] Private TypeVarTuple `_Ts2` is never used | 8 | _P = ParamSpec("_P") 9 | _P2 = typing.ParamSpec("_P2") @@ -53,3 +89,12 @@ PYI018.py:10:1: PYI018 Private TypeVarTuple `_Ts2` is never used 12 | # OK | = help: Remove unused private TypeVarTuple `_Ts2` + +ℹ Unsafe fix +7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") +8 8 | _P = ParamSpec("_P") +9 9 | _P2 = typing.ParamSpec("_P2") +10 |-_Ts2 = TypeVarTuple("_Ts2") +11 10 | +12 11 | # OK +13 12 | _UsedTypeVar = TypeVar("_UsedTypeVar") diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI018_PYI018.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI018_PYI018.pyi.snap index 4661096325aa85..a7913fbfa888e8 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI018_PYI018.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI018_PYI018.pyi.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI018.pyi:6:1: PYI018 Private TypeVar `_T` is never used +PYI018.pyi:6:1: PYI018 [*] Private TypeVar `_T` is never used | 4 | from typing_extensions import ParamSpec, TypeVarTuple 5 | @@ -12,7 +12,16 @@ PYI018.pyi:6:1: PYI018 Private TypeVar `_T` is never used | = help: Remove unused private TypeVar `_T` -PYI018.pyi:7:1: PYI018 Private TypeVarTuple `_Ts` is never used +ℹ Unsafe fix +3 3 | from typing import TypeVar +4 4 | from typing_extensions import ParamSpec, TypeVarTuple +5 5 | +6 |-_T = typing.TypeVar("_T") +7 6 | _Ts = typing_extensions.TypeVarTuple("_Ts") +8 7 | _P = ParamSpec("_P") +9 8 | _P2 = typing.ParamSpec("_P2") + +PYI018.pyi:7:1: PYI018 [*] Private TypeVarTuple `_Ts` is never used | 6 | _T = typing.TypeVar("_T") 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") @@ -22,7 +31,16 @@ PYI018.pyi:7:1: PYI018 Private TypeVarTuple `_Ts` is never used | = help: Remove unused private TypeVarTuple `_Ts` -PYI018.pyi:8:1: PYI018 Private ParamSpec `_P` is never used +ℹ Unsafe fix +4 4 | from typing_extensions import ParamSpec, TypeVarTuple +5 5 | +6 6 | _T = typing.TypeVar("_T") +7 |-_Ts = typing_extensions.TypeVarTuple("_Ts") +8 7 | _P = ParamSpec("_P") +9 8 | _P2 = typing.ParamSpec("_P2") +10 9 | _Ts2 = TypeVarTuple("_Ts2") + +PYI018.pyi:8:1: PYI018 [*] Private ParamSpec `_P` is never used | 6 | _T = typing.TypeVar("_T") 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") @@ -33,7 +51,16 @@ PYI018.pyi:8:1: PYI018 Private ParamSpec `_P` is never used | = help: Remove unused private ParamSpec `_P` -PYI018.pyi:9:1: PYI018 Private ParamSpec `_P2` is never used +ℹ Unsafe fix +5 5 | +6 6 | _T = typing.TypeVar("_T") +7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") +8 |-_P = ParamSpec("_P") +9 8 | _P2 = typing.ParamSpec("_P2") +10 9 | _Ts2 = TypeVarTuple("_Ts2") +11 10 | + +PYI018.pyi:9:1: PYI018 [*] Private ParamSpec `_P2` is never used | 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") 8 | _P = ParamSpec("_P") @@ -43,7 +70,16 @@ PYI018.pyi:9:1: PYI018 Private ParamSpec `_P2` is never used | = help: Remove unused private ParamSpec `_P2` -PYI018.pyi:10:1: PYI018 Private TypeVarTuple `_Ts2` is never used +ℹ Unsafe fix +6 6 | _T = typing.TypeVar("_T") +7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") +8 8 | _P = ParamSpec("_P") +9 |-_P2 = typing.ParamSpec("_P2") +10 9 | _Ts2 = TypeVarTuple("_Ts2") +11 10 | +12 11 | # OK + +PYI018.pyi:10:1: PYI018 [*] Private TypeVarTuple `_Ts2` is never used | 8 | _P = ParamSpec("_P") 9 | _P2 = typing.ParamSpec("_P2") @@ -53,3 +89,12 @@ PYI018.pyi:10:1: PYI018 Private TypeVarTuple `_Ts2` is never used 12 | # OK | = help: Remove unused private TypeVarTuple `_Ts2` + +ℹ Unsafe fix +7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") +8 8 | _P = ParamSpec("_P") +9 9 | _P2 = typing.ParamSpec("_P2") +10 |-_Ts2 = TypeVarTuple("_Ts2") +11 10 | +12 11 | # OK +13 12 | _UsedTypeVar = TypeVar("_UsedTypeVar") diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.py.snap index b060ca42e9af65..1bbfbef8e2c361 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.py.snap @@ -1,316 +1,692 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI019_0.py:7:62: PYI019 Use `Self` instead of custom TypeVar `_S` +PYI019_0.py:7:16: PYI019 [*] Use `Self` instead of custom TypeVar `_S` | 6 | class BadClass: 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 - | ^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `_S` with `Self` -PYI019_0.py:10:54: PYI019 Use `Self` instead of custom TypeVar `_S` +ℹ Safe fix +4 4 | _S2 = TypeVar("_S2", BadClass, GoodClass) +5 5 | +6 6 | class BadClass: +7 |- def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 + 7 |+ def __new__(cls, *args: str, **kwargs: int) -> Self: ... # PYI019 +8 8 | +9 9 | +10 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 + +PYI019_0.py:10:28: PYI019 [*] Use `Self` instead of custom TypeVar `_S` | 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 - | ^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `_S` with `Self` -PYI019_0.py:14:54: PYI019 Use `Self` instead of custom TypeVar `_S` +ℹ Safe fix +7 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 +8 8 | +9 9 | +10 |- def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 + 10 |+ def bad_instance_method(self, arg: bytes) -> Self: ... # PYI019 +11 11 | +12 12 | +13 13 | @classmethod + +PYI019_0.py:14:25: PYI019 [*] Use `Self` instead of custom TypeVar `_S` | 13 | @classmethod 14 | def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 - | ^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `_S` with `Self` -PYI019_0.py:18:55: PYI019 Use `Self` instead of custom TypeVar `_S` +ℹ Safe fix +11 11 | +12 12 | +13 13 | @classmethod +14 |- def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 + 14 |+ def bad_class_method(cls, arg: int) -> Self: ... # PYI019 +15 15 | +16 16 | +17 17 | @classmethod + +PYI019_0.py:18:33: PYI019 [*] Use `Self` instead of custom TypeVar `_S` | 17 | @classmethod 18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 - | ^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `_S` with `Self` -PYI019_0.py:39:63: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +15 15 | +16 16 | +17 17 | @classmethod +18 |- def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 + 18 |+ def bad_posonly_class_method(cls, /) -> Self: ... # PYI019 +19 19 | +20 20 | +21 21 | @classmethod + +PYI019_0.py:39:14: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 37 | # Python > 3.12 38 | class PEP695BadDunderNew[T]: 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:42:46: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +36 36 | +37 37 | # Python > 3.12 +38 38 | class PEP695BadDunderNew[T]: +39 |- def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 + 39 |+ def __new__(cls, *args: Any, ** kwargs: Any) -> Self: ... # PYI019 +40 40 | +41 41 | +42 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 + +PYI019_0.py:42:30: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:54:32: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +39 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 +40 40 | +41 41 | +42 |- def generic_instance_method[S](self: S) -> S: ... # PYI019 + 42 |+ def generic_instance_method(self) -> Self: ... # PYI019 +43 43 | +44 44 | +45 45 | class PEP695GoodDunderNew[T]: + +PYI019_0.py:54:11: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 52 | # in the settings for this test: 53 | @foo_classmethod 54 | def foo[S](cls: type[S]) -> S: ... # PYI019 - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:61:48: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +51 51 | # due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators` +52 52 | # in the settings for this test: +53 53 | @foo_classmethod +54 |- def foo[S](cls: type[S]) -> S: ... # PYI019 + 54 |+ def foo(cls) -> Self: ... # PYI019 +55 55 | +56 56 | +57 57 | _S695 = TypeVar("_S695", bound="PEP695Fix") + +PYI019_0.py:61:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 60 | class PEP695Fix: 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 62 | 63 | def __init_subclass__[S](cls: type[S]) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:63:47: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +58 58 | +59 59 | +60 60 | class PEP695Fix: +61 |- def __new__[S: PEP695Fix](cls: type[S]) -> S: ... + 61 |+ def __new__(cls) -> Self: ... +62 62 | +63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... +64 64 | + +PYI019_0.py:63:26: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... 62 | 63 | def __init_subclass__[S](cls: type[S]) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 64 | 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:65:43: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +60 60 | class PEP695Fix: +61 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... +62 62 | +63 |- def __init_subclass__[S](cls: type[S]) -> S: ... + 63 |+ def __init_subclass__(cls) -> Self: ... +64 64 | +65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... +66 66 | + +PYI019_0.py:65:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 63 | def __init_subclass__[S](cls: type[S]) -> S: ... 64 | 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 66 | 67 | def __pos__[S](self: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:67:32: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +62 62 | +63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... +64 64 | +65 |- def __neg__[S: PEP695Fix](self: S) -> S: ... + 65 |+ def __neg__(self) -> Self: ... +66 66 | +67 67 | def __pos__[S](self: S) -> S: ... +68 68 | + +PYI019_0.py:67:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... 66 | 67 | def __pos__[S](self: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^ PYI019 68 | 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:69:53: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +64 64 | +65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... +66 66 | +67 |- def __pos__[S](self: S) -> S: ... + 67 |+ def __pos__(self) -> Self: ... +68 68 | +69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +70 70 | + +PYI019_0.py:69:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 67 | def __pos__[S](self: S) -> S: ... 68 | 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 70 | 71 | def __sub__[S](self: S, other: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:71:42: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +66 66 | +67 67 | def __pos__[S](self: S) -> S: ... +68 68 | +69 |- def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + 69 |+ def __add__(self, other: Self) -> Self: ... +70 70 | +71 71 | def __sub__[S](self: S, other: S) -> S: ... +72 72 | + +PYI019_0.py:71:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... 70 | 71 | def __sub__[S](self: S, other: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 72 | 73 | @classmethod | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:74:59: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +68 68 | +69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +70 70 | +71 |- def __sub__[S](self: S, other: S) -> S: ... + 71 |+ def __sub__(self, other: Self) -> Self: ... +72 72 | +73 73 | @classmethod +74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + +PYI019_0.py:74:27: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 73 | @classmethod 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 75 | 76 | @classmethod | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:77:50: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +71 71 | def __sub__[S](self: S, other: S) -> S: ... +72 72 | +73 73 | @classmethod +74 |- def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + 74 |+ def class_method_bound(cls) -> Self: ... +75 75 | +76 76 | @classmethod +77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... + +PYI019_0.py:77:29: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 76 | @classmethod 77 | def class_method_unbound[S](cls: type[S]) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 78 | 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:79:57: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... +75 75 | +76 76 | @classmethod +77 |- def class_method_unbound[S](cls: type[S]) -> S: ... + 77 |+ def class_method_unbound(cls) -> Self: ... +78 78 | +79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +80 80 | + +PYI019_0.py:79:30: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 77 | def class_method_unbound[S](cls: type[S]) -> S: ... 78 | 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 80 | 81 | def instance_method_unbound[S](self: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:81:48: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +76 76 | @classmethod +77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... +78 78 | +79 |- def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + 79 |+ def instance_method_bound(self) -> Self: ... +80 80 | +81 81 | def instance_method_unbound[S](self: S) -> S: ... +82 82 | + +PYI019_0.py:81:32: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... 80 | 81 | def instance_method_unbound[S](self: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^ PYI019 82 | 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:83:90: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +78 78 | +79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +80 80 | +81 |- def instance_method_unbound[S](self: S) -> S: ... + 81 |+ def instance_method_unbound(self) -> Self: ... +82 82 | +83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +84 84 | + +PYI019_0.py:83:53: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 81 | def instance_method_unbound[S](self: S) -> S: ... 82 | 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 84 | 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:85:81: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +80 80 | +81 81 | def instance_method_unbound[S](self: S) -> S: ... +82 82 | +83 |- def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + 83 |+ def instance_method_bound_with_another_parameter(self, other: Self) -> Self: ... +84 84 | +85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 86 | + +PYI019_0.py:85:55: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... 84 | 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 86 | 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:87:94: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +82 82 | +83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +84 84 | +85 |- def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + 85 |+ def instance_method_unbound_with_another_parameter(self, other: Self) -> Self: ... +86 86 | +87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 88 | + +PYI019_0.py:87:27: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... 86 | 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 88 | 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:89:75: PYI019 Use `Self` instead of custom TypeVar `_S695` +ℹ Safe fix +84 84 | +85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 86 | +87 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + 87 |+ def multiple_type_vars[*Ts, T](self, other: Self, /, *args: *Ts, a: T, b: list[T]) -> Self: ... +88 88 | +89 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... +90 90 | + +PYI019_0.py:89:43: PYI019 [*] Use `Self` instead of custom TypeVar `_S695` | 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... 88 | 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... - | ^^^^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `_S695` with `Self` -PYI019_0.py:114:31: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +86 86 | +87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 88 | +89 |- def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + 89 |+ def mixing_old_and_new_style_type_vars[T](self, a: T, b: T) -> Self: ... +90 90 | +91 91 | +92 92 | class InvalidButWeDoNotPanic: + +PYI019_0.py:94:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` + | +92 | class InvalidButWeDoNotPanic: +93 | @classmethod +94 | def m[S](cls: type[S], /) -> S[int]: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 +95 | def n(self: S) -> S[int]: ... + | + = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +91 91 | +92 92 | class InvalidButWeDoNotPanic: +93 93 | @classmethod +94 |- def m[S](cls: type[S], /) -> S[int]: ... + 94 |+ def m(cls, /) -> Self[int]: ... +95 95 | def n(self: S) -> S[int]: ... +96 96 | +97 97 | + +PYI019_0.py:114:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 112 | class SubscriptReturnType: 113 | @classmethod 114 | def m[S](cls: type[S]) -> type[S]: ... # PYI019 - | ^^^^^^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 + | + = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +111 111 | +112 112 | class SubscriptReturnType: +113 113 | @classmethod +114 |- def m[S](cls: type[S]) -> type[S]: ... # PYI019 + 114 |+ def m(cls) -> type[Self]: ... # PYI019 +115 115 | +116 116 | +117 117 | class SelfNotUsedInReturnAnnotation: + +PYI019_0.py:118:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` + | +117 | class SelfNotUsedInReturnAnnotation: +118 | def m[S](self: S, other: S) -> int: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 +119 | @classmethod +120 | def n[S](cls: type[S], other: S) -> int: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:129:34: PYI019 Use `Self` instead of custom TypeVar `_NotATypeVar` +ℹ Safe fix +115 115 | +116 116 | +117 117 | class SelfNotUsedInReturnAnnotation: +118 |- def m[S](self: S, other: S) -> int: ... + 118 |+ def m(self, other: Self) -> int: ... +119 119 | @classmethod +120 120 | def n[S](cls: type[S], other: S) -> int: ... +121 121 | + +PYI019_0.py:120:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` + | +118 | def m[S](self: S, other: S) -> int: ... +119 | @classmethod +120 | def n[S](cls: type[S], other: S) -> int: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 + | + = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +117 117 | class SelfNotUsedInReturnAnnotation: +118 118 | def m[S](self: S, other: S) -> int: ... +119 119 | @classmethod +120 |- def n[S](cls: type[S], other: S) -> int: ... + 120 |+ def n(cls, other: Self) -> int: ... +121 121 | +122 122 | +123 123 | class _NotATypeVar: ... + +PYI019_0.py:135:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | -127 | # but our preview-mode logic is smarter about this. -128 | class Foo: -129 | def x(self: _NotATypeVar) -> _NotATypeVar: ... - | ^^^^^^^^^^^^ PYI019 -130 | @classmethod -131 | def y(self: type[_NotATypeVar]) -> _NotATypeVar: ... +134 | class NoReturnAnnotations: +135 | def m[S](self: S, other: S): ... + | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 +136 | @classmethod +137 | def n[S](cls: type[S], other: S): ... | - = help: Replace TypeVar `_NotATypeVar` with `Self` + = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +132 132 | +133 133 | +134 134 | class NoReturnAnnotations: +135 |- def m[S](self: S, other: S): ... + 135 |+ def m(self, other: Self): ... +136 136 | @classmethod +137 137 | def n[S](cls: type[S], other: S): ... +138 138 | -PYI019_0.py:131:40: PYI019 Use `Self` instead of custom TypeVar `_NotATypeVar` +PYI019_0.py:137:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | -129 | def x(self: _NotATypeVar) -> _NotATypeVar: ... -130 | @classmethod -131 | def y(self: type[_NotATypeVar]) -> _NotATypeVar: ... - | ^^^^^^^^^^^^ PYI019 +135 | def m[S](self: S, other: S): ... +136 | @classmethod +137 | def n[S](cls: type[S], other: S): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 +138 | +139 | class MultipleBoundParameters: | - = help: Replace TypeVar `_NotATypeVar` with `Self` + = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +134 134 | class NoReturnAnnotations: +135 135 | def m[S](self: S, other: S): ... +136 136 | @classmethod +137 |- def n[S](cls: type[S], other: S): ... + 137 |+ def n(cls, other: Self): ... +138 138 | +139 139 | class MultipleBoundParameters: +140 140 | def m[S: int, T: int](self: S, other: T) -> S: ... -PYI019_0.py:140:49: PYI019 Use `Self` instead of custom TypeVar `S` +PYI019_0.py:140:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 139 | class MultipleBoundParameters: 140 | def m[S: int, T: int](self: S, other: T) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 141 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:141:63: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +137 137 | def n[S](cls: type[S], other: S): ... +138 138 | +139 139 | class MultipleBoundParameters: +140 |- def m[S: int, T: int](self: S, other: T) -> S: ... + 140 |+ def m[T: int](self, other: T) -> Self: ... +141 141 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... +142 142 | +143 143 | class MethodsWithBody: + +PYI019_0.py:141:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 139 | class MultipleBoundParameters: 140 | def m[S: int, T: int](self: S, other: T) -> S: ... 141 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 142 | 143 | class MethodsWithBody: | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:144:36: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +138 138 | +139 139 | class MultipleBoundParameters: +140 140 | def m[S: int, T: int](self: S, other: T) -> S: ... +141 |- def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... + 141 |+ def n[T: (int, str)](self, other: T) -> Self: ... +142 142 | +143 143 | class MethodsWithBody: +144 144 | def m[S](self: S, other: S) -> S: + +PYI019_0.py:144:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 143 | class MethodsWithBody: 144 | def m[S](self: S, other: S) -> S: - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 145 | x: S = other 146 | return x | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:149:41: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +141 141 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... +142 142 | +143 143 | class MethodsWithBody: +144 |- def m[S](self: S, other: S) -> S: +145 |- x: S = other + 144 |+ def m(self, other: Self) -> Self: + 145 |+ x: Self = other +146 146 | return x +147 147 | +148 148 | @classmethod + +PYI019_0.py:149:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 148 | @classmethod 149 | def n[S](cls: type[S], other: S) -> S: - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 150 | x: type[S] = type(other) 151 | return x() | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:154:26: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +146 146 | return x +147 147 | +148 148 | @classmethod +149 |- def n[S](cls: type[S], other: S) -> S: +150 |- x: type[S] = type(other) + 149 |+ def n(cls, other: Self) -> Self: + 150 |+ x: type[Self] = type(other) +151 151 | return x() +152 152 | +153 153 | class StringizedReferencesCanBeFixed: + +PYI019_0.py:154:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 153 | class StringizedReferencesCanBeFixed: 154 | def m[S](self: S) -> S: - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^ PYI019 155 | x = cast("list[tuple[S, S]]", self) 156 | return x | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:159:28: PYI019 Use `Self` instead of custom TypeVar `_T` +ℹ Safe fix +151 151 | return x() +152 152 | +153 153 | class StringizedReferencesCanBeFixed: +154 |- def m[S](self: S) -> S: +155 |- x = cast("list[tuple[S, S]]", self) + 154 |+ def m(self) -> Self: + 155 |+ x = cast("list[tuple[Self, Self]]", self) +156 156 | return x +157 157 | +158 158 | class ButStrangeStringizedReferencesCannotBeFixed: + +PYI019_0.py:159:10: PYI019 Use `Self` instead of custom TypeVar `_T` | 158 | class ButStrangeStringizedReferencesCannotBeFixed: 159 | def m[_T](self: _T) -> _T: - | ^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^ PYI019 160 | x = cast('list[_\x54]', self) 161 | return x | = help: Replace TypeVar `_T` with `Self` -PYI019_0.py:164:26: PYI019 Use `Self` instead of custom TypeVar `S` +PYI019_0.py:164:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 163 | class DeletionsAreNotTouched: 164 | def m[S](self: S) -> S: - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^ PYI019 165 | # `S` is not a local variable here, and `del` can only be used with local variables, 166 | # so `del S` here is not actually a reference to the type variable `S`. | = help: Replace TypeVar `S` with `Self` -PYI019_0.py:173:26: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +161 161 | return x +162 162 | +163 163 | class DeletionsAreNotTouched: +164 |- def m[S](self: S) -> S: + 164 |+ def m(self) -> Self: +165 165 | # `S` is not a local variable here, and `del` can only be used with local variables, +166 166 | # so `del S` here is not actually a reference to the type variable `S`. +167 167 | # This `del` statement is therefore not touched by the autofix (it raises `UnboundLocalError` + +PYI019_0.py:173:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 172 | class NamesShadowingTypeVarAreNotTouched: 173 | def m[S](self: S) -> S: - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^ PYI019 174 | type S = int 175 | print(S) # not a reference to the type variable, so not touched by the autofix | = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +170 170 | return self +171 171 | +172 172 | class NamesShadowingTypeVarAreNotTouched: +173 |- def m[S](self: S) -> S: + 173 |+ def m(self) -> Self: +174 174 | type S = int +175 175 | print(S) # not a reference to the type variable, so not touched by the autofix +176 176 | return 42 diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.pyi.snap index 9ec752ffcb01fe..7c5b794bd56218 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.pyi.snap @@ -1,293 +1,684 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI019_0.pyi:7:62: PYI019 Use `Self` instead of custom TypeVar `_S` +PYI019_0.pyi:7:16: PYI019 [*] Use `Self` instead of custom TypeVar `_S` | 6 | class BadClass: 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 - | ^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `_S` with `Self` -PYI019_0.pyi:10:54: PYI019 Use `Self` instead of custom TypeVar `_S` +ℹ Safe fix +4 4 | _S2 = TypeVar("_S2", BadClass, GoodClass) +5 5 | +6 6 | class BadClass: +7 |- def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 + 7 |+ def __new__(cls, *args: str, **kwargs: int) -> Self: ... # PYI019 +8 8 | +9 9 | +10 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 + +PYI019_0.pyi:10:28: PYI019 [*] Use `Self` instead of custom TypeVar `_S` | 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 - | ^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `_S` with `Self` -PYI019_0.pyi:14:54: PYI019 Use `Self` instead of custom TypeVar `_S` +ℹ Safe fix +7 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 +8 8 | +9 9 | +10 |- def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 + 10 |+ def bad_instance_method(self, arg: bytes) -> Self: ... # PYI019 +11 11 | +12 12 | +13 13 | @classmethod + +PYI019_0.pyi:14:25: PYI019 [*] Use `Self` instead of custom TypeVar `_S` | 13 | @classmethod 14 | def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 - | ^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `_S` with `Self` -PYI019_0.pyi:18:55: PYI019 Use `Self` instead of custom TypeVar `_S` +ℹ Safe fix +11 11 | +12 12 | +13 13 | @classmethod +14 |- def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 + 14 |+ def bad_class_method(cls, arg: int) -> Self: ... # PYI019 +15 15 | +16 16 | +17 17 | @classmethod + +PYI019_0.pyi:18:33: PYI019 [*] Use `Self` instead of custom TypeVar `_S` | 17 | @classmethod 18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 - | ^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `_S` with `Self` -PYI019_0.pyi:39:63: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +15 15 | +16 16 | +17 17 | @classmethod +18 |- def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 + 18 |+ def bad_posonly_class_method(cls, /) -> Self: ... # PYI019 +19 19 | +20 20 | +21 21 | @classmethod + +PYI019_0.pyi:39:14: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 37 | # Python > 3.12 38 | class PEP695BadDunderNew[T]: 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:42:46: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +36 36 | +37 37 | # Python > 3.12 +38 38 | class PEP695BadDunderNew[T]: +39 |- def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 + 39 |+ def __new__(cls, *args: Any, ** kwargs: Any) -> Self: ... # PYI019 +40 40 | +41 41 | +42 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 + +PYI019_0.pyi:42:30: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:54:32: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +39 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 +40 40 | +41 41 | +42 |- def generic_instance_method[S](self: S) -> S: ... # PYI019 + 42 |+ def generic_instance_method(self) -> Self: ... # PYI019 +43 43 | +44 44 | +45 45 | class PEP695GoodDunderNew[T]: + +PYI019_0.pyi:54:11: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 52 | # in the settings for this test: 53 | @foo_classmethod 54 | def foo[S](cls: type[S]) -> S: ... # PYI019 - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:61:48: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +51 51 | # due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators` +52 52 | # in the settings for this test: +53 53 | @foo_classmethod +54 |- def foo[S](cls: type[S]) -> S: ... # PYI019 + 54 |+ def foo(cls) -> Self: ... # PYI019 +55 55 | +56 56 | +57 57 | _S695 = TypeVar("_S695", bound="PEP695Fix") + +PYI019_0.pyi:61:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 60 | class PEP695Fix: 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 62 | 63 | def __init_subclass__[S](cls: type[S]) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:63:47: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +58 58 | +59 59 | +60 60 | class PEP695Fix: +61 |- def __new__[S: PEP695Fix](cls: type[S]) -> S: ... + 61 |+ def __new__(cls) -> Self: ... +62 62 | +63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... +64 64 | + +PYI019_0.pyi:63:26: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... 62 | 63 | def __init_subclass__[S](cls: type[S]) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 64 | 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:65:43: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +60 60 | class PEP695Fix: +61 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... +62 62 | +63 |- def __init_subclass__[S](cls: type[S]) -> S: ... + 63 |+ def __init_subclass__(cls) -> Self: ... +64 64 | +65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... +66 66 | + +PYI019_0.pyi:65:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 63 | def __init_subclass__[S](cls: type[S]) -> S: ... 64 | 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 66 | 67 | def __pos__[S](self: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:67:32: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +62 62 | +63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... +64 64 | +65 |- def __neg__[S: PEP695Fix](self: S) -> S: ... + 65 |+ def __neg__(self) -> Self: ... +66 66 | +67 67 | def __pos__[S](self: S) -> S: ... +68 68 | + +PYI019_0.pyi:67:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... 66 | 67 | def __pos__[S](self: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^ PYI019 68 | 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:69:53: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +64 64 | +65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... +66 66 | +67 |- def __pos__[S](self: S) -> S: ... + 67 |+ def __pos__(self) -> Self: ... +68 68 | +69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +70 70 | + +PYI019_0.pyi:69:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 67 | def __pos__[S](self: S) -> S: ... 68 | 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 70 | 71 | def __sub__[S](self: S, other: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:71:42: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +66 66 | +67 67 | def __pos__[S](self: S) -> S: ... +68 68 | +69 |- def __add__[S: PEP695Fix](self: S, other: S) -> S: ... + 69 |+ def __add__(self, other: Self) -> Self: ... +70 70 | +71 71 | def __sub__[S](self: S, other: S) -> S: ... +72 72 | + +PYI019_0.pyi:71:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... 70 | 71 | def __sub__[S](self: S, other: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 72 | 73 | @classmethod | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:74:59: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +68 68 | +69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... +70 70 | +71 |- def __sub__[S](self: S, other: S) -> S: ... + 71 |+ def __sub__(self, other: Self) -> Self: ... +72 72 | +73 73 | @classmethod +74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + +PYI019_0.pyi:74:27: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 73 | @classmethod 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 75 | 76 | @classmethod | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:77:50: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +71 71 | def __sub__[S](self: S, other: S) -> S: ... +72 72 | +73 73 | @classmethod +74 |- def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... + 74 |+ def class_method_bound(cls) -> Self: ... +75 75 | +76 76 | @classmethod +77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... + +PYI019_0.pyi:77:29: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 76 | @classmethod 77 | def class_method_unbound[S](cls: type[S]) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 78 | 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:79:57: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... +75 75 | +76 76 | @classmethod +77 |- def class_method_unbound[S](cls: type[S]) -> S: ... + 77 |+ def class_method_unbound(cls) -> Self: ... +78 78 | +79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +80 80 | + +PYI019_0.pyi:79:30: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 77 | def class_method_unbound[S](cls: type[S]) -> S: ... 78 | 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 80 | 81 | def instance_method_unbound[S](self: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:81:48: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +76 76 | @classmethod +77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... +78 78 | +79 |- def instance_method_bound[S: PEP695Fix](self: S) -> S: ... + 79 |+ def instance_method_bound(self) -> Self: ... +80 80 | +81 81 | def instance_method_unbound[S](self: S) -> S: ... +82 82 | + +PYI019_0.pyi:81:32: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... 80 | 81 | def instance_method_unbound[S](self: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^ PYI019 82 | 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:83:90: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +78 78 | +79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... +80 80 | +81 |- def instance_method_unbound[S](self: S) -> S: ... + 81 |+ def instance_method_unbound(self) -> Self: ... +82 82 | +83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +84 84 | + +PYI019_0.pyi:83:53: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 81 | def instance_method_unbound[S](self: S) -> S: ... 82 | 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 84 | 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:85:81: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +80 80 | +81 81 | def instance_method_unbound[S](self: S) -> S: ... +82 82 | +83 |- def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... + 83 |+ def instance_method_bound_with_another_parameter(self, other: Self) -> Self: ... +84 84 | +85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 86 | + +PYI019_0.pyi:85:55: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... 84 | 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 86 | 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:87:94: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +82 82 | +83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... +84 84 | +85 |- def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... + 85 |+ def instance_method_unbound_with_another_parameter(self, other: Self) -> Self: ... +86 86 | +87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 88 | + +PYI019_0.pyi:87:27: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... 86 | 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 88 | 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:89:75: PYI019 Use `Self` instead of custom TypeVar `_S695` +ℹ Safe fix +84 84 | +85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... +86 86 | +87 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... + 87 |+ def multiple_type_vars[*Ts, T](self, other: Self, /, *args: *Ts, a: T, b: list[T]) -> Self: ... +88 88 | +89 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... +90 90 | + +PYI019_0.pyi:89:43: PYI019 [*] Use `Self` instead of custom TypeVar `_S695` | 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... 88 | 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... - | ^^^^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `_S695` with `Self` -PYI019_0.pyi:114:31: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +86 86 | +87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... +88 88 | +89 |- def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... + 89 |+ def mixing_old_and_new_style_type_vars[T](self, a: T, b: T) -> Self: ... +90 90 | +91 91 | +92 92 | class InvalidButWeDoNotPanic: + +PYI019_0.pyi:94:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` + | +92 | class InvalidButWeDoNotPanic: +93 | @classmethod +94 | def m[S](cls: type[S], /) -> S[int]: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 +95 | def n(self: S) -> S[int]: ... + | + = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +91 91 | +92 92 | class InvalidButWeDoNotPanic: +93 93 | @classmethod +94 |- def m[S](cls: type[S], /) -> S[int]: ... + 94 |+ def m(cls, /) -> Self[int]: ... +95 95 | def n(self: S) -> S[int]: ... +96 96 | +97 97 | + +PYI019_0.pyi:114:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 112 | class SubscriptReturnType: 113 | @classmethod 114 | def m[S](cls: type[S]) -> type[S]: ... # PYI019 - | ^^^^^^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:118:29: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +111 111 | +112 112 | class SubscriptReturnType: +113 113 | @classmethod +114 |- def m[S](cls: type[S]) -> type[S]: ... # PYI019 + 114 |+ def m(cls) -> type[Self]: ... # PYI019 +115 115 | +116 116 | +117 117 | class PEP695TypeParameterAtTheVeryEndOfTheList: + +PYI019_0.pyi:118:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 117 | class PEP695TypeParameterAtTheVeryEndOfTheList: 118 | def f[T, S](self: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:122:100: PYI019 Use `Self` instead of custom TypeVar `_S695` +ℹ Safe fix +115 115 | +116 116 | +117 117 | class PEP695TypeParameterAtTheVeryEndOfTheList: +118 |- def f[T, S](self: S) -> S: ... + 118 |+ def f[T](self) -> Self: ... +119 119 | +120 120 | +121 121 | class PEP695Again: + +PYI019_0.pyi:122:26: PYI019 [*] Use `Self` instead of custom TypeVar `_S695` | 121 | class PEP695Again: 122 | def mixing_and_nested[T](self: _S695, a: list[_S695], b: dict[_S695, str | T | set[_S695]]) -> _S695: ... - | ^^^^^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 123 | def also_uses_s695_but_should_not_be_edited(self, v: set[tuple[_S695]]) -> _S695: ... | = help: Replace TypeVar `_S695` with `Self` -PYI019_0.pyi:132:10: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +119 119 | +120 120 | +121 121 | class PEP695Again: +122 |- def mixing_and_nested[T](self: _S695, a: list[_S695], b: dict[_S695, str | T | set[_S695]]) -> _S695: ... + 122 |+ def mixing_and_nested[T](self, a: list[Self], b: dict[Self, str | T | set[Self]]) -> Self: ... +123 123 | def also_uses_s695_but_should_not_be_edited(self, v: set[tuple[_S695]]) -> _S695: ... +124 124 | +125 125 | @classmethod + +PYI019_0.pyi:126:29: PYI019 [*] Use `Self` instead of custom TypeVar `S` + | +125 | @classmethod +126 | def comment_in_fix_range[T, S]( + | _____________________________^ +127 | | cls: type[ # Lorem ipsum +128 | | S +129 | | ], +130 | | a: T, +131 | | b: tuple[S, T] +132 | | ) -> S: ... + | |__________^ PYI019 +133 | +134 | def comment_outside_fix_range[T, S]( + | + = help: Replace TypeVar `S` with `Self` + +ℹ Unsafe fix +123 123 | def also_uses_s695_but_should_not_be_edited(self, v: set[tuple[_S695]]) -> _S695: ... +124 124 | +125 125 | @classmethod +126 |- def comment_in_fix_range[T, S]( +127 |- cls: type[ # Lorem ipsum +128 |- S +129 |- ], + 126 |+ def comment_in_fix_range[T]( + 127 |+ cls, +130 128 | a: T, +131 |- b: tuple[S, T] +132 |- ) -> S: ... + 129 |+ b: tuple[Self, T] + 130 |+ ) -> Self: ... +133 131 | +134 132 | def comment_outside_fix_range[T, S]( +135 133 | self: S, + +PYI019_0.pyi:134:34: PYI019 [*] Use `Self` instead of custom TypeVar `S` | -130 | a: T, -131 | b: tuple[S, T] -132 | ) -> S: ... - | ^ PYI019 +132 | ) -> S: ... 133 | -134 | def comment_outside_fix_range[T, S]( +134 | def comment_outside_fix_range[T, S]( + | __________________________________^ +135 | | self: S, +136 | | a: T, +137 | | b: tuple[ +138 | | # Lorem ipsum +139 | | S, T +140 | | ] +141 | | ) -> S: ... + | |__________^ PYI019 | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:141:10: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +131 131 | b: tuple[S, T] +132 132 | ) -> S: ... +133 133 | +134 |- def comment_outside_fix_range[T, S]( +135 |- self: S, + 134 |+ def comment_outside_fix_range[T]( + 135 |+ self, +136 136 | a: T, +137 137 | b: tuple[ +138 138 | # Lorem ipsum +139 |- S, T + 139 |+ Self, T +140 140 | ] +141 |- ) -> S: ... + 141 |+ ) -> Self: ... +142 142 | +143 143 | +144 144 | class SelfNotUsedInReturnAnnotation: + +PYI019_0.pyi:145:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | -139 | S, T -140 | ] -141 | ) -> S: ... - | ^ PYI019 +144 | class SelfNotUsedInReturnAnnotation: +145 | def m[S](self: S, other: S) -> int: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 +146 | @classmethod +147 | def n[S](cls: type[S], other: S) -> int: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:156:34: PYI019 Use `Self` instead of custom TypeVar `_NotATypeVar` +ℹ Safe fix +142 142 | +143 143 | +144 144 | class SelfNotUsedInReturnAnnotation: +145 |- def m[S](self: S, other: S) -> int: ... + 145 |+ def m(self, other: Self) -> int: ... +146 146 | @classmethod +147 147 | def n[S](cls: type[S], other: S) -> int: ... +148 148 | + +PYI019_0.pyi:147:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | -154 | # but our preview-mode logic is smarter about this. -155 | class Foo: -156 | def x(self: _NotATypeVar) -> _NotATypeVar: ... - | ^^^^^^^^^^^^ PYI019 -157 | @classmethod -158 | def y(self: type[_NotATypeVar]) -> _NotATypeVar: ... +145 | def m[S](self: S, other: S) -> int: ... +146 | @classmethod +147 | def n[S](cls: type[S], other: S) -> int: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | - = help: Replace TypeVar `_NotATypeVar` with `Self` + = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +144 144 | class SelfNotUsedInReturnAnnotation: +145 145 | def m[S](self: S, other: S) -> int: ... +146 146 | @classmethod +147 |- def n[S](cls: type[S], other: S) -> int: ... + 147 |+ def n(cls, other: Self) -> int: ... +148 148 | +149 149 | +150 150 | class _NotATypeVar: ... -PYI019_0.pyi:158:40: PYI019 Use `Self` instead of custom TypeVar `_NotATypeVar` +PYI019_0.pyi:161:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | -156 | def x(self: _NotATypeVar) -> _NotATypeVar: ... -157 | @classmethod -158 | def y(self: type[_NotATypeVar]) -> _NotATypeVar: ... - | ^^^^^^^^^^^^ PYI019 -159 | 160 | class NoReturnAnnotations: +161 | def m[S](self: S, other: S): ... + | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 +162 | @classmethod +163 | def n[S](cls: type[S], other: S): ... + | + = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +158 158 | def y(self: type[_NotATypeVar]) -> _NotATypeVar: ... +159 159 | +160 160 | class NoReturnAnnotations: +161 |- def m[S](self: S, other: S): ... + 161 |+ def m(self, other: Self): ... +162 162 | @classmethod +163 163 | def n[S](cls: type[S], other: S): ... +164 164 | + +PYI019_0.pyi:163:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | - = help: Replace TypeVar `_NotATypeVar` with `Self` +161 | def m[S](self: S, other: S): ... +162 | @classmethod +163 | def n[S](cls: type[S], other: S): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 +164 | +165 | class MultipleBoundParameters: + | + = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:166:49: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +160 160 | class NoReturnAnnotations: +161 161 | def m[S](self: S, other: S): ... +162 162 | @classmethod +163 |- def n[S](cls: type[S], other: S): ... + 163 |+ def n(cls, other: Self): ... +164 164 | +165 165 | class MultipleBoundParameters: +166 166 | def m[S: int, T: int](self: S, other: T) -> S: ... + +PYI019_0.pyi:166:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 165 | class MultipleBoundParameters: 166 | def m[S: int, T: int](self: S, other: T) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 167 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... | = help: Replace TypeVar `S` with `Self` -PYI019_0.pyi:167:63: PYI019 Use `Self` instead of custom TypeVar `S` +ℹ Safe fix +163 163 | def n[S](cls: type[S], other: S): ... +164 164 | +165 165 | class MultipleBoundParameters: +166 |- def m[S: int, T: int](self: S, other: T) -> S: ... + 166 |+ def m[T: int](self, other: T) -> Self: ... +167 167 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... +168 168 | +169 169 | + +PYI019_0.pyi:167:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 165 | class MultipleBoundParameters: 166 | def m[S: int, T: int](self: S, other: T) -> S: ... 167 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +164 164 | +165 165 | class MultipleBoundParameters: +166 166 | def m[S: int, T: int](self: S, other: T) -> S: ... +167 |- def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... + 167 |+ def n[T: (int, str)](self, other: T) -> Self: ... +168 168 | +169 169 | +170 170 | MetaType = TypeVar("MetaType") diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_1.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_1.pyi.snap index 0e29a0c93f36e0..00a0b3b3553269 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_1.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_1.pyi.snap @@ -1,10 +1,17 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI019_1.pyi:4:26: PYI019 Use `Self` instead of custom TypeVar `S` +PYI019_1.pyi:4:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` | 3 | class F: 4 | def m[S](self: S) -> S: ... - | ^ PYI019 + | ^^^^^^^^^^^^^^^^^ PYI019 | = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +1 1 | import typing +2 2 | +3 3 | class F: +4 |- def m[S](self: S) -> S: ... + 4 |+ def m(self) -> typing.Self: ... diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview_PYI019_PYI019_0.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview_PYI019_PYI019_0.py.snap deleted file mode 100644 index 1bbfbef8e2c361..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview_PYI019_PYI019_0.py.snap +++ /dev/null @@ -1,692 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs ---- -PYI019_0.py:7:16: PYI019 [*] Use `Self` instead of custom TypeVar `_S` - | -6 | class BadClass: -7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `_S` with `Self` - -ℹ Safe fix -4 4 | _S2 = TypeVar("_S2", BadClass, GoodClass) -5 5 | -6 6 | class BadClass: -7 |- def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 - 7 |+ def __new__(cls, *args: str, **kwargs: int) -> Self: ... # PYI019 -8 8 | -9 9 | -10 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 - -PYI019_0.py:10:28: PYI019 [*] Use `Self` instead of custom TypeVar `_S` - | -10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `_S` with `Self` - -ℹ Safe fix -7 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 -8 8 | -9 9 | -10 |- def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 - 10 |+ def bad_instance_method(self, arg: bytes) -> Self: ... # PYI019 -11 11 | -12 12 | -13 13 | @classmethod - -PYI019_0.py:14:25: PYI019 [*] Use `Self` instead of custom TypeVar `_S` - | -13 | @classmethod -14 | def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `_S` with `Self` - -ℹ Safe fix -11 11 | -12 12 | -13 13 | @classmethod -14 |- def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 - 14 |+ def bad_class_method(cls, arg: int) -> Self: ... # PYI019 -15 15 | -16 16 | -17 17 | @classmethod - -PYI019_0.py:18:33: PYI019 [*] Use `Self` instead of custom TypeVar `_S` - | -17 | @classmethod -18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `_S` with `Self` - -ℹ Safe fix -15 15 | -16 16 | -17 17 | @classmethod -18 |- def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 - 18 |+ def bad_posonly_class_method(cls, /) -> Self: ... # PYI019 -19 19 | -20 20 | -21 21 | @classmethod - -PYI019_0.py:39:14: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -37 | # Python > 3.12 -38 | class PEP695BadDunderNew[T]: -39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -36 36 | -37 37 | # Python > 3.12 -38 38 | class PEP695BadDunderNew[T]: -39 |- def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 - 39 |+ def __new__(cls, *args: Any, ** kwargs: Any) -> Self: ... # PYI019 -40 40 | -41 41 | -42 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 - -PYI019_0.py:42:30: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -39 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 -40 40 | -41 41 | -42 |- def generic_instance_method[S](self: S) -> S: ... # PYI019 - 42 |+ def generic_instance_method(self) -> Self: ... # PYI019 -43 43 | -44 44 | -45 45 | class PEP695GoodDunderNew[T]: - -PYI019_0.py:54:11: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -52 | # in the settings for this test: -53 | @foo_classmethod -54 | def foo[S](cls: type[S]) -> S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -51 51 | # due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators` -52 52 | # in the settings for this test: -53 53 | @foo_classmethod -54 |- def foo[S](cls: type[S]) -> S: ... # PYI019 - 54 |+ def foo(cls) -> Self: ... # PYI019 -55 55 | -56 56 | -57 57 | _S695 = TypeVar("_S695", bound="PEP695Fix") - -PYI019_0.py:61:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -60 | class PEP695Fix: -61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -62 | -63 | def __init_subclass__[S](cls: type[S]) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -58 58 | -59 59 | -60 60 | class PEP695Fix: -61 |- def __new__[S: PEP695Fix](cls: type[S]) -> S: ... - 61 |+ def __new__(cls) -> Self: ... -62 62 | -63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... -64 64 | - -PYI019_0.py:63:26: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... -62 | -63 | def __init_subclass__[S](cls: type[S]) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 -64 | -65 | def __neg__[S: PEP695Fix](self: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -60 60 | class PEP695Fix: -61 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... -62 62 | -63 |- def __init_subclass__[S](cls: type[S]) -> S: ... - 63 |+ def __init_subclass__(cls) -> Self: ... -64 64 | -65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... -66 66 | - -PYI019_0.py:65:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -63 | def __init_subclass__[S](cls: type[S]) -> S: ... -64 | -65 | def __neg__[S: PEP695Fix](self: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -66 | -67 | def __pos__[S](self: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -62 62 | -63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... -64 64 | -65 |- def __neg__[S: PEP695Fix](self: S) -> S: ... - 65 |+ def __neg__(self) -> Self: ... -66 66 | -67 67 | def __pos__[S](self: S) -> S: ... -68 68 | - -PYI019_0.py:67:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -65 | def __neg__[S: PEP695Fix](self: S) -> S: ... -66 | -67 | def __pos__[S](self: S) -> S: ... - | ^^^^^^^^^^^^^^^^^ PYI019 -68 | -69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -64 64 | -65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... -66 66 | -67 |- def __pos__[S](self: S) -> S: ... - 67 |+ def __pos__(self) -> Self: ... -68 68 | -69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... -70 70 | - -PYI019_0.py:69:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -67 | def __pos__[S](self: S) -> S: ... -68 | -69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -70 | -71 | def __sub__[S](self: S, other: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -66 66 | -67 67 | def __pos__[S](self: S) -> S: ... -68 68 | -69 |- def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - 69 |+ def __add__(self, other: Self) -> Self: ... -70 70 | -71 71 | def __sub__[S](self: S, other: S) -> S: ... -72 72 | - -PYI019_0.py:71:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... -70 | -71 | def __sub__[S](self: S, other: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -72 | -73 | @classmethod - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -68 68 | -69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... -70 70 | -71 |- def __sub__[S](self: S, other: S) -> S: ... - 71 |+ def __sub__(self, other: Self) -> Self: ... -72 72 | -73 73 | @classmethod -74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - -PYI019_0.py:74:27: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -73 | @classmethod -74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -75 | -76 | @classmethod - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -71 71 | def __sub__[S](self: S, other: S) -> S: ... -72 72 | -73 73 | @classmethod -74 |- def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - 74 |+ def class_method_bound(cls) -> Self: ... -75 75 | -76 76 | @classmethod -77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... - -PYI019_0.py:77:29: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -76 | @classmethod -77 | def class_method_unbound[S](cls: type[S]) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 -78 | -79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... -75 75 | -76 76 | @classmethod -77 |- def class_method_unbound[S](cls: type[S]) -> S: ... - 77 |+ def class_method_unbound(cls) -> Self: ... -78 78 | -79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... -80 80 | - -PYI019_0.py:79:30: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -77 | def class_method_unbound[S](cls: type[S]) -> S: ... -78 | -79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -80 | -81 | def instance_method_unbound[S](self: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -76 76 | @classmethod -77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... -78 78 | -79 |- def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - 79 |+ def instance_method_bound(self) -> Self: ... -80 80 | -81 81 | def instance_method_unbound[S](self: S) -> S: ... -82 82 | - -PYI019_0.py:81:32: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... -80 | -81 | def instance_method_unbound[S](self: S) -> S: ... - | ^^^^^^^^^^^^^^^^^ PYI019 -82 | -83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -78 78 | -79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... -80 80 | -81 |- def instance_method_unbound[S](self: S) -> S: ... - 81 |+ def instance_method_unbound(self) -> Self: ... -82 82 | -83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... -84 84 | - -PYI019_0.py:83:53: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -81 | def instance_method_unbound[S](self: S) -> S: ... -82 | -83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -84 | -85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -80 80 | -81 81 | def instance_method_unbound[S](self: S) -> S: ... -82 82 | -83 |- def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - 83 |+ def instance_method_bound_with_another_parameter(self, other: Self) -> Self: ... -84 84 | -85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... -86 86 | - -PYI019_0.py:85:55: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... -84 | -85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -86 | -87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -82 82 | -83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... -84 84 | -85 |- def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - 85 |+ def instance_method_unbound_with_another_parameter(self, other: Self) -> Self: ... -86 86 | -87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... -88 88 | - -PYI019_0.py:87:27: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... -86 | -87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -88 | -89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -84 84 | -85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... -86 86 | -87 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - 87 |+ def multiple_type_vars[*Ts, T](self, other: Self, /, *args: *Ts, a: T, b: list[T]) -> Self: ... -88 88 | -89 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... -90 90 | - -PYI019_0.py:89:43: PYI019 [*] Use `Self` instead of custom TypeVar `_S695` - | -87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... -88 | -89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `_S695` with `Self` - -ℹ Safe fix -86 86 | -87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... -88 88 | -89 |- def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... - 89 |+ def mixing_old_and_new_style_type_vars[T](self, a: T, b: T) -> Self: ... -90 90 | -91 91 | -92 92 | class InvalidButWeDoNotPanic: - -PYI019_0.py:94:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -92 | class InvalidButWeDoNotPanic: -93 | @classmethod -94 | def m[S](cls: type[S], /) -> S[int]: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -95 | def n(self: S) -> S[int]: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -91 91 | -92 92 | class InvalidButWeDoNotPanic: -93 93 | @classmethod -94 |- def m[S](cls: type[S], /) -> S[int]: ... - 94 |+ def m(cls, /) -> Self[int]: ... -95 95 | def n(self: S) -> S[int]: ... -96 96 | -97 97 | - -PYI019_0.py:114:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -112 | class SubscriptReturnType: -113 | @classmethod -114 | def m[S](cls: type[S]) -> type[S]: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -111 111 | -112 112 | class SubscriptReturnType: -113 113 | @classmethod -114 |- def m[S](cls: type[S]) -> type[S]: ... # PYI019 - 114 |+ def m(cls) -> type[Self]: ... # PYI019 -115 115 | -116 116 | -117 117 | class SelfNotUsedInReturnAnnotation: - -PYI019_0.py:118:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -117 | class SelfNotUsedInReturnAnnotation: -118 | def m[S](self: S, other: S) -> int: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -119 | @classmethod -120 | def n[S](cls: type[S], other: S) -> int: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -115 115 | -116 116 | -117 117 | class SelfNotUsedInReturnAnnotation: -118 |- def m[S](self: S, other: S) -> int: ... - 118 |+ def m(self, other: Self) -> int: ... -119 119 | @classmethod -120 120 | def n[S](cls: type[S], other: S) -> int: ... -121 121 | - -PYI019_0.py:120:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -118 | def m[S](self: S, other: S) -> int: ... -119 | @classmethod -120 | def n[S](cls: type[S], other: S) -> int: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -117 117 | class SelfNotUsedInReturnAnnotation: -118 118 | def m[S](self: S, other: S) -> int: ... -119 119 | @classmethod -120 |- def n[S](cls: type[S], other: S) -> int: ... - 120 |+ def n(cls, other: Self) -> int: ... -121 121 | -122 122 | -123 123 | class _NotATypeVar: ... - -PYI019_0.py:135:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -134 | class NoReturnAnnotations: -135 | def m[S](self: S, other: S): ... - | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 -136 | @classmethod -137 | def n[S](cls: type[S], other: S): ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -132 132 | -133 133 | -134 134 | class NoReturnAnnotations: -135 |- def m[S](self: S, other: S): ... - 135 |+ def m(self, other: Self): ... -136 136 | @classmethod -137 137 | def n[S](cls: type[S], other: S): ... -138 138 | - -PYI019_0.py:137:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -135 | def m[S](self: S, other: S): ... -136 | @classmethod -137 | def n[S](cls: type[S], other: S): ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -138 | -139 | class MultipleBoundParameters: - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -134 134 | class NoReturnAnnotations: -135 135 | def m[S](self: S, other: S): ... -136 136 | @classmethod -137 |- def n[S](cls: type[S], other: S): ... - 137 |+ def n(cls, other: Self): ... -138 138 | -139 139 | class MultipleBoundParameters: -140 140 | def m[S: int, T: int](self: S, other: T) -> S: ... - -PYI019_0.py:140:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -139 | class MultipleBoundParameters: -140 | def m[S: int, T: int](self: S, other: T) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -141 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -137 137 | def n[S](cls: type[S], other: S): ... -138 138 | -139 139 | class MultipleBoundParameters: -140 |- def m[S: int, T: int](self: S, other: T) -> S: ... - 140 |+ def m[T: int](self, other: T) -> Self: ... -141 141 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... -142 142 | -143 143 | class MethodsWithBody: - -PYI019_0.py:141:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -139 | class MultipleBoundParameters: -140 | def m[S: int, T: int](self: S, other: T) -> S: ... -141 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -142 | -143 | class MethodsWithBody: - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -138 138 | -139 139 | class MultipleBoundParameters: -140 140 | def m[S: int, T: int](self: S, other: T) -> S: ... -141 |- def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... - 141 |+ def n[T: (int, str)](self, other: T) -> Self: ... -142 142 | -143 143 | class MethodsWithBody: -144 144 | def m[S](self: S, other: S) -> S: - -PYI019_0.py:144:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -143 | class MethodsWithBody: -144 | def m[S](self: S, other: S) -> S: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -145 | x: S = other -146 | return x - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -141 141 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... -142 142 | -143 143 | class MethodsWithBody: -144 |- def m[S](self: S, other: S) -> S: -145 |- x: S = other - 144 |+ def m(self, other: Self) -> Self: - 145 |+ x: Self = other -146 146 | return x -147 147 | -148 148 | @classmethod - -PYI019_0.py:149:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -148 | @classmethod -149 | def n[S](cls: type[S], other: S) -> S: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -150 | x: type[S] = type(other) -151 | return x() - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -146 146 | return x -147 147 | -148 148 | @classmethod -149 |- def n[S](cls: type[S], other: S) -> S: -150 |- x: type[S] = type(other) - 149 |+ def n(cls, other: Self) -> Self: - 150 |+ x: type[Self] = type(other) -151 151 | return x() -152 152 | -153 153 | class StringizedReferencesCanBeFixed: - -PYI019_0.py:154:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -153 | class StringizedReferencesCanBeFixed: -154 | def m[S](self: S) -> S: - | ^^^^^^^^^^^^^^^^^ PYI019 -155 | x = cast("list[tuple[S, S]]", self) -156 | return x - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -151 151 | return x() -152 152 | -153 153 | class StringizedReferencesCanBeFixed: -154 |- def m[S](self: S) -> S: -155 |- x = cast("list[tuple[S, S]]", self) - 154 |+ def m(self) -> Self: - 155 |+ x = cast("list[tuple[Self, Self]]", self) -156 156 | return x -157 157 | -158 158 | class ButStrangeStringizedReferencesCannotBeFixed: - -PYI019_0.py:159:10: PYI019 Use `Self` instead of custom TypeVar `_T` - | -158 | class ButStrangeStringizedReferencesCannotBeFixed: -159 | def m[_T](self: _T) -> _T: - | ^^^^^^^^^^^^^^^^^^^^ PYI019 -160 | x = cast('list[_\x54]', self) -161 | return x - | - = help: Replace TypeVar `_T` with `Self` - -PYI019_0.py:164:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -163 | class DeletionsAreNotTouched: -164 | def m[S](self: S) -> S: - | ^^^^^^^^^^^^^^^^^ PYI019 -165 | # `S` is not a local variable here, and `del` can only be used with local variables, -166 | # so `del S` here is not actually a reference to the type variable `S`. - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -161 161 | return x -162 162 | -163 163 | class DeletionsAreNotTouched: -164 |- def m[S](self: S) -> S: - 164 |+ def m(self) -> Self: -165 165 | # `S` is not a local variable here, and `del` can only be used with local variables, -166 166 | # so `del S` here is not actually a reference to the type variable `S`. -167 167 | # This `del` statement is therefore not touched by the autofix (it raises `UnboundLocalError` - -PYI019_0.py:173:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -172 | class NamesShadowingTypeVarAreNotTouched: -173 | def m[S](self: S) -> S: - | ^^^^^^^^^^^^^^^^^ PYI019 -174 | type S = int -175 | print(S) # not a reference to the type variable, so not touched by the autofix - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -170 170 | return self -171 171 | -172 172 | class NamesShadowingTypeVarAreNotTouched: -173 |- def m[S](self: S) -> S: - 173 |+ def m(self) -> Self: -174 174 | type S = int -175 175 | print(S) # not a reference to the type variable, so not touched by the autofix -176 176 | return 42 diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview_PYI019_PYI019_0.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview_PYI019_PYI019_0.pyi.snap deleted file mode 100644 index 7c5b794bd56218..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview_PYI019_PYI019_0.pyi.snap +++ /dev/null @@ -1,684 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs ---- -PYI019_0.pyi:7:16: PYI019 [*] Use `Self` instead of custom TypeVar `_S` - | -6 | class BadClass: -7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `_S` with `Self` - -ℹ Safe fix -4 4 | _S2 = TypeVar("_S2", BadClass, GoodClass) -5 5 | -6 6 | class BadClass: -7 |- def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 - 7 |+ def __new__(cls, *args: str, **kwargs: int) -> Self: ... # PYI019 -8 8 | -9 9 | -10 10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 - -PYI019_0.pyi:10:28: PYI019 [*] Use `Self` instead of custom TypeVar `_S` - | -10 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `_S` with `Self` - -ℹ Safe fix -7 7 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019 -8 8 | -9 9 | -10 |- def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019 - 10 |+ def bad_instance_method(self, arg: bytes) -> Self: ... # PYI019 -11 11 | -12 12 | -13 13 | @classmethod - -PYI019_0.pyi:14:25: PYI019 [*] Use `Self` instead of custom TypeVar `_S` - | -13 | @classmethod -14 | def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `_S` with `Self` - -ℹ Safe fix -11 11 | -12 12 | -13 13 | @classmethod -14 |- def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019 - 14 |+ def bad_class_method(cls, arg: int) -> Self: ... # PYI019 -15 15 | -16 16 | -17 17 | @classmethod - -PYI019_0.pyi:18:33: PYI019 [*] Use `Self` instead of custom TypeVar `_S` - | -17 | @classmethod -18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `_S` with `Self` - -ℹ Safe fix -15 15 | -16 16 | -17 17 | @classmethod -18 |- def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019 - 18 |+ def bad_posonly_class_method(cls, /) -> Self: ... # PYI019 -19 19 | -20 20 | -21 21 | @classmethod - -PYI019_0.pyi:39:14: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -37 | # Python > 3.12 -38 | class PEP695BadDunderNew[T]: -39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -36 36 | -37 37 | # Python > 3.12 -38 38 | class PEP695BadDunderNew[T]: -39 |- def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 - 39 |+ def __new__(cls, *args: Any, ** kwargs: Any) -> Self: ... # PYI019 -40 40 | -41 41 | -42 42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 - -PYI019_0.pyi:42:30: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -42 | def generic_instance_method[S](self: S) -> S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -39 39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019 -40 40 | -41 41 | -42 |- def generic_instance_method[S](self: S) -> S: ... # PYI019 - 42 |+ def generic_instance_method(self) -> Self: ... # PYI019 -43 43 | -44 44 | -45 45 | class PEP695GoodDunderNew[T]: - -PYI019_0.pyi:54:11: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -52 | # in the settings for this test: -53 | @foo_classmethod -54 | def foo[S](cls: type[S]) -> S: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -51 51 | # due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators` -52 52 | # in the settings for this test: -53 53 | @foo_classmethod -54 |- def foo[S](cls: type[S]) -> S: ... # PYI019 - 54 |+ def foo(cls) -> Self: ... # PYI019 -55 55 | -56 56 | -57 57 | _S695 = TypeVar("_S695", bound="PEP695Fix") - -PYI019_0.pyi:61:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -60 | class PEP695Fix: -61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -62 | -63 | def __init_subclass__[S](cls: type[S]) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -58 58 | -59 59 | -60 60 | class PEP695Fix: -61 |- def __new__[S: PEP695Fix](cls: type[S]) -> S: ... - 61 |+ def __new__(cls) -> Self: ... -62 62 | -63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... -64 64 | - -PYI019_0.pyi:63:26: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... -62 | -63 | def __init_subclass__[S](cls: type[S]) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 -64 | -65 | def __neg__[S: PEP695Fix](self: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -60 60 | class PEP695Fix: -61 61 | def __new__[S: PEP695Fix](cls: type[S]) -> S: ... -62 62 | -63 |- def __init_subclass__[S](cls: type[S]) -> S: ... - 63 |+ def __init_subclass__(cls) -> Self: ... -64 64 | -65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... -66 66 | - -PYI019_0.pyi:65:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -63 | def __init_subclass__[S](cls: type[S]) -> S: ... -64 | -65 | def __neg__[S: PEP695Fix](self: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -66 | -67 | def __pos__[S](self: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -62 62 | -63 63 | def __init_subclass__[S](cls: type[S]) -> S: ... -64 64 | -65 |- def __neg__[S: PEP695Fix](self: S) -> S: ... - 65 |+ def __neg__(self) -> Self: ... -66 66 | -67 67 | def __pos__[S](self: S) -> S: ... -68 68 | - -PYI019_0.pyi:67:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -65 | def __neg__[S: PEP695Fix](self: S) -> S: ... -66 | -67 | def __pos__[S](self: S) -> S: ... - | ^^^^^^^^^^^^^^^^^ PYI019 -68 | -69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -64 64 | -65 65 | def __neg__[S: PEP695Fix](self: S) -> S: ... -66 66 | -67 |- def __pos__[S](self: S) -> S: ... - 67 |+ def __pos__(self) -> Self: ... -68 68 | -69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... -70 70 | - -PYI019_0.pyi:69:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -67 | def __pos__[S](self: S) -> S: ... -68 | -69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -70 | -71 | def __sub__[S](self: S, other: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -66 66 | -67 67 | def __pos__[S](self: S) -> S: ... -68 68 | -69 |- def __add__[S: PEP695Fix](self: S, other: S) -> S: ... - 69 |+ def __add__(self, other: Self) -> Self: ... -70 70 | -71 71 | def __sub__[S](self: S, other: S) -> S: ... -72 72 | - -PYI019_0.pyi:71:16: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... -70 | -71 | def __sub__[S](self: S, other: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -72 | -73 | @classmethod - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -68 68 | -69 69 | def __add__[S: PEP695Fix](self: S, other: S) -> S: ... -70 70 | -71 |- def __sub__[S](self: S, other: S) -> S: ... - 71 |+ def __sub__(self, other: Self) -> Self: ... -72 72 | -73 73 | @classmethod -74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - -PYI019_0.pyi:74:27: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -73 | @classmethod -74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -75 | -76 | @classmethod - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -71 71 | def __sub__[S](self: S, other: S) -> S: ... -72 72 | -73 73 | @classmethod -74 |- def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... - 74 |+ def class_method_bound(cls) -> Self: ... -75 75 | -76 76 | @classmethod -77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... - -PYI019_0.pyi:77:29: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -76 | @classmethod -77 | def class_method_unbound[S](cls: type[S]) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 -78 | -79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -74 74 | def class_method_bound[S: PEP695Fix](cls: type[S]) -> S: ... -75 75 | -76 76 | @classmethod -77 |- def class_method_unbound[S](cls: type[S]) -> S: ... - 77 |+ def class_method_unbound(cls) -> Self: ... -78 78 | -79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... -80 80 | - -PYI019_0.pyi:79:30: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -77 | def class_method_unbound[S](cls: type[S]) -> S: ... -78 | -79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -80 | -81 | def instance_method_unbound[S](self: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -76 76 | @classmethod -77 77 | def class_method_unbound[S](cls: type[S]) -> S: ... -78 78 | -79 |- def instance_method_bound[S: PEP695Fix](self: S) -> S: ... - 79 |+ def instance_method_bound(self) -> Self: ... -80 80 | -81 81 | def instance_method_unbound[S](self: S) -> S: ... -82 82 | - -PYI019_0.pyi:81:32: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... -80 | -81 | def instance_method_unbound[S](self: S) -> S: ... - | ^^^^^^^^^^^^^^^^^ PYI019 -82 | -83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -78 78 | -79 79 | def instance_method_bound[S: PEP695Fix](self: S) -> S: ... -80 80 | -81 |- def instance_method_unbound[S](self: S) -> S: ... - 81 |+ def instance_method_unbound(self) -> Self: ... -82 82 | -83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... -84 84 | - -PYI019_0.pyi:83:53: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -81 | def instance_method_unbound[S](self: S) -> S: ... -82 | -83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -84 | -85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -80 80 | -81 81 | def instance_method_unbound[S](self: S) -> S: ... -82 82 | -83 |- def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... - 83 |+ def instance_method_bound_with_another_parameter(self, other: Self) -> Self: ... -84 84 | -85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... -86 86 | - -PYI019_0.pyi:85:55: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... -84 | -85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -86 | -87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -82 82 | -83 83 | def instance_method_bound_with_another_parameter[S: PEP695Fix](self: S, other: S) -> S: ... -84 84 | -85 |- def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... - 85 |+ def instance_method_unbound_with_another_parameter(self, other: Self) -> Self: ... -86 86 | -87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... -88 88 | - -PYI019_0.pyi:87:27: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... -86 | -87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -88 | -89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -84 84 | -85 85 | def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: ... -86 86 | -87 |- def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... - 87 |+ def multiple_type_vars[*Ts, T](self, other: Self, /, *args: *Ts, a: T, b: list[T]) -> Self: ... -88 88 | -89 89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... -90 90 | - -PYI019_0.pyi:89:43: PYI019 [*] Use `Self` instead of custom TypeVar `_S695` - | -87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... -88 | -89 | def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `_S695` with `Self` - -ℹ Safe fix -86 86 | -87 87 | def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ... -88 88 | -89 |- def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ... - 89 |+ def mixing_old_and_new_style_type_vars[T](self, a: T, b: T) -> Self: ... -90 90 | -91 91 | -92 92 | class InvalidButWeDoNotPanic: - -PYI019_0.pyi:94:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -92 | class InvalidButWeDoNotPanic: -93 | @classmethod -94 | def m[S](cls: type[S], /) -> S[int]: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -95 | def n(self: S) -> S[int]: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -91 91 | -92 92 | class InvalidButWeDoNotPanic: -93 93 | @classmethod -94 |- def m[S](cls: type[S], /) -> S[int]: ... - 94 |+ def m(cls, /) -> Self[int]: ... -95 95 | def n(self: S) -> S[int]: ... -96 96 | -97 97 | - -PYI019_0.pyi:114:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -112 | class SubscriptReturnType: -113 | @classmethod -114 | def m[S](cls: type[S]) -> type[S]: ... # PYI019 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -111 111 | -112 112 | class SubscriptReturnType: -113 113 | @classmethod -114 |- def m[S](cls: type[S]) -> type[S]: ... # PYI019 - 114 |+ def m(cls) -> type[Self]: ... # PYI019 -115 115 | -116 116 | -117 117 | class PEP695TypeParameterAtTheVeryEndOfTheList: - -PYI019_0.pyi:118:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -117 | class PEP695TypeParameterAtTheVeryEndOfTheList: -118 | def f[T, S](self: S) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -115 115 | -116 116 | -117 117 | class PEP695TypeParameterAtTheVeryEndOfTheList: -118 |- def f[T, S](self: S) -> S: ... - 118 |+ def f[T](self) -> Self: ... -119 119 | -120 120 | -121 121 | class PEP695Again: - -PYI019_0.pyi:122:26: PYI019 [*] Use `Self` instead of custom TypeVar `_S695` - | -121 | class PEP695Again: -122 | def mixing_and_nested[T](self: _S695, a: list[_S695], b: dict[_S695, str | T | set[_S695]]) -> _S695: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -123 | def also_uses_s695_but_should_not_be_edited(self, v: set[tuple[_S695]]) -> _S695: ... - | - = help: Replace TypeVar `_S695` with `Self` - -ℹ Safe fix -119 119 | -120 120 | -121 121 | class PEP695Again: -122 |- def mixing_and_nested[T](self: _S695, a: list[_S695], b: dict[_S695, str | T | set[_S695]]) -> _S695: ... - 122 |+ def mixing_and_nested[T](self, a: list[Self], b: dict[Self, str | T | set[Self]]) -> Self: ... -123 123 | def also_uses_s695_but_should_not_be_edited(self, v: set[tuple[_S695]]) -> _S695: ... -124 124 | -125 125 | @classmethod - -PYI019_0.pyi:126:29: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -125 | @classmethod -126 | def comment_in_fix_range[T, S]( - | _____________________________^ -127 | | cls: type[ # Lorem ipsum -128 | | S -129 | | ], -130 | | a: T, -131 | | b: tuple[S, T] -132 | | ) -> S: ... - | |__________^ PYI019 -133 | -134 | def comment_outside_fix_range[T, S]( - | - = help: Replace TypeVar `S` with `Self` - -ℹ Unsafe fix -123 123 | def also_uses_s695_but_should_not_be_edited(self, v: set[tuple[_S695]]) -> _S695: ... -124 124 | -125 125 | @classmethod -126 |- def comment_in_fix_range[T, S]( -127 |- cls: type[ # Lorem ipsum -128 |- S -129 |- ], - 126 |+ def comment_in_fix_range[T]( - 127 |+ cls, -130 128 | a: T, -131 |- b: tuple[S, T] -132 |- ) -> S: ... - 129 |+ b: tuple[Self, T] - 130 |+ ) -> Self: ... -133 131 | -134 132 | def comment_outside_fix_range[T, S]( -135 133 | self: S, - -PYI019_0.pyi:134:34: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -132 | ) -> S: ... -133 | -134 | def comment_outside_fix_range[T, S]( - | __________________________________^ -135 | | self: S, -136 | | a: T, -137 | | b: tuple[ -138 | | # Lorem ipsum -139 | | S, T -140 | | ] -141 | | ) -> S: ... - | |__________^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -131 131 | b: tuple[S, T] -132 132 | ) -> S: ... -133 133 | -134 |- def comment_outside_fix_range[T, S]( -135 |- self: S, - 134 |+ def comment_outside_fix_range[T]( - 135 |+ self, -136 136 | a: T, -137 137 | b: tuple[ -138 138 | # Lorem ipsum -139 |- S, T - 139 |+ Self, T -140 140 | ] -141 |- ) -> S: ... - 141 |+ ) -> Self: ... -142 142 | -143 143 | -144 144 | class SelfNotUsedInReturnAnnotation: - -PYI019_0.pyi:145:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -144 | class SelfNotUsedInReturnAnnotation: -145 | def m[S](self: S, other: S) -> int: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -146 | @classmethod -147 | def n[S](cls: type[S], other: S) -> int: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -142 142 | -143 143 | -144 144 | class SelfNotUsedInReturnAnnotation: -145 |- def m[S](self: S, other: S) -> int: ... - 145 |+ def m(self, other: Self) -> int: ... -146 146 | @classmethod -147 147 | def n[S](cls: type[S], other: S) -> int: ... -148 148 | - -PYI019_0.pyi:147:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -145 | def m[S](self: S, other: S) -> int: ... -146 | @classmethod -147 | def n[S](cls: type[S], other: S) -> int: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -144 144 | class SelfNotUsedInReturnAnnotation: -145 145 | def m[S](self: S, other: S) -> int: ... -146 146 | @classmethod -147 |- def n[S](cls: type[S], other: S) -> int: ... - 147 |+ def n(cls, other: Self) -> int: ... -148 148 | -149 149 | -150 150 | class _NotATypeVar: ... - -PYI019_0.pyi:161:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -160 | class NoReturnAnnotations: -161 | def m[S](self: S, other: S): ... - | ^^^^^^^^^^^^^^^^^^^^^^ PYI019 -162 | @classmethod -163 | def n[S](cls: type[S], other: S): ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -158 158 | def y(self: type[_NotATypeVar]) -> _NotATypeVar: ... -159 159 | -160 160 | class NoReturnAnnotations: -161 |- def m[S](self: S, other: S): ... - 161 |+ def m(self, other: Self): ... -162 162 | @classmethod -163 163 | def n[S](cls: type[S], other: S): ... -164 164 | - -PYI019_0.pyi:163:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -161 | def m[S](self: S, other: S): ... -162 | @classmethod -163 | def n[S](cls: type[S], other: S): ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -164 | -165 | class MultipleBoundParameters: - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -160 160 | class NoReturnAnnotations: -161 161 | def m[S](self: S, other: S): ... -162 162 | @classmethod -163 |- def n[S](cls: type[S], other: S): ... - 163 |+ def n(cls, other: Self): ... -164 164 | -165 165 | class MultipleBoundParameters: -166 166 | def m[S: int, T: int](self: S, other: T) -> S: ... - -PYI019_0.pyi:166:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -165 | class MultipleBoundParameters: -166 | def m[S: int, T: int](self: S, other: T) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 -167 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -163 163 | def n[S](cls: type[S], other: S): ... -164 164 | -165 165 | class MultipleBoundParameters: -166 |- def m[S: int, T: int](self: S, other: T) -> S: ... - 166 |+ def m[T: int](self, other: T) -> Self: ... -167 167 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... -168 168 | -169 169 | - -PYI019_0.pyi:167:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -165 | class MultipleBoundParameters: -166 | def m[S: int, T: int](self: S, other: T) -> S: ... -167 | def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -164 164 | -165 165 | class MultipleBoundParameters: -166 166 | def m[S: int, T: int](self: S, other: T) -> S: ... -167 |- def n[T: (int, str), S: (int, str)](self: S, other: T) -> S: ... - 167 |+ def n[T: (int, str)](self, other: T) -> Self: ... -168 168 | -169 169 | -170 170 | MetaType = TypeVar("MetaType") diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview_PYI019_PYI019_1.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview_PYI019_PYI019_1.pyi.snap deleted file mode 100644 index 00a0b3b3553269..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview_PYI019_PYI019_1.pyi.snap +++ /dev/null @@ -1,17 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs ---- -PYI019_1.pyi:4:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` - | -3 | class F: -4 | def m[S](self: S) -> S: ... - | ^^^^^^^^^^^^^^^^^ PYI019 - | - = help: Replace TypeVar `S` with `Self` - -ℹ Safe fix -1 1 | import typing -2 2 | -3 3 | class F: -4 |- def m[S](self: S) -> S: ... - 4 |+ def m(self) -> typing.Self: ... diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI018_PYI018.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI018_PYI018.py.snap deleted file mode 100644 index 19ab98788364a5..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI018_PYI018.py.snap +++ /dev/null @@ -1,100 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs ---- -PYI018.py:6:1: PYI018 [*] Private TypeVar `_T` is never used - | -4 | from typing_extensions import ParamSpec, TypeVarTuple -5 | -6 | _T = typing.TypeVar("_T") - | ^^ PYI018 -7 | _Ts = typing_extensions.TypeVarTuple("_Ts") -8 | _P = ParamSpec("_P") - | - = help: Remove unused private TypeVar `_T` - -ℹ Unsafe fix -3 3 | from typing import TypeVar -4 4 | from typing_extensions import ParamSpec, TypeVarTuple -5 5 | -6 |-_T = typing.TypeVar("_T") -7 6 | _Ts = typing_extensions.TypeVarTuple("_Ts") -8 7 | _P = ParamSpec("_P") -9 8 | _P2 = typing.ParamSpec("_P2") - -PYI018.py:7:1: PYI018 [*] Private TypeVarTuple `_Ts` is never used - | -6 | _T = typing.TypeVar("_T") -7 | _Ts = typing_extensions.TypeVarTuple("_Ts") - | ^^^ PYI018 -8 | _P = ParamSpec("_P") -9 | _P2 = typing.ParamSpec("_P2") - | - = help: Remove unused private TypeVarTuple `_Ts` - -ℹ Unsafe fix -4 4 | from typing_extensions import ParamSpec, TypeVarTuple -5 5 | -6 6 | _T = typing.TypeVar("_T") -7 |-_Ts = typing_extensions.TypeVarTuple("_Ts") -8 7 | _P = ParamSpec("_P") -9 8 | _P2 = typing.ParamSpec("_P2") -10 9 | _Ts2 = TypeVarTuple("_Ts2") - -PYI018.py:8:1: PYI018 [*] Private ParamSpec `_P` is never used - | - 6 | _T = typing.TypeVar("_T") - 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") - 8 | _P = ParamSpec("_P") - | ^^ PYI018 - 9 | _P2 = typing.ParamSpec("_P2") -10 | _Ts2 = TypeVarTuple("_Ts2") - | - = help: Remove unused private ParamSpec `_P` - -ℹ Unsafe fix -5 5 | -6 6 | _T = typing.TypeVar("_T") -7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") -8 |-_P = ParamSpec("_P") -9 8 | _P2 = typing.ParamSpec("_P2") -10 9 | _Ts2 = TypeVarTuple("_Ts2") -11 10 | - -PYI018.py:9:1: PYI018 [*] Private ParamSpec `_P2` is never used - | - 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") - 8 | _P = ParamSpec("_P") - 9 | _P2 = typing.ParamSpec("_P2") - | ^^^ PYI018 -10 | _Ts2 = TypeVarTuple("_Ts2") - | - = help: Remove unused private ParamSpec `_P2` - -ℹ Unsafe fix -6 6 | _T = typing.TypeVar("_T") -7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") -8 8 | _P = ParamSpec("_P") -9 |-_P2 = typing.ParamSpec("_P2") -10 9 | _Ts2 = TypeVarTuple("_Ts2") -11 10 | -12 11 | # OK - -PYI018.py:10:1: PYI018 [*] Private TypeVarTuple `_Ts2` is never used - | - 8 | _P = ParamSpec("_P") - 9 | _P2 = typing.ParamSpec("_P2") -10 | _Ts2 = TypeVarTuple("_Ts2") - | ^^^^ PYI018 -11 | -12 | # OK - | - = help: Remove unused private TypeVarTuple `_Ts2` - -ℹ Unsafe fix -7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") -8 8 | _P = ParamSpec("_P") -9 9 | _P2 = typing.ParamSpec("_P2") -10 |-_Ts2 = TypeVarTuple("_Ts2") -11 10 | -12 11 | # OK -13 12 | _UsedTypeVar = TypeVar("_UsedTypeVar") diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI018_PYI018.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI018_PYI018.pyi.snap deleted file mode 100644 index a7913fbfa888e8..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI018_PYI018.pyi.snap +++ /dev/null @@ -1,100 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs ---- -PYI018.pyi:6:1: PYI018 [*] Private TypeVar `_T` is never used - | -4 | from typing_extensions import ParamSpec, TypeVarTuple -5 | -6 | _T = typing.TypeVar("_T") - | ^^ PYI018 -7 | _Ts = typing_extensions.TypeVarTuple("_Ts") -8 | _P = ParamSpec("_P") - | - = help: Remove unused private TypeVar `_T` - -ℹ Unsafe fix -3 3 | from typing import TypeVar -4 4 | from typing_extensions import ParamSpec, TypeVarTuple -5 5 | -6 |-_T = typing.TypeVar("_T") -7 6 | _Ts = typing_extensions.TypeVarTuple("_Ts") -8 7 | _P = ParamSpec("_P") -9 8 | _P2 = typing.ParamSpec("_P2") - -PYI018.pyi:7:1: PYI018 [*] Private TypeVarTuple `_Ts` is never used - | -6 | _T = typing.TypeVar("_T") -7 | _Ts = typing_extensions.TypeVarTuple("_Ts") - | ^^^ PYI018 -8 | _P = ParamSpec("_P") -9 | _P2 = typing.ParamSpec("_P2") - | - = help: Remove unused private TypeVarTuple `_Ts` - -ℹ Unsafe fix -4 4 | from typing_extensions import ParamSpec, TypeVarTuple -5 5 | -6 6 | _T = typing.TypeVar("_T") -7 |-_Ts = typing_extensions.TypeVarTuple("_Ts") -8 7 | _P = ParamSpec("_P") -9 8 | _P2 = typing.ParamSpec("_P2") -10 9 | _Ts2 = TypeVarTuple("_Ts2") - -PYI018.pyi:8:1: PYI018 [*] Private ParamSpec `_P` is never used - | - 6 | _T = typing.TypeVar("_T") - 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") - 8 | _P = ParamSpec("_P") - | ^^ PYI018 - 9 | _P2 = typing.ParamSpec("_P2") -10 | _Ts2 = TypeVarTuple("_Ts2") - | - = help: Remove unused private ParamSpec `_P` - -ℹ Unsafe fix -5 5 | -6 6 | _T = typing.TypeVar("_T") -7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") -8 |-_P = ParamSpec("_P") -9 8 | _P2 = typing.ParamSpec("_P2") -10 9 | _Ts2 = TypeVarTuple("_Ts2") -11 10 | - -PYI018.pyi:9:1: PYI018 [*] Private ParamSpec `_P2` is never used - | - 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") - 8 | _P = ParamSpec("_P") - 9 | _P2 = typing.ParamSpec("_P2") - | ^^^ PYI018 -10 | _Ts2 = TypeVarTuple("_Ts2") - | - = help: Remove unused private ParamSpec `_P2` - -ℹ Unsafe fix -6 6 | _T = typing.TypeVar("_T") -7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") -8 8 | _P = ParamSpec("_P") -9 |-_P2 = typing.ParamSpec("_P2") -10 9 | _Ts2 = TypeVarTuple("_Ts2") -11 10 | -12 11 | # OK - -PYI018.pyi:10:1: PYI018 [*] Private TypeVarTuple `_Ts2` is never used - | - 8 | _P = ParamSpec("_P") - 9 | _P2 = typing.ParamSpec("_P2") -10 | _Ts2 = TypeVarTuple("_Ts2") - | ^^^^ PYI018 -11 | -12 | # OK - | - = help: Remove unused private TypeVarTuple `_Ts2` - -ℹ Unsafe fix -7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts") -8 8 | _P = ParamSpec("_P") -9 9 | _P2 = typing.ParamSpec("_P2") -10 |-_Ts2 = TypeVarTuple("_Ts2") -11 10 | -12 11 | # OK -13 12 | _UsedTypeVar = TypeVar("_UsedTypeVar") diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs index dca6e3ced4486c..7cb3486d0141d6 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs @@ -11,7 +11,7 @@ mod tests { use test_case::test_case; use crate::registry::Rule; - use crate::settings::types::{IdentifierPattern, PreviewMode}; + use crate::settings::types::IdentifierPattern; use crate::test::test_path; use crate::{assert_messages, settings}; @@ -358,36 +358,6 @@ mod tests { Ok(()) } - #[test_case( - Rule::PytestRaisesWithMultipleStatements, - Path::new("PT012.py"), - Settings::default(), - "PT012_preview" - )] - #[test_case( - Rule::PytestWarnsWithMultipleStatements, - Path::new("PT031.py"), - Settings::default(), - "PT031_preview" - )] - fn test_pytest_style_preview( - rule_code: Rule, - path: &Path, - plugin_settings: Settings, - name: &str, - ) -> Result<()> { - let diagnostics = test_path( - Path::new("flake8_pytest_style").join(path).as_path(), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - flake8_pytest_style: plugin_settings, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(name, diagnostics); - Ok(()) - } - /// This test ensure that PT006 and PT007 don't conflict when both of them suggest a fix that /// edits `argvalues` for `pytest.mark.parametrize`. #[test] diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs index 4701b9e2fb7055..08428d3284d659 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs @@ -13,6 +13,10 @@ use super::helpers::is_empty_or_null_string; /// ## What it does /// Checks for `pytest.raises` context managers with multiple statements. /// +/// This rule allows `pytest.raises` bodies to contain `for` +/// loops with empty bodies (e.g., `pass` or `...` statements), to test +/// iterator behavior. +/// /// ## Why is this bad? /// When a `pytest.raises` is used as a context manager and contains multiple /// statements, it can lead to the test passing when it actually should fail. @@ -20,10 +24,6 @@ use super::helpers::is_empty_or_null_string; /// A `pytest.raises` context manager should only contain a single simple /// statement that raises the expected exception. /// -/// In [preview], this rule allows `pytest.raises` bodies to contain `for` -/// loops with empty bodies (e.g., `pass` or `...` statements), to test -/// iterator behavior. -/// /// ## Example /// ```python /// import pytest @@ -50,8 +50,6 @@ use super::helpers::is_empty_or_null_string; /// /// ## References /// - [`pytest` documentation: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises) -/// -/// [preview]: https://docs.astral.sh/ruff/preview/ #[derive(ViolationMetadata)] pub(crate) struct PytestRaisesWithMultipleStatements; @@ -206,14 +204,12 @@ pub(crate) fn complex_raises(checker: &Checker, stmt: &Stmt, items: &[WithItem], // Check body for `pytest.raises` context manager if raises_called { let is_too_complex = if let [stmt] = body { - let in_preview = checker.settings.preview.is_enabled(); - match stmt { Stmt::With(ast::StmtWith { body, .. }) => is_non_trivial_with_body(body), // Allow function and class definitions to test decorators. Stmt::ClassDef(_) | Stmt::FunctionDef(_) => false, // Allow empty `for` loops to test iterators. - Stmt::For(ast::StmtFor { body, .. }) if in_preview => match &body[..] { + Stmt::For(ast::StmtFor { body, .. }) => match &body[..] { [Stmt::Pass(_)] => false, [Stmt::Expr(ast::StmtExpr { value, .. })] => !value.is_ellipsis_literal_expr(), _ => true, diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs index 3b741f3af6f21d..32594a1c24e4c1 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs @@ -13,6 +13,10 @@ use super::helpers::is_empty_or_null_string; /// ## What it does /// Checks for `pytest.warns` context managers with multiple statements. /// +/// This rule allows `pytest.warns` bodies to contain `for` +/// loops with empty bodies (e.g., `pass` or `...` statements), to test +/// iterator behavior. +/// /// ## Why is this bad? /// When `pytest.warns` is used as a context manager and contains multiple /// statements, it can lead to the test passing when it should instead fail. @@ -20,9 +24,6 @@ use super::helpers::is_empty_or_null_string; /// A `pytest.warns` context manager should only contain a single /// simple statement that triggers the expected warning. /// -/// In [preview], this rule allows `pytest.warns` bodies to contain `for` -/// loops with empty bodies (e.g., `pass` or `...` statements), to test -/// iterator behavior. /// /// ## Example /// ```python @@ -48,8 +49,6 @@ use super::helpers::is_empty_or_null_string; /// /// ## References /// - [`pytest` documentation: `pytest.warns`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-warns) -/// -/// [preview]: https://docs.astral.sh/ruff/preview/ #[derive(ViolationMetadata)] pub(crate) struct PytestWarnsWithMultipleStatements; @@ -206,14 +205,12 @@ pub(crate) fn complex_warns(checker: &Checker, stmt: &Stmt, items: &[WithItem], // Check body for `pytest.warns` context manager if warns_called { let is_too_complex = if let [stmt] = body { - let in_preview = checker.settings.preview.is_enabled(); - match stmt { Stmt::With(ast::StmtWith { body, .. }) => is_non_trivial_with_body(body), // Allow function and class definitions to test decorators. Stmt::ClassDef(_) | Stmt::FunctionDef(_) => false, // Allow empty `for` loops to test iterators. - Stmt::For(ast::StmtFor { body, .. }) if in_preview => match &body[..] { + Stmt::For(ast::StmtFor { body, .. }) => match &body[..] { [Stmt::Pass(_)] => false, [Stmt::Expr(ast::StmtExpr { value, .. })] => !value.is_ellipsis_literal_expr(), _ => true, diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT012.snap b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT012.snap index 3bbaf9924fd520..43cf518d001870 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT012.snap +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT012.snap @@ -124,49 +124,3 @@ PT012.py:95:5: PT012 `pytest.raises()` block should contain a single simple stat 97 | | assert foo | |______________________^ PT012 | - -PT012.py:102:5: PT012 `pytest.raises()` block should contain a single simple statement - | -100 | ## No errors in preview -101 | -102 | / with pytest.raises(RuntimeError): -103 | | for a in b: -104 | | pass - | |________________^ PT012 -105 | -106 | with pytest.raises(RuntimeError): - | - -PT012.py:106:5: PT012 `pytest.raises()` block should contain a single simple statement - | -104 | pass -105 | -106 | / with pytest.raises(RuntimeError): -107 | | for a in b: -108 | | ... - | |_______________^ PT012 -109 | -110 | with pytest.raises(RuntimeError): - | - -PT012.py:110:5: PT012 `pytest.raises()` block should contain a single simple statement - | -108 | ... -109 | -110 | / with pytest.raises(RuntimeError): -111 | | async for a in b: -112 | | pass - | |________________^ PT012 -113 | -114 | with pytest.raises(RuntimeError): - | - -PT012.py:114:5: PT012 `pytest.raises()` block should contain a single simple statement - | -112 | pass -113 | -114 | / with pytest.raises(RuntimeError): -115 | | async for a in b: -116 | | ... - | |_______________^ PT012 - | diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT012_preview.snap b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT012_preview.snap deleted file mode 100644 index 43cf518d001870..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT012_preview.snap +++ /dev/null @@ -1,126 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs ---- -PT012.py:42:5: PT012 `pytest.raises()` block should contain a single simple statement - | -41 | def test_error_multiple_statements(): -42 | / with pytest.raises(AttributeError): -43 | | len([]) -44 | | [].size - | |_______________^ PT012 - | - -PT012.py:48:5: PT012 `pytest.raises()` block should contain a single simple statement - | -47 | async def test_error_complex_statement(): -48 | / with pytest.raises(AttributeError): -49 | | if True: -50 | | [].size - | |___________________^ PT012 -51 | -52 | with pytest.raises(AttributeError): - | - -PT012.py:52:5: PT012 `pytest.raises()` block should contain a single simple statement - | -50 | [].size -51 | -52 | / with pytest.raises(AttributeError): -53 | | for i in []: -54 | | [].size - | |___________________^ PT012 -55 | -56 | with pytest.raises(AttributeError): - | - -PT012.py:56:5: PT012 `pytest.raises()` block should contain a single simple statement - | -54 | [].size -55 | -56 | / with pytest.raises(AttributeError): -57 | | async for i in []: -58 | | [].size - | |___________________^ PT012 -59 | -60 | with pytest.raises(AttributeError): - | - -PT012.py:60:5: PT012 `pytest.raises()` block should contain a single simple statement - | -58 | [].size -59 | -60 | / with pytest.raises(AttributeError): -61 | | while True: -62 | | [].size - | |___________________^ PT012 -63 | -64 | with pytest.raises(AttributeError): - | - -PT012.py:64:5: PT012 `pytest.raises()` block should contain a single simple statement - | -62 | [].size -63 | -64 | / with pytest.raises(AttributeError): -65 | | async with context_manager_under_test(): -66 | | if True: -67 | | raise Exception - | |_______________________________^ PT012 - | - -PT012.py:71:5: PT012 `pytest.raises()` block should contain a single simple statement - | -70 | def test_error_try(): -71 | / with pytest.raises(AttributeError): -72 | | try: -73 | | [].size -74 | | except: -75 | | raise - | |_________________^ PT012 - | - -PT012.py:83:5: PT012 `pytest.raises()` block should contain a single simple statement - | -81 | ## Errors -82 | -83 | / with pytest.raises(RuntimeError): -84 | | for a in b: -85 | | print() - | |___________________^ PT012 -86 | -87 | with pytest.raises(RuntimeError): - | - -PT012.py:87:5: PT012 `pytest.raises()` block should contain a single simple statement - | -85 | print() -86 | -87 | / with pytest.raises(RuntimeError): -88 | | for a in b: -89 | | assert foo - | |______________________^ PT012 -90 | -91 | with pytest.raises(RuntimeError): - | - -PT012.py:91:5: PT012 `pytest.raises()` block should contain a single simple statement - | -89 | assert foo -90 | -91 | / with pytest.raises(RuntimeError): -92 | | async for a in b: -93 | | print() - | |___________________^ PT012 -94 | -95 | with pytest.raises(RuntimeError): - | - -PT012.py:95:5: PT012 `pytest.raises()` block should contain a single simple statement - | -93 | print() -94 | -95 | / with pytest.raises(RuntimeError): -96 | | async for a in b: -97 | | assert foo - | |______________________^ PT012 - | diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT031.snap b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT031.snap index 1df272526e43f3..ebf9e8b2fccd2b 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT031.snap +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT031.snap @@ -124,49 +124,3 @@ PT031.py:95:5: PT031 `pytest.warns()` block should contain a single simple state 97 | | assert foo | |______________________^ PT031 | - -PT031.py:102:5: PT031 `pytest.warns()` block should contain a single simple statement - | -100 | ## No errors in preview -101 | -102 | / with pytest.warns(RuntimeError): -103 | | for a in b: -104 | | pass - | |________________^ PT031 -105 | -106 | with pytest.warns(RuntimeError): - | - -PT031.py:106:5: PT031 `pytest.warns()` block should contain a single simple statement - | -104 | pass -105 | -106 | / with pytest.warns(RuntimeError): -107 | | for a in b: -108 | | ... - | |_______________^ PT031 -109 | -110 | with pytest.warns(RuntimeError): - | - -PT031.py:110:5: PT031 `pytest.warns()` block should contain a single simple statement - | -108 | ... -109 | -110 | / with pytest.warns(RuntimeError): -111 | | async for a in b: -112 | | pass - | |________________^ PT031 -113 | -114 | with pytest.warns(RuntimeError): - | - -PT031.py:114:5: PT031 `pytest.warns()` block should contain a single simple statement - | -112 | pass -113 | -114 | / with pytest.warns(RuntimeError): -115 | | async for a in b: -116 | | ... - | |_______________^ PT031 - | diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT031_preview.snap b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT031_preview.snap deleted file mode 100644 index ebf9e8b2fccd2b..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT031_preview.snap +++ /dev/null @@ -1,126 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs ---- -PT031.py:42:5: PT031 `pytest.warns()` block should contain a single simple statement - | -41 | def test_error_multiple_statements(): -42 | / with pytest.warns(UserWarning): -43 | | foo() -44 | | bar() - | |_____________^ PT031 - | - -PT031.py:48:5: PT031 `pytest.warns()` block should contain a single simple statement - | -47 | async def test_error_complex_statement(): -48 | / with pytest.warns(UserWarning): -49 | | if True: -50 | | foo() - | |_________________^ PT031 -51 | -52 | with pytest.warns(UserWarning): - | - -PT031.py:52:5: PT031 `pytest.warns()` block should contain a single simple statement - | -50 | foo() -51 | -52 | / with pytest.warns(UserWarning): -53 | | for i in []: -54 | | foo() - | |_________________^ PT031 -55 | -56 | with pytest.warns(UserWarning): - | - -PT031.py:56:5: PT031 `pytest.warns()` block should contain a single simple statement - | -54 | foo() -55 | -56 | / with pytest.warns(UserWarning): -57 | | async for i in []: -58 | | foo() - | |_________________^ PT031 -59 | -60 | with pytest.warns(UserWarning): - | - -PT031.py:60:5: PT031 `pytest.warns()` block should contain a single simple statement - | -58 | foo() -59 | -60 | / with pytest.warns(UserWarning): -61 | | while True: -62 | | foo() - | |_________________^ PT031 -63 | -64 | with pytest.warns(UserWarning): - | - -PT031.py:64:5: PT031 `pytest.warns()` block should contain a single simple statement - | -62 | foo() -63 | -64 | / with pytest.warns(UserWarning): -65 | | async with context_manager_under_test(): -66 | | if True: -67 | | foo() - | |_____________________^ PT031 - | - -PT031.py:71:5: PT031 `pytest.warns()` block should contain a single simple statement - | -70 | def test_error_try(): -71 | / with pytest.warns(UserWarning): -72 | | try: -73 | | foo() -74 | | except: -75 | | raise - | |_________________^ PT031 - | - -PT031.py:83:5: PT031 `pytest.warns()` block should contain a single simple statement - | -81 | ## Errors -82 | -83 | / with pytest.warns(RuntimeError): -84 | | for a in b: -85 | | print() - | |___________________^ PT031 -86 | -87 | with pytest.warns(RuntimeError): - | - -PT031.py:87:5: PT031 `pytest.warns()` block should contain a single simple statement - | -85 | print() -86 | -87 | / with pytest.warns(RuntimeError): -88 | | for a in b: -89 | | assert foo - | |______________________^ PT031 -90 | -91 | with pytest.warns(RuntimeError): - | - -PT031.py:91:5: PT031 `pytest.warns()` block should contain a single simple statement - | -89 | assert foo -90 | -91 | / with pytest.warns(RuntimeError): -92 | | async for a in b: -93 | | print() - | |___________________^ PT031 -94 | -95 | with pytest.warns(RuntimeError): - | - -PT031.py:95:5: PT031 `pytest.warns()` block should contain a single simple statement - | -93 | print() -94 | -95 | / with pytest.warns(RuntimeError): -96 | | async for a in b: -97 | | assert foo - | |______________________^ PT031 - | diff --git a/crates/ruff_linter/src/rules/flake8_simplify/mod.rs b/crates/ruff_linter/src/rules/flake8_simplify/mod.rs index 50274bc9aae174..6fb706df1709ea 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/mod.rs @@ -58,7 +58,6 @@ mod tests { Ok(()) } - #[test_case(Rule::NeedlessBool, Path::new("SIM103.py"))] #[test_case(Rule::IfElseBlockInsteadOfIfExp, Path::new("SIM108.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs index 091a4bcc098bd5..9b73053450e2c0 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs @@ -14,7 +14,7 @@ use crate::fix::snippet::SourceCodeSnippet; /// /// ## Why is this bad? /// `if` statements that return `True` for a truthy condition and `False` for -/// a falsey condition can be replaced with boolean casts. +/// a falsy condition can be replaced with boolean casts. /// /// ## Example /// Given: @@ -42,10 +42,6 @@ use crate::fix::snippet::SourceCodeSnippet; /// return x > 0 /// ``` /// -/// ## Preview -/// In preview, double negations such as `not a != b`, `not a not in b`, `not a is not b` -/// will be simplified to `a == b`, `a in b` and `a is b`, respectively. -/// /// ## References /// - [Python documentation: Truth Value Testing](https://docs.python.org/3/library/stdtypes.html#truth-value-testing) #[derive(ViolationMetadata)] @@ -222,16 +218,15 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) { left, comparators, .. - }) if checker.settings.preview.is_enabled() - && matches!( - ops.as_ref(), - [ast::CmpOp::Eq - | ast::CmpOp::NotEq - | ast::CmpOp::In - | ast::CmpOp::NotIn - | ast::CmpOp::Is - | ast::CmpOp::IsNot] - ) => + }) if matches!( + ops.as_ref(), + [ast::CmpOp::Eq + | ast::CmpOp::NotEq + | ast::CmpOp::In + | ast::CmpOp::NotIn + | ast::CmpOp::Is + | ast::CmpOp::IsNot] + ) => { let ([op], [right]) = (ops.as_ref(), comparators.as_ref()) else { unreachable!("Single comparison with multiple comparators"); diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs index f6af57b493a267..4d670aa7f86c38 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs @@ -39,6 +39,8 @@ use crate::checkers::ast::Checker; /// ).split(",") /// ``` /// +/// as this is converted to `["a", "b"]` without any of the comments. +/// /// ## References /// - [Python documentation: `str.split`](https://docs.python.org/3/library/stdtypes.html#str.split) #[derive(ViolationMetadata)] diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM103_SIM103.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM103_SIM103.py.snap index 471be94489d450..58286e133c3448 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM103_SIM103.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM103_SIM103.py.snap @@ -252,7 +252,7 @@ SIM103.py:123:5: SIM103 [*] Return the condition `not 10 < a` directly 127 125 | 128 126 | def f(): -SIM103.py:129:5: SIM103 [*] Return the condition `not 10 in a` directly +SIM103.py:129:5: SIM103 [*] Return the condition `10 not in a` directly | 128 | def f(): 129 | / if 10 in a: @@ -260,7 +260,7 @@ SIM103.py:129:5: SIM103 [*] Return the condition `not 10 in a` directly 131 | | return True | |_______________^ SIM103 | - = help: Replace with `return not 10 in a` + = help: Replace with `return 10 not in a` ℹ Unsafe fix 126 126 | @@ -269,12 +269,12 @@ SIM103.py:129:5: SIM103 [*] Return the condition `not 10 in a` directly 129 |- if 10 in a: 130 |- return False 131 |- return True - 129 |+ return not 10 in a + 129 |+ return 10 not in a 132 130 | 133 131 | 134 132 | def f(): -SIM103.py:135:5: SIM103 [*] Return the condition `not 10 not in a` directly +SIM103.py:135:5: SIM103 [*] Return the condition `10 in a` directly | 134 | def f(): 135 | / if 10 not in a: @@ -282,7 +282,7 @@ SIM103.py:135:5: SIM103 [*] Return the condition `not 10 not in a` directly 137 | | return True | |_______________^ SIM103 | - = help: Replace with `return not 10 not in a` + = help: Replace with `return 10 in a` ℹ Unsafe fix 132 132 | @@ -291,12 +291,12 @@ SIM103.py:135:5: SIM103 [*] Return the condition `not 10 not in a` directly 135 |- if 10 not in a: 136 |- return False 137 |- return True - 135 |+ return not 10 not in a + 135 |+ return 10 in a 138 136 | 139 137 | 140 138 | def f(): -SIM103.py:141:5: SIM103 [*] Return the condition `not a is 10` directly +SIM103.py:141:5: SIM103 [*] Return the condition `a is not 10` directly | 140 | def f(): 141 | / if a is 10: @@ -304,7 +304,7 @@ SIM103.py:141:5: SIM103 [*] Return the condition `not a is 10` directly 143 | | return True | |_______________^ SIM103 | - = help: Replace with `return not a is 10` + = help: Replace with `return a is not 10` ℹ Unsafe fix 138 138 | @@ -313,12 +313,12 @@ SIM103.py:141:5: SIM103 [*] Return the condition `not a is 10` directly 141 |- if a is 10: 142 |- return False 143 |- return True - 141 |+ return not a is 10 + 141 |+ return a is not 10 144 142 | 145 143 | 146 144 | def f(): -SIM103.py:147:5: SIM103 [*] Return the condition `not a is not 10` directly +SIM103.py:147:5: SIM103 [*] Return the condition `a is 10` directly | 146 | def f(): 147 | / if a is not 10: @@ -326,7 +326,7 @@ SIM103.py:147:5: SIM103 [*] Return the condition `not a is not 10` directly 149 | | return True | |_______________^ SIM103 | - = help: Replace with `return not a is not 10` + = help: Replace with `return a is 10` ℹ Unsafe fix 144 144 | @@ -335,12 +335,12 @@ SIM103.py:147:5: SIM103 [*] Return the condition `not a is not 10` directly 147 |- if a is not 10: 148 |- return False 149 |- return True - 147 |+ return not a is not 10 + 147 |+ return a is 10 150 148 | 151 149 | 152 150 | def f(): -SIM103.py:153:5: SIM103 [*] Return the condition `not a == 10` directly +SIM103.py:153:5: SIM103 [*] Return the condition `a != 10` directly | 152 | def f(): 153 | / if a == 10: @@ -348,7 +348,7 @@ SIM103.py:153:5: SIM103 [*] Return the condition `not a == 10` directly 155 | | return True | |_______________^ SIM103 | - = help: Replace with `return not a == 10` + = help: Replace with `return a != 10` ℹ Unsafe fix 150 150 | @@ -357,12 +357,12 @@ SIM103.py:153:5: SIM103 [*] Return the condition `not a == 10` directly 153 |- if a == 10: 154 |- return False 155 |- return True - 153 |+ return not a == 10 + 153 |+ return a != 10 156 154 | 157 155 | 158 156 | def f(): -SIM103.py:159:5: SIM103 [*] Return the condition `not a != 10` directly +SIM103.py:159:5: SIM103 [*] Return the condition `a == 10` directly | 158 | def f(): 159 | / if a != 10: @@ -370,7 +370,7 @@ SIM103.py:159:5: SIM103 [*] Return the condition `not a != 10` directly 161 | | return True | |_______________^ SIM103 | - = help: Replace with `return not a != 10` + = help: Replace with `return a == 10` ℹ Unsafe fix 156 156 | @@ -379,4 +379,4 @@ SIM103.py:159:5: SIM103 [*] Return the condition `not a != 10` directly 159 |- if a != 10: 160 |- return False 161 |- return True - 159 |+ return not a != 10 + 159 |+ return a == 10 diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM108_SIM108.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM108_SIM108.py.snap index 3306802cce8707..a18c5fdac5b1be 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM108_SIM108.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM108_SIM108.py.snap @@ -226,6 +226,33 @@ SIM108.py:167:1: SIM108 [*] Use ternary operator `z = 1 if True else other` inst 172 169 | if False: 173 170 | z = 1 +SIM108.py:172:1: SIM108 [*] Use ternary operator `z = 1 if False else other` instead of `if`-`else`-block + | +170 | z = other +171 | +172 | / if False: +173 | | z = 1 +174 | | else: +175 | | z = other + | |_____________^ SIM108 +176 | +177 | if 1: + | + = help: Replace `if`-`else`-block with `z = 1 if False else other` + +ℹ Unsafe fix +169 169 | else: +170 170 | z = other +171 171 | +172 |-if False: +173 |- z = 1 +174 |-else: +175 |- z = other + 172 |+z = 1 if False else other +176 173 | +177 174 | if 1: +178 175 | z = True + SIM108.py:177:1: SIM108 [*] Use ternary operator `z = True if 1 else other` instead of `if`-`else`-block | 175 | z = other diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM103_SIM103.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM103_SIM103.py.snap deleted file mode 100644 index 58286e133c3448..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM103_SIM103.py.snap +++ /dev/null @@ -1,382 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs ---- -SIM103.py:3:5: SIM103 [*] Return the condition `bool(a)` directly - | -1 | def f(): -2 | # SIM103 -3 | / if a: -4 | | return True -5 | | else: -6 | | return False - | |____________________^ SIM103 - | - = help: Replace with `return bool(a)` - -ℹ Unsafe fix -1 1 | def f(): -2 2 | # SIM103 -3 |- if a: -4 |- return True -5 |- else: -6 |- return False - 3 |+ return bool(a) -7 4 | -8 5 | -9 6 | def f(): - -SIM103.py:11:5: SIM103 [*] Return the condition `a == b` directly - | - 9 | def f(): -10 | # SIM103 -11 | / if a == b: -12 | | return True -13 | | else: -14 | | return False - | |____________________^ SIM103 - | - = help: Replace with `return a == b` - -ℹ Unsafe fix -8 8 | -9 9 | def f(): -10 10 | # SIM103 -11 |- if a == b: -12 |- return True -13 |- else: -14 |- return False - 11 |+ return a == b -15 12 | -16 13 | -17 14 | def f(): - -SIM103.py:21:5: SIM103 [*] Return the condition `bool(b)` directly - | -19 | if a: -20 | return 1 -21 | / elif b: -22 | | return True -23 | | else: -24 | | return False - | |____________________^ SIM103 - | - = help: Replace with `return bool(b)` - -ℹ Unsafe fix -18 18 | # SIM103 -19 19 | if a: -20 20 | return 1 -21 |- elif b: -22 |- return True -23 |- else: -24 |- return False - 21 |+ return bool(b) -25 22 | -26 23 | -27 24 | def f(): - -SIM103.py:32:9: SIM103 [*] Return the condition `bool(b)` directly - | -30 | return 1 -31 | else: -32 | / if b: -33 | | return True -34 | | else: -35 | | return False - | |________________________^ SIM103 - | - = help: Replace with `return bool(b)` - -ℹ Unsafe fix -29 29 | if a: -30 30 | return 1 -31 31 | else: -32 |- if b: -33 |- return True -34 |- else: -35 |- return False - 32 |+ return bool(b) -36 33 | -37 34 | -38 35 | def f(): - -SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly - | -55 | def f(): -56 | # SIM103 -57 | / if a: -58 | | return False -59 | | else: -60 | | return True - | |___________________^ SIM103 - | - = help: Replace with `return not a` - -ℹ Unsafe fix -54 54 | -55 55 | def f(): -56 56 | # SIM103 -57 |- if a: -58 |- return False -59 |- else: -60 |- return True - 57 |+ return not a -61 58 | -62 59 | -63 60 | def f(): - -SIM103.py:83:5: SIM103 Return the condition directly - | -81 | def bool(): -82 | return False -83 | / if a: -84 | | return True -85 | | else: -86 | | return False - | |____________________^ SIM103 - | - = help: Inline condition - -SIM103.py:91:5: SIM103 [*] Return the condition `not (keys is not None and notice.key not in keys)` directly - | -89 | def f(): -90 | # SIM103 -91 | / if keys is not None and notice.key not in keys: -92 | | return False -93 | | else: -94 | | return True - | |___________________^ SIM103 - | - = help: Replace with `return not (keys is not None and notice.key not in keys)` - -ℹ Unsafe fix -88 88 | -89 89 | def f(): -90 90 | # SIM103 -91 |- if keys is not None and notice.key not in keys: -92 |- return False -93 |- else: -94 |- return True - 91 |+ return not (keys is not None and notice.key not in keys) -95 92 | -96 93 | -97 94 | ### - -SIM103.py:104:5: SIM103 [*] Return the condition `bool(a)` directly - | -102 | def f(): -103 | # SIM103 -104 | / if a: -105 | | return True -106 | | return False - | |________________^ SIM103 - | - = help: Replace with `return bool(a)` - -ℹ Unsafe fix -101 101 | -102 102 | def f(): -103 103 | # SIM103 -104 |- if a: -105 |- return True -106 |- return False - 104 |+ return bool(a) -107 105 | -108 106 | -109 107 | def f(): - -SIM103.py:111:5: SIM103 [*] Return the condition `not a` directly - | -109 | def f(): -110 | # SIM103 -111 | / if a: -112 | | return False -113 | | return True - | |_______________^ SIM103 - | - = help: Replace with `return not a` - -ℹ Unsafe fix -108 108 | -109 109 | def f(): -110 110 | # SIM103 -111 |- if a: -112 |- return False -113 |- return True - 111 |+ return not a -114 112 | -115 113 | -116 114 | def f(): - -SIM103.py:117:5: SIM103 [*] Return the condition `10 < a` directly - | -116 | def f(): -117 | / if not 10 < a: -118 | | return False -119 | | return True - | |_______________^ SIM103 - | - = help: Replace with `return 10 < a` - -ℹ Unsafe fix -114 114 | -115 115 | -116 116 | def f(): -117 |- if not 10 < a: -118 |- return False -119 |- return True - 117 |+ return 10 < a -120 118 | -121 119 | -122 120 | def f(): - -SIM103.py:123:5: SIM103 [*] Return the condition `not 10 < a` directly - | -122 | def f(): -123 | / if 10 < a: -124 | | return False -125 | | return True - | |_______________^ SIM103 - | - = help: Replace with `return not 10 < a` - -ℹ Unsafe fix -120 120 | -121 121 | -122 122 | def f(): -123 |- if 10 < a: -124 |- return False -125 |- return True - 123 |+ return not 10 < a -126 124 | -127 125 | -128 126 | def f(): - -SIM103.py:129:5: SIM103 [*] Return the condition `10 not in a` directly - | -128 | def f(): -129 | / if 10 in a: -130 | | return False -131 | | return True - | |_______________^ SIM103 - | - = help: Replace with `return 10 not in a` - -ℹ Unsafe fix -126 126 | -127 127 | -128 128 | def f(): -129 |- if 10 in a: -130 |- return False -131 |- return True - 129 |+ return 10 not in a -132 130 | -133 131 | -134 132 | def f(): - -SIM103.py:135:5: SIM103 [*] Return the condition `10 in a` directly - | -134 | def f(): -135 | / if 10 not in a: -136 | | return False -137 | | return True - | |_______________^ SIM103 - | - = help: Replace with `return 10 in a` - -ℹ Unsafe fix -132 132 | -133 133 | -134 134 | def f(): -135 |- if 10 not in a: -136 |- return False -137 |- return True - 135 |+ return 10 in a -138 136 | -139 137 | -140 138 | def f(): - -SIM103.py:141:5: SIM103 [*] Return the condition `a is not 10` directly - | -140 | def f(): -141 | / if a is 10: -142 | | return False -143 | | return True - | |_______________^ SIM103 - | - = help: Replace with `return a is not 10` - -ℹ Unsafe fix -138 138 | -139 139 | -140 140 | def f(): -141 |- if a is 10: -142 |- return False -143 |- return True - 141 |+ return a is not 10 -144 142 | -145 143 | -146 144 | def f(): - -SIM103.py:147:5: SIM103 [*] Return the condition `a is 10` directly - | -146 | def f(): -147 | / if a is not 10: -148 | | return False -149 | | return True - | |_______________^ SIM103 - | - = help: Replace with `return a is 10` - -ℹ Unsafe fix -144 144 | -145 145 | -146 146 | def f(): -147 |- if a is not 10: -148 |- return False -149 |- return True - 147 |+ return a is 10 -150 148 | -151 149 | -152 150 | def f(): - -SIM103.py:153:5: SIM103 [*] Return the condition `a != 10` directly - | -152 | def f(): -153 | / if a == 10: -154 | | return False -155 | | return True - | |_______________^ SIM103 - | - = help: Replace with `return a != 10` - -ℹ Unsafe fix -150 150 | -151 151 | -152 152 | def f(): -153 |- if a == 10: -154 |- return False -155 |- return True - 153 |+ return a != 10 -156 154 | -157 155 | -158 156 | def f(): - -SIM103.py:159:5: SIM103 [*] Return the condition `a == 10` directly - | -158 | def f(): -159 | / if a != 10: -160 | | return False -161 | | return True - | |_______________^ SIM103 - | - = help: Replace with `return a == 10` - -ℹ Unsafe fix -156 156 | -157 157 | -158 158 | def f(): -159 |- if a != 10: -160 |- return False -161 |- return True - 159 |+ return a == 10 diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__empty-type-checking-block_TC005.py.snap b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__empty-type-checking-block_TC005.py.snap index 137f000062e721..5486cdfa4c8bcb 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__empty-type-checking-block_TC005.py.snap +++ b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__empty-type-checking-block_TC005.py.snap @@ -16,102 +16,64 @@ TC005.py:4:5: TC005 [*] Found empty type-checking block 4 |- pass # TC005 5 3 | 6 4 | -7 5 | if False: +7 5 | def example(): -TC005.py:8:5: TC005 [*] Found empty type-checking block +TC005.py:9:9: TC005 [*] Found empty type-checking block | - 7 | if False: - 8 | pass # TC005 - | ^^^^ TC005 - 9 | -10 | if 0: - | - = help: Delete empty type-checking block - -ℹ Safe fix -4 4 | pass # TC005 -5 5 | -6 6 | -7 |-if False: -8 |- pass # TC005 -9 7 | -10 8 | if 0: -11 9 | pass # TC005 - -TC005.py:11:5: TC005 [*] Found empty type-checking block - | -10 | if 0: -11 | pass # TC005 - | ^^^^ TC005 + 7 | def example(): + 8 | if TYPE_CHECKING: + 9 | pass # TC005 + | ^^^^ TC005 +10 | return | = help: Delete empty type-checking block ℹ Safe fix -7 7 | if False: -8 8 | pass # TC005 -9 9 | -10 |-if 0: -11 |- pass # TC005 +5 5 | +6 6 | +7 7 | def example(): +8 |- if TYPE_CHECKING: +9 |- pass # TC005 +10 8 | return +11 9 | 12 10 | -13 11 | -14 12 | def example(): -TC005.py:16:9: TC005 [*] Found empty type-checking block +TC005.py:15:9: TC005 [*] Found empty type-checking block | -14 | def example(): -15 | if TYPE_CHECKING: -16 | pass # TC005 +13 | class Test: +14 | if TYPE_CHECKING: +15 | pass # TC005 | ^^^^ TC005 -17 | return +16 | x = 2 | = help: Delete empty type-checking block ℹ Safe fix +11 11 | 12 12 | -13 13 | -14 14 | def example(): -15 |- if TYPE_CHECKING: -16 |- pass # TC005 -17 15 | return +13 13 | class Test: +14 |- if TYPE_CHECKING: +15 |- pass # TC005 +16 14 | x = 2 +17 15 | 18 16 | -19 17 | - -TC005.py:22:9: TC005 [*] Found empty type-checking block - | -20 | class Test: -21 | if TYPE_CHECKING: -22 | pass # TC005 - | ^^^^ TC005 -23 | x = 2 - | - = help: Delete empty type-checking block - -ℹ Safe fix -18 18 | -19 19 | -20 20 | class Test: -21 |- if TYPE_CHECKING: -22 |- pass # TC005 -23 21 | x = 2 -24 22 | -25 23 | -TC005.py:45:5: TC005 [*] Found empty type-checking block +TC005.py:31:5: TC005 [*] Found empty type-checking block | -44 | if TYPE_CHECKING: -45 | pass # TC005 +30 | if TYPE_CHECKING: +31 | pass # TC005 | ^^^^ TC005 -46 | -47 | # https://github.com/astral-sh/ruff/issues/11368 +32 | +33 | # https://github.com/astral-sh/ruff/issues/11368 | = help: Delete empty type-checking block ℹ Safe fix -41 41 | -42 42 | from typing_extensions import TYPE_CHECKING -43 43 | -44 |-if TYPE_CHECKING: -45 |- pass # TC005 -46 44 | -47 45 | # https://github.com/astral-sh/ruff/issues/11368 -48 46 | if TYPE_CHECKING: +27 27 | +28 28 | from typing_extensions import TYPE_CHECKING +29 29 | +30 |-if TYPE_CHECKING: +31 |- pass # TC005 +32 30 | +33 31 | # https://github.com/astral-sh/ruff/issues/11368 +34 32 | if TYPE_CHECKING: diff --git a/crates/ruff_linter/src/rules/pep8_naming/mod.rs b/crates/ruff_linter/src/rules/pep8_naming/mod.rs index 15aa305e0d36f2..8e24e4492afd77 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/mod.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/mod.rs @@ -14,13 +14,13 @@ mod tests { use crate::registry::Rule; use crate::rules::pep8_naming::settings::IgnoreNames; use crate::rules::{flake8_import_conventions, pep8_naming}; - use crate::settings::types::PreviewMode; use crate::test::test_path; use crate::{assert_messages, settings}; #[test_case(Rule::InvalidClassName, Path::new("N801.py"))] #[test_case(Rule::InvalidFunctionName, Path::new("N802.py"))] #[test_case(Rule::InvalidArgumentName, Path::new("N803.py"))] + #[test_case(Rule::InvalidArgumentName, Path::new("N804.py"))] #[test_case(Rule::InvalidFirstArgumentNameForClassMethod, Path::new("N804.py"))] #[test_case(Rule::InvalidFirstArgumentNameForMethod, Path::new("N805.py"))] #[test_case(Rule::NonLowercaseVariableInFunction, Path::new("N806.py"))] @@ -89,25 +89,6 @@ mod tests { Ok(()) } - #[test_case(Rule::InvalidArgumentName, Path::new("N803.py"))] - #[test_case(Rule::InvalidArgumentName, Path::new("N804.py"))] - fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "preview__{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("pep8_naming").join(path).as_path(), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } - #[test] fn camelcase_imported_as_incorrect_convention() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs index dd0a83a9a3cc91..d2445e8c39959c 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs @@ -23,7 +23,7 @@ use crate::checkers::ast::Checker; /// > mixedCase is allowed only in contexts where that’s already the /// > prevailing style (e.g. threading.py), to retain backwards compatibility. /// -/// In [preview], overridden methods are ignored. +/// Methods decorated with `@typing.override` are ignored. /// /// ## Example /// ```python @@ -61,8 +61,7 @@ pub(crate) fn invalid_argument_name_function(checker: &Checker, function_def: &S let semantic = checker.semantic(); let scope = semantic.current_scope(); - if checker.settings.preview.is_enabled() - && matches!(scope.kind, ScopeKind::Class(_)) + if matches!(scope.kind, ScopeKind::Class(_)) && is_override(&function_def.decorator_list, semantic) { return; diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs index 9c09110b3a4adf..21bd9172a7134a 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs @@ -82,8 +82,8 @@ impl Violation for InvalidFirstArgumentNameForMethod { /// Checks for class methods that use a name other than `cls` for their /// first argument. /// -/// With [`preview`] enabled, the method `__new__` is exempted from this -/// check and the corresponding violation is then caught by +/// The method `__new__` is exempted from this +/// check and the corresponding violation is caught by /// [`bad-staticmethod-argument`][PLW0211]. /// /// ## Why is this bad? @@ -164,8 +164,6 @@ enum FunctionType { Method, /// The function is a class method. ClassMethod, - /// The function is the method `__new__` - NewMethod, } impl FunctionType { @@ -177,11 +175,6 @@ impl FunctionType { is_new: false, } .into(), - Self::NewMethod => InvalidFirstArgumentNameForClassMethod { - argument_name, - is_new: true, - } - .into(), } } @@ -189,7 +182,6 @@ impl FunctionType { match self { Self::Method => "self", Self::ClassMethod => "cls", - Self::NewMethod => "cls", } } @@ -197,7 +189,6 @@ impl FunctionType { match self { Self::Method => Rule::InvalidFirstArgumentNameForMethod, Self::ClassMethod => Rule::InvalidFirstArgumentNameForClassMethod, - Self::NewMethod => Rule::InvalidFirstArgumentNameForClassMethod, } } } @@ -241,11 +232,10 @@ pub(crate) fn invalid_first_argument_name(checker: &Checker, scope: &Scope) { IsMetaclass::Maybe => return, }, function_type::FunctionType::ClassMethod => FunctionType::ClassMethod, - // In preview, this violation is caught by `PLW0211` instead - function_type::FunctionType::NewMethod if checker.settings.preview.is_enabled() => { + // This violation is caught by `PLW0211` instead + function_type::FunctionType::NewMethod => { return; } - function_type::FunctionType::NewMethod => FunctionType::NewMethod, }; if !checker.enabled(function_type.rule()) { return; diff --git a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N803_N803.py.snap b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N803_N803.py.snap index 7af2a22d2e1e71..d8f2c61cd671a7 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N803_N803.py.snap +++ b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N803_N803.py.snap @@ -16,14 +16,6 @@ N803.py:6:28: N803 Argument name `A` should be lowercase 7 | return _, a, A | -N803.py:18:28: N803 Argument name `A` should be lowercase - | -16 | class Extended(Class): -17 | @override -18 | def method(self, _, a, A): ... - | ^ N803 - | - N803.py:22:16: N803 Argument name `A` should be lowercase | 21 | @override # Incorrect usage diff --git a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__preview__N803_N804.py.snap b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N803_N804.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__preview__N803_N804.py.snap rename to crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N803_N804.py.snap diff --git a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__preview__N803_N803.py.snap b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__preview__N803_N803.py.snap deleted file mode 100644 index d8f2c61cd671a7..00000000000000 --- a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__preview__N803_N803.py.snap +++ /dev/null @@ -1,37 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pep8_naming/mod.rs ---- -N803.py:1:16: N803 Argument name `A` should be lowercase - | -1 | def func(_, a, A): - | ^ N803 -2 | return _, a, A - | - -N803.py:6:28: N803 Argument name `A` should be lowercase - | -5 | class Class: -6 | def method(self, _, a, A): - | ^ N803 -7 | return _, a, A - | - -N803.py:22:16: N803 Argument name `A` should be lowercase - | -21 | @override # Incorrect usage -22 | def func(_, a, A): ... - | ^ N803 - | - -N803.py:25:21: N803 Argument name `A` should be lowercase - | -25 | func = lambda _, a, A: ... - | ^ N803 - | - -N803.py:29:42: N803 Argument name `A` should be lowercase - | -28 | class Extended(Class): -29 | method = override(lambda self, _, a, A: ...) # Incorrect usage - | ^ N803 - | diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs index 53bca7c9e564a5..5f48e00abd9d8a 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs @@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_trivia::Cursor; use ruff_text_size::{Ranged, TextRange}; -use crate::noqa::{Directive, FileNoqaDirectives, NoqaDirectives, ParsedFileExemption}; +use crate::noqa::{self, Directive, FileNoqaDirectives, NoqaDirectives}; use crate::settings::types::PreviewMode; use crate::Locator; @@ -46,7 +46,6 @@ use crate::Locator; #[derive(ViolationMetadata)] pub(crate) struct BlanketNOQA { missing_colon: bool, - space_before_colon: bool, file_exemption: bool, } @@ -57,27 +56,22 @@ impl Violation for BlanketNOQA { fn message(&self) -> String { let BlanketNOQA { missing_colon, - space_before_colon, file_exemption, } = self; // This awkward branching is necessary to ensure that the generic message is picked up by // `derive_message_formats`. - if !missing_colon && !space_before_colon && !file_exemption { + if !missing_colon && !file_exemption { "Use specific rule codes when using `noqa`".to_string() } else if *file_exemption { "Use specific rule codes when using `ruff: noqa`".to_string() - } else if *missing_colon { - "Use a colon when specifying `noqa` rule codes".to_string() } else { - "Do not add spaces between `noqa` and its colon".to_string() + "Use a colon when specifying `noqa` rule codes".to_string() } } fn fix_title(&self) -> Option { if self.missing_colon { Some("Add missing colon".to_string()) - } else if self.space_before_colon { - Some("Remove space(s) before colon".to_string()) } else { None } @@ -94,11 +88,10 @@ pub(crate) fn blanket_noqa( ) { if preview.is_enabled() { for line in file_noqa_directives.lines() { - if let ParsedFileExemption::All = line.parsed_file_exemption { + if let Directive::All(_) = line.parsed_file_exemption { diagnostics.push(Diagnostic::new( BlanketNOQA { missing_colon: false, - space_before_colon: false, file_exemption: true, }, line.range(), @@ -116,22 +109,7 @@ pub(crate) fn blanket_noqa( let mut cursor = Cursor::new(&line[noqa_end.to_usize()..]); cursor.eat_while(char::is_whitespace); - // Check for extraneous spaces before the colon. - // Ex) `# noqa : F401` - if cursor.first() == ':' { - let start = all.end(); - let end = start + cursor.token_len(); - let mut diagnostic = Diagnostic::new( - BlanketNOQA { - missing_colon: false, - space_before_colon: true, - file_exemption: false, - }, - TextRange::new(all.start(), end), - ); - diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion(start, end))); - diagnostics.push(diagnostic); - } else if Directive::lex_code(cursor.chars().as_str()).is_some() { + if noqa::lex_codes(cursor.chars().as_str()).is_ok_and(|codes| !codes.is_empty()) { // Check for a missing colon. // Ex) `# noqa F401` let start = all.end(); @@ -139,7 +117,6 @@ pub(crate) fn blanket_noqa( let mut diagnostic = Diagnostic::new( BlanketNOQA { missing_colon: true, - space_before_colon: false, file_exemption: false, }, TextRange::new(all.start(), end), @@ -151,7 +128,6 @@ pub(crate) fn blanket_noqa( diagnostics.push(Diagnostic::new( BlanketNOQA { missing_colon: false, - space_before_colon: false, file_exemption: false, }, all.range(), diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_0.py.snap b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_0.py.snap index d53e1ef7faed10..1b812d166f4da9 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_0.py.snap +++ b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_0.py.snap @@ -68,58 +68,3 @@ PGH004_0.py:21:8: PGH004 [*] Use a colon when specifying `noqa` rule codes 22 22 | 23 23 | # PGH004 24 24 | x = 2 # noqa : X300 - -PGH004_0.py:24:8: PGH004 [*] Do not add spaces between `noqa` and its colon - | -23 | # PGH004 -24 | x = 2 # noqa : X300 - | ^^^^^^^ PGH004 -25 | -26 | # PGH004 - | - = help: Remove space(s) before colon - -ℹ Unsafe fix -21 21 | x = 2 # noqa X100, X200 -22 22 | -23 23 | # PGH004 -24 |-x = 2 # noqa : X300 - 24 |+x = 2 # noqa: X300 -25 25 | -26 26 | # PGH004 -27 27 | x = 2 # noqa : X400 - -PGH004_0.py:27:8: PGH004 [*] Do not add spaces between `noqa` and its colon - | -26 | # PGH004 -27 | x = 2 # noqa : X400 - | ^^^^^^^^ PGH004 -28 | -29 | # PGH004 - | - = help: Remove space(s) before colon - -ℹ Unsafe fix -24 24 | x = 2 # noqa : X300 -25 25 | -26 26 | # PGH004 -27 |-x = 2 # noqa : X400 - 27 |+x = 2 # noqa: X400 -28 28 | -29 29 | # PGH004 -30 30 | x = 2 # noqa :X500 - -PGH004_0.py:30:8: PGH004 [*] Do not add spaces between `noqa` and its colon - | -29 | # PGH004 -30 | x = 2 # noqa :X500 - | ^^^^^^^ PGH004 - | - = help: Remove space(s) before colon - -ℹ Unsafe fix -27 27 | x = 2 # noqa : X400 -28 28 | -29 29 | # PGH004 -30 |-x = 2 # noqa :X500 - 30 |+x = 2 # noqa:X500 diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 32ec558580a116..4c19774b045680 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -16,10 +16,10 @@ mod tests { use crate::registry::Rule; use crate::rules::{flake8_tidy_imports, pylint}; + use crate::assert_messages; use crate::settings::types::PreviewMode; use crate::settings::LinterSettings; use crate::test::test_path; - use crate::{assert_messages, settings}; #[test_case(Rule::SingledispatchMethod, Path::new("singledispatch_method.py"))] #[test_case( @@ -440,31 +440,4 @@ mod tests { assert_messages!(diagnostics); Ok(()) } - - #[test_case( - Rule::RepeatedEqualityComparison, - Path::new("repeated_equality_comparison.py") - )] - #[test_case(Rule::InvalidEnvvarDefault, Path::new("invalid_envvar_default.py"))] - #[test_case(Rule::BadStrStripCall, Path::new("bad_str_strip_call.py"))] - #[test_case( - Rule::BadStaticmethodArgument, - Path::new("bad_staticmethod_argument.py") - )] - fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "preview__{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("pylint").join(path).as_path(), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs index 48ce24a79836ca..9cf65152fc934a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs @@ -3,6 +3,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast as ast; use ruff_python_ast::ParameterWithDefault; use ruff_python_semantic::analyze::function_type; +use ruff_python_semantic::analyze::function_type::FunctionType; use ruff_python_semantic::Scope; use ruff_text_size::Ranged; @@ -10,9 +11,7 @@ use crate::checkers::ast::Checker; /// ## What it does /// Checks for static methods that use `self` or `cls` as their first argument. -/// -/// If [`preview`] mode is enabled, this rule also applies to -/// `__new__` methods, which are implicitly static. +/// This rule also applies to `__new__` methods, which are implicitly static. /// /// ## Why is this bad? /// [PEP 8] recommends the use of `self` and `cls` as the first arguments for @@ -77,9 +76,8 @@ pub(crate) fn bad_staticmethod_argument(checker: &Checker, scope: &Scope) { ); match type_ { - function_type::FunctionType::StaticMethod => {} - function_type::FunctionType::NewMethod if checker.settings.preview.is_enabled() => {} - _ => { + FunctionType::StaticMethod | FunctionType::NewMethod => {} + FunctionType::Function | FunctionType::Method | FunctionType::ClassMethod => { return; } }; diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs index 07dd4f84706899..dc1f0269e126f0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs @@ -187,12 +187,6 @@ pub(crate) fn bad_str_strip_call(checker: &Checker, call: &ast::ExprCall) { let value = &**value; - if checker.settings.preview.is_disabled() - && !matches!(value, Expr::StringLiteral(_) | Expr::BytesLiteral(_)) - { - return; - } - let Some(value_kind) = ValueKind::from(value, checker.semantic()) else { return; }; diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs index 51cf79b7ca6ad8..7a4e935b3d5eb9 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs @@ -52,14 +52,10 @@ pub(crate) fn invalid_envvar_default(checker: &Checker, call: &ast::ExprCall) { .semantic() .resolve_qualified_name(&call.func) .is_some_and(|qualified_name| { - if checker.settings.preview.is_enabled() { - matches!( - qualified_name.segments(), - ["os", "getenv"] | ["os", "environ", "get"] - ) - } else { - matches!(qualified_name.segments(), ["os", "getenv"]) - } + matches!( + qualified_name.segments(), + ["os", "getenv"] | ["os", "environ", "get"] + ) }) { // Find the `default` argument, if it exists. diff --git a/crates/ruff_linter/src/rules/pylint/rules/len_test.rs b/crates/ruff_linter/src/rules/pylint/rules/len_test.rs index 7ff9e480435687..28b531eef2d4c4 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/len_test.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/len_test.rs @@ -9,12 +9,11 @@ use ruff_python_semantic::{BindingId, SemanticModel}; use ruff_text_size::Ranged; /// ## What it does -/// Checks for usage of call of 'len' on sequences -/// in boolean test context. +/// Checks for `len` calls on sequences in a boolean test context. /// /// ## Why is this bad? /// Empty sequences are considered false in a boolean context. -/// You can either remove the call to 'len' +/// You can either remove the call to `len` /// or compare the length against a scalar. /// /// ## Example diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs index 783fc5f8bc809f..29967c6aef3710 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs @@ -15,9 +15,13 @@ use crate::fix::snippet::SourceCodeSnippet; use crate::Locator; /// ## What it does -/// Checks for repeated equality comparisons that can rewritten as a membership +/// Checks for repeated equality comparisons that can be rewritten as a membership /// test. /// +/// This rule will try to determine if the values are hashable +/// and the fix will use a `set` if they are. If unable to determine, the fix +/// will use a `tuple` and suggest the use of a `set`. +/// /// ## Why is this bad? /// To check if a variable is equal to one of many values, it is common to /// write a series of equality comparisons (e.g., @@ -28,10 +32,6 @@ use crate::Locator; /// If the items are hashable, use a `set` for efficiency; otherwise, use a /// `tuple`. /// -/// In [preview], this rule will try to determine if the values are hashable -/// and the fix will use a `set` if they are. If unable to determine, the fix -/// will use a `tuple` and continue to suggest the use of a `set`. -/// /// ## Example /// ```python /// foo == "bar" or foo == "baz" or foo == "qux" @@ -46,8 +46,6 @@ use crate::Locator; /// - [Python documentation: Comparisons](https://docs.python.org/3/reference/expressions.html#comparisons) /// - [Python documentation: Membership test operations](https://docs.python.org/3/reference/expressions.html#membership-test-operations) /// - [Python documentation: `set`](https://docs.python.org/3/library/stdtypes.html#set) -/// -/// [preview]: https://docs.astral.sh/ruff/preview/ #[derive(ViolationMetadata)] pub(crate) struct RepeatedEqualityComparison { expression: SourceCodeSnippet, @@ -135,10 +133,9 @@ pub(crate) fn repeated_equality_comparison(checker: &Checker, bool_op: &ast::Exp // if we can determine that all the values are hashable, we can use a set // TODO: improve with type inference - let all_hashable = checker.settings.preview.is_enabled() - && comparators - .iter() - .all(|comparator| comparator.is_literal_expr()); + let all_hashable = comparators + .iter() + .all(|comparator| comparator.is_literal_expr()); let mut diagnostic = Diagnostic::new( RepeatedEqualityComparison { diff --git a/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs b/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs index af2bb08651b3f3..246766925b54ae 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs @@ -13,7 +13,7 @@ use crate::checkers::ast::Checker; /// `os.environ` is not a `dict` object, but rather, a proxy object. As such, mutating a shallow /// copy of `os.environ` will also mutate the original object. /// -/// See: [#15373] for more information. +/// See [BPO 15373] for more information. /// /// ## Example /// ```python @@ -41,7 +41,7 @@ use crate::checkers::ast::Checker; /// - [Python documentation: `copy` — Shallow and deep copy operations](https://docs.python.org/3/library/copy.html) /// - [Python documentation: `os.environ`](https://docs.python.org/3/library/os.html#os.environ) /// -/// [#15373]: https://bugs.python.org/issue15373 +/// [BPO 15373]: https://bugs.python.org/issue15373 #[derive(ViolationMetadata)] pub(crate) struct ShallowCopyEnviron; diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1310_bad_str_strip_call.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1310_bad_str_strip_call.py.snap index d017b7b062095b..a3d6f646c01275 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1310_bad_str_strip_call.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1310_bad_str_strip_call.py.snap @@ -181,3 +181,30 @@ bad_str_strip_call.py:81:10: PLE1310 String `strip` call contains duplicate char 82 | 83 | # Errors: Type inference | + +bad_str_strip_call.py:85:9: PLE1310 String `strip` call contains duplicate characters + | +83 | # Errors: Type inference +84 | b = b'' +85 | b.strip(b'//') + | ^^^^^ PLE1310 +86 | +87 | # Errors: Type inference (preview) + | + +bad_str_strip_call.py:89:12: PLE1310 String `rstrip` call contains duplicate characters (did you mean `removesuffix`?) + | +87 | # Errors: Type inference (preview) +88 | foo: str = ""; bar: bytes = b"" +89 | foo.rstrip("//") + | ^^^^ PLE1310 +90 | bar.lstrip(b"//") + | + +bad_str_strip_call.py:90:12: PLE1310 String `lstrip` call contains duplicate characters (did you mean `removeprefix`?) + | +88 | foo: str = ""; bar: bytes = b"" +89 | foo.rstrip("//") +90 | bar.lstrip(b"//") + | ^^^^^ PLE1310 + | diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1714_repeated_equality_comparison.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1714_repeated_equality_comparison.py.snap index 7e75b2ed09a972..8c988f02f717dc 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1714_repeated_equality_comparison.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1714_repeated_equality_comparison.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- -repeated_equality_comparison.py:2:1: PLR1714 [*] Consider merging multiple comparisons: `foo in ("a", "b")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:2:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b"}`. | 1 | # Errors. 2 | foo == "a" or foo == "b" @@ -14,12 +14,12 @@ repeated_equality_comparison.py:2:1: PLR1714 [*] Consider merging multiple compa ℹ Unsafe fix 1 1 | # Errors. 2 |-foo == "a" or foo == "b" - 2 |+foo in ("a", "b") + 2 |+foo in {"a", "b"} 3 3 | 4 4 | foo != "a" and foo != "b" 5 5 | -repeated_equality_comparison.py:4:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in ("a", "b")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:4:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in {"a", "b"}`. | 2 | foo == "a" or foo == "b" 3 | @@ -35,12 +35,12 @@ repeated_equality_comparison.py:4:1: PLR1714 [*] Consider merging multiple compa 2 2 | foo == "a" or foo == "b" 3 3 | 4 |-foo != "a" and foo != "b" - 4 |+foo not in ("a", "b") + 4 |+foo not in {"a", "b"} 5 5 | 6 6 | foo == "a" or foo == "b" or foo == "c" 7 7 | -repeated_equality_comparison.py:6:1: PLR1714 [*] Consider merging multiple comparisons: `foo in ("a", "b", "c")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:6:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b", "c"}`. | 4 | foo != "a" and foo != "b" 5 | @@ -56,12 +56,12 @@ repeated_equality_comparison.py:6:1: PLR1714 [*] Consider merging multiple compa 4 4 | foo != "a" and foo != "b" 5 5 | 6 |-foo == "a" or foo == "b" or foo == "c" - 6 |+foo in ("a", "b", "c") + 6 |+foo in {"a", "b", "c"} 7 7 | 8 8 | foo != "a" and foo != "b" and foo != "c" 9 9 | -repeated_equality_comparison.py:8:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in ("a", "b", "c")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:8:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in {"a", "b", "c"}`. | 6 | foo == "a" or foo == "b" or foo == "c" 7 | @@ -77,7 +77,7 @@ repeated_equality_comparison.py:8:1: PLR1714 [*] Consider merging multiple compa 6 6 | foo == "a" or foo == "b" or foo == "c" 7 7 | 8 |-foo != "a" and foo != "b" and foo != "c" - 8 |+foo not in ("a", "b", "c") + 8 |+foo not in {"a", "b", "c"} 9 9 | 10 10 | foo == a or foo == "b" or foo == 3 # Mixed types. 11 11 | @@ -103,7 +103,7 @@ repeated_equality_comparison.py:10:1: PLR1714 [*] Consider merging multiple comp 12 12 | "a" == foo or "b" == foo or "c" == foo 13 13 | -repeated_equality_comparison.py:12:1: PLR1714 [*] Consider merging multiple comparisons: `foo in ("a", "b", "c")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:12:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b", "c"}`. | 10 | foo == a or foo == "b" or foo == 3 # Mixed types. 11 | @@ -119,12 +119,12 @@ repeated_equality_comparison.py:12:1: PLR1714 [*] Consider merging multiple comp 10 10 | foo == a or foo == "b" or foo == 3 # Mixed types. 11 11 | 12 |-"a" == foo or "b" == foo or "c" == foo - 12 |+foo in ("a", "b", "c") + 12 |+foo in {"a", "b", "c"} 13 13 | 14 14 | "a" != foo and "b" != foo and "c" != foo 15 15 | -repeated_equality_comparison.py:14:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in ("a", "b", "c")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:14:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in {"a", "b", "c"}`. | 12 | "a" == foo or "b" == foo or "c" == foo 13 | @@ -140,12 +140,12 @@ repeated_equality_comparison.py:14:1: PLR1714 [*] Consider merging multiple comp 12 12 | "a" == foo or "b" == foo or "c" == foo 13 13 | 14 |-"a" != foo and "b" != foo and "c" != foo - 14 |+foo not in ("a", "b", "c") + 14 |+foo not in {"a", "b", "c"} 15 15 | 16 16 | "a" == foo or foo == "b" or "c" == foo 17 17 | -repeated_equality_comparison.py:16:1: PLR1714 [*] Consider merging multiple comparisons: `foo in ("a", "b", "c")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:16:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b", "c"}`. | 14 | "a" != foo and "b" != foo and "c" != foo 15 | @@ -161,7 +161,7 @@ repeated_equality_comparison.py:16:1: PLR1714 [*] Consider merging multiple comp 14 14 | "a" != foo and "b" != foo and "c" != foo 15 15 | 16 |-"a" == foo or foo == "b" or "c" == foo - 16 |+foo in ("a", "b", "c") + 16 |+foo in {"a", "b", "c"} 17 17 | 18 18 | foo == bar or baz == foo or qux == foo 19 19 | @@ -187,7 +187,7 @@ repeated_equality_comparison.py:18:1: PLR1714 [*] Consider merging multiple comp 20 20 | foo == "a" or "b" == foo or foo == "c" 21 21 | -repeated_equality_comparison.py:20:1: PLR1714 [*] Consider merging multiple comparisons: `foo in ("a", "b", "c")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:20:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b", "c"}`. | 18 | foo == bar or baz == foo or qux == foo 19 | @@ -203,12 +203,12 @@ repeated_equality_comparison.py:20:1: PLR1714 [*] Consider merging multiple comp 18 18 | foo == bar or baz == foo or qux == foo 19 19 | 20 |-foo == "a" or "b" == foo or foo == "c" - 20 |+foo in ("a", "b", "c") + 20 |+foo in {"a", "b", "c"} 21 21 | 22 22 | foo != "a" and "b" != foo and foo != "c" 23 23 | -repeated_equality_comparison.py:22:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in ("a", "b", "c")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:22:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in {"a", "b", "c"}`. | 20 | foo == "a" or "b" == foo or foo == "c" 21 | @@ -224,12 +224,12 @@ repeated_equality_comparison.py:22:1: PLR1714 [*] Consider merging multiple comp 20 20 | foo == "a" or "b" == foo or foo == "c" 21 21 | 22 |-foo != "a" and "b" != foo and foo != "c" - 22 |+foo not in ("a", "b", "c") + 22 |+foo not in {"a", "b", "c"} 23 23 | 24 24 | foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets 25 25 | -repeated_equality_comparison.py:24:1: PLR1714 [*] Consider merging multiple comparisons: `foo in ("a", "b")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:24:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b"}`. | 22 | foo != "a" and "b" != foo and foo != "c" 23 | @@ -245,12 +245,12 @@ repeated_equality_comparison.py:24:1: PLR1714 [*] Consider merging multiple comp 22 22 | foo != "a" and "b" != foo and foo != "c" 23 23 | 24 |-foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets - 24 |+foo in ("a", "b") or "c" == bar or "d" == bar # Multiple targets + 24 |+foo in {"a", "b"} or "c" == bar or "d" == bar # Multiple targets 25 25 | 26 26 | foo.bar == "a" or foo.bar == "b" # Attributes. 27 27 | -repeated_equality_comparison.py:24:1: PLR1714 [*] Consider merging multiple comparisons: `bar in ("c", "d")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:24:1: PLR1714 [*] Consider merging multiple comparisons: `bar in {"c", "d"}`. | 22 | foo != "a" and "b" != foo and foo != "c" 23 | @@ -266,12 +266,12 @@ repeated_equality_comparison.py:24:1: PLR1714 [*] Consider merging multiple comp 22 22 | foo != "a" and "b" != foo and foo != "c" 23 23 | 24 |-foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets - 24 |+foo == "a" or foo == "b" or bar in ("c", "d") # Multiple targets + 24 |+foo == "a" or foo == "b" or bar in {"c", "d"} # Multiple targets 25 25 | 26 26 | foo.bar == "a" or foo.bar == "b" # Attributes. 27 27 | -repeated_equality_comparison.py:26:1: PLR1714 [*] Consider merging multiple comparisons: `foo.bar in ("a", "b")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:26:1: PLR1714 [*] Consider merging multiple comparisons: `foo.bar in {"a", "b"}`. | 24 | foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets 25 | @@ -287,12 +287,12 @@ repeated_equality_comparison.py:26:1: PLR1714 [*] Consider merging multiple comp 24 24 | foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets 25 25 | 26 |-foo.bar == "a" or foo.bar == "b" # Attributes. - 26 |+foo.bar in ("a", "b") # Attributes. + 26 |+foo.bar in {"a", "b"} # Attributes. 27 27 | 28 28 | # OK 29 29 | foo == "a" and foo == "b" and foo == "c" # `and` mixed with `==`. -repeated_equality_comparison.py:61:16: PLR1714 [*] Consider merging multiple comparisons: `bar in ("c", "d")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:61:16: PLR1714 [*] Consider merging multiple comparisons: `bar in {"c", "d"}`. | 59 | foo == "a" or "c" == bar or foo == "b" or "d" == bar # Multiple targets 60 | @@ -308,12 +308,12 @@ repeated_equality_comparison.py:61:16: PLR1714 [*] Consider merging multiple com 59 59 | foo == "a" or "c" == bar or foo == "b" or "d" == bar # Multiple targets 60 60 | 61 |-foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets - 61 |+foo == "a" or (bar in ("c", "d")) or foo == "b" # Multiple targets + 61 |+foo == "a" or (bar in {"c", "d"}) or foo == "b" # Multiple targets 62 62 | 63 63 | foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets 64 64 | -repeated_equality_comparison.py:63:1: PLR1714 [*] Consider merging multiple comparisons: `foo in ("a", "b")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:63:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b"}`. | 61 | foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets 62 | @@ -329,12 +329,12 @@ repeated_equality_comparison.py:63:1: PLR1714 [*] Consider merging multiple comp 61 61 | foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets 62 62 | 63 |-foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets - 63 |+foo in ("a", "b") or "c" != bar and "d" != bar # Multiple targets + 63 |+foo in {"a", "b"} or "c" != bar and "d" != bar # Multiple targets 64 64 | 65 65 | foo == "a" or ("c" != bar and "d" != bar) or foo == "b" # Multiple targets 66 66 | -repeated_equality_comparison.py:63:29: PLR1714 [*] Consider merging multiple comparisons: `bar not in ("c", "d")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:63:29: PLR1714 [*] Consider merging multiple comparisons: `bar not in {"c", "d"}`. | 61 | foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets 62 | @@ -350,12 +350,12 @@ repeated_equality_comparison.py:63:29: PLR1714 [*] Consider merging multiple com 61 61 | foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets 62 62 | 63 |-foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets - 63 |+foo == "a" or foo == "b" or bar not in ("c", "d") # Multiple targets + 63 |+foo == "a" or foo == "b" or bar not in {"c", "d"} # Multiple targets 64 64 | 65 65 | foo == "a" or ("c" != bar and "d" != bar) or foo == "b" # Multiple targets 66 66 | -repeated_equality_comparison.py:65:16: PLR1714 [*] Consider merging multiple comparisons: `bar not in ("c", "d")`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:65:16: PLR1714 [*] Consider merging multiple comparisons: `bar not in {"c", "d"}`. | 63 | foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets 64 | @@ -371,12 +371,12 @@ repeated_equality_comparison.py:65:16: PLR1714 [*] Consider merging multiple com 63 63 | foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets 64 64 | 65 |-foo == "a" or ("c" != bar and "d" != bar) or foo == "b" # Multiple targets - 65 |+foo == "a" or (bar not in ("c", "d")) or foo == "b" # Multiple targets + 65 |+foo == "a" or (bar not in {"c", "d"}) or foo == "b" # Multiple targets 66 66 | 67 67 | foo == "a" and "c" != bar or foo == "b" and "d" != bar # Multiple targets 68 68 | -repeated_equality_comparison.py:69:1: PLR1714 [*] Consider merging multiple comparisons: `foo in (1, True)`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:69:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {1, True}`. | 67 | foo == "a" and "c" != bar or foo == "b" and "d" != bar # Multiple targets 68 | @@ -392,12 +392,12 @@ repeated_equality_comparison.py:69:1: PLR1714 [*] Consider merging multiple comp 67 67 | foo == "a" and "c" != bar or foo == "b" and "d" != bar # Multiple targets 68 68 | 69 |-foo == 1 or foo == True # Different types, same hashed value - 69 |+foo in (1, True) # Different types, same hashed value + 69 |+foo in {1, True} # Different types, same hashed value 70 70 | 71 71 | foo == 1 or foo == 1.0 # Different types, same hashed value 72 72 | -repeated_equality_comparison.py:71:1: PLR1714 [*] Consider merging multiple comparisons: `foo in (1, 1.0)`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:71:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {1, 1.0}`. | 69 | foo == 1 or foo == True # Different types, same hashed value 70 | @@ -413,12 +413,12 @@ repeated_equality_comparison.py:71:1: PLR1714 [*] Consider merging multiple comp 69 69 | foo == 1 or foo == True # Different types, same hashed value 70 70 | 71 |-foo == 1 or foo == 1.0 # Different types, same hashed value - 71 |+foo in (1, 1.0) # Different types, same hashed value + 71 |+foo in {1, 1.0} # Different types, same hashed value 72 72 | 73 73 | foo == False or foo == 0 # Different types, same hashed value 74 74 | -repeated_equality_comparison.py:73:1: PLR1714 [*] Consider merging multiple comparisons: `foo in (False, 0)`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:73:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {False, 0}`. | 71 | foo == 1 or foo == 1.0 # Different types, same hashed value 72 | @@ -434,11 +434,11 @@ repeated_equality_comparison.py:73:1: PLR1714 [*] Consider merging multiple comp 71 71 | foo == 1 or foo == 1.0 # Different types, same hashed value 72 72 | 73 |-foo == False or foo == 0 # Different types, same hashed value - 73 |+foo in (False, 0) # Different types, same hashed value + 73 |+foo in {False, 0} # Different types, same hashed value 74 74 | 75 75 | foo == 0.0 or foo == 0j # Different types, same hashed value -repeated_equality_comparison.py:75:1: PLR1714 [*] Consider merging multiple comparisons: `foo in (0.0, 0j)`. Use a `set` if the elements are hashable. +repeated_equality_comparison.py:75:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {0.0, 0j}`. | 73 | foo == False or foo == 0 # Different types, same hashed value 74 | @@ -452,4 +452,4 @@ repeated_equality_comparison.py:75:1: PLR1714 [*] Consider merging multiple comp 73 73 | foo == False or foo == 0 # Different types, same hashed value 74 74 | 75 |-foo == 0.0 or foo == 0j # Different types, same hashed value - 75 |+foo in (0.0, 0j) # Different types, same hashed value + 75 |+foo in {0.0, 0j} # Different types, same hashed value diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0211_bad_staticmethod_argument.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0211_bad_staticmethod_argument.py.snap index add63e311b0946..9a18d7a33df55a 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0211_bad_staticmethod_argument.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0211_bad_staticmethod_argument.py.snap @@ -26,3 +26,12 @@ bad_staticmethod_argument.py:19:15: PLW0211 First argument of a static method sh | ^^^^ PLW0211 20 | pass | + +bad_staticmethod_argument.py:55:17: PLW0211 First argument of a static method should not be named `self` + | +53 | # `self` but not with `cls` as first argument - see above). +54 | class Foo: +55 | def __new__(self, x, y, z): # [bad-staticmethod-argument] + | ^^^^ PLW0211 +56 | pass + | diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap index b1fed4b4b93332..0ad2917d0ca3b5 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap @@ -50,3 +50,13 @@ invalid_envvar_default.py:14:17: PLW1508 Invalid type for environment variable d 15 | os.environ.get("TEST", 12) # [invalid-envvar-default] 16 | os.environ.get("TEST", "AA" * 12) | + +invalid_envvar_default.py:15:24: PLW1508 Invalid type for environment variable default; expected `str` or `None` + | +13 | os.getenv("AA", "GOOD" if Z else "BAR") +14 | os.getenv("AA", 1 if Z else "BAR") # [invalid-envvar-default] +15 | os.environ.get("TEST", 12) # [invalid-envvar-default] + | ^^ PLW1508 +16 | os.environ.get("TEST", "AA" * 12) +17 | os.environ.get("TEST", 13 * "AA") + | diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLE1310_bad_str_strip_call.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLE1310_bad_str_strip_call.py.snap deleted file mode 100644 index a3d6f646c01275..00000000000000 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLE1310_bad_str_strip_call.py.snap +++ /dev/null @@ -1,210 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pylint/mod.rs ---- -bad_str_strip_call.py:2:21: PLE1310 String `strip` call contains duplicate characters - | -1 | # PLE1310 -2 | "Hello World".strip("Hello") - | ^^^^^^^ PLE1310 -3 | -4 | # PLE1310 - | - -bad_str_strip_call.py:5:21: PLE1310 String `strip` call contains duplicate characters - | -4 | # PLE1310 -5 | "Hello World".strip("Hello") - | ^^^^^^^ PLE1310 -6 | -7 | # PLE1310 - | - -bad_str_strip_call.py:8:21: PLE1310 String `strip` call contains duplicate characters - | - 7 | # PLE1310 - 8 | "Hello World".strip(u"Hello") - | ^^^^^^^^ PLE1310 - 9 | -10 | # PLE1310 - | - -bad_str_strip_call.py:11:21: PLE1310 String `strip` call contains duplicate characters - | -10 | # PLE1310 -11 | "Hello World".strip(r"Hello") - | ^^^^^^^^ PLE1310 -12 | -13 | # PLE1310 - | - -bad_str_strip_call.py:14:21: PLE1310 String `strip` call contains duplicate characters - | -13 | # PLE1310 -14 | "Hello World".strip("Hello\t") - | ^^^^^^^^^ PLE1310 -15 | -16 | # PLE1310 - | - -bad_str_strip_call.py:17:21: PLE1310 String `strip` call contains duplicate characters - | -16 | # PLE1310 -17 | "Hello World".strip(r"Hello\t") - | ^^^^^^^^^^ PLE1310 -18 | -19 | # PLE1310 - | - -bad_str_strip_call.py:20:21: PLE1310 String `strip` call contains duplicate characters - | -19 | # PLE1310 -20 | "Hello World".strip("Hello\\") - | ^^^^^^^^^ PLE1310 -21 | -22 | # PLE1310 - | - -bad_str_strip_call.py:23:21: PLE1310 String `strip` call contains duplicate characters - | -22 | # PLE1310 -23 | "Hello World".strip(r"Hello\\") - | ^^^^^^^^^^ PLE1310 -24 | -25 | # PLE1310 - | - -bad_str_strip_call.py:26:21: PLE1310 String `strip` call contains duplicate characters - | -25 | # PLE1310 -26 | "Hello World".strip("🤣🤣🤣🤣🙃👀😀") - | ^^^^^^^^^^^^^^^^ PLE1310 -27 | -28 | # PLE1310 - | - -bad_str_strip_call.py:30:5: PLE1310 String `strip` call contains duplicate characters - | -28 | # PLE1310 -29 | "Hello World".strip( -30 | / """ -31 | | there are a lot of characters to strip -32 | | """ - | |___^ PLE1310 -33 | ) - | - -bad_str_strip_call.py:36:21: PLE1310 String `strip` call contains duplicate characters - | -35 | # PLE1310 -36 | "Hello World".strip("can we get a long " \ - | _____________________^ -37 | | "string of characters to strip " \ -38 | | "please?") - | |_____________________________^ PLE1310 -39 | -40 | # PLE1310 - | - -bad_str_strip_call.py:42:5: PLE1310 String `strip` call contains duplicate characters - | -40 | # PLE1310 -41 | "Hello World".strip( -42 | / "can we get a long " -43 | | "string of characters to strip " -44 | | "please?" - | |_____________^ PLE1310 -45 | ) - | - -bad_str_strip_call.py:49:5: PLE1310 String `strip` call contains duplicate characters - | -47 | # PLE1310 -48 | "Hello World".strip( -49 | / "can \t we get a long" -50 | | "string \t of characters to strip" -51 | | "please?" - | |_____________^ PLE1310 -52 | ) - | - -bad_str_strip_call.py:61:11: PLE1310 String `strip` call contains duplicate characters - | -60 | # PLE1310 -61 | u''.strip('http://') - | ^^^^^^^^^ PLE1310 -62 | -63 | # PLE1310 - | - -bad_str_strip_call.py:64:12: PLE1310 String `lstrip` call contains duplicate characters (did you mean `removeprefix`?) - | -63 | # PLE1310 -64 | u''.lstrip('http://') - | ^^^^^^^^^ PLE1310 -65 | -66 | # PLE1310 - | - -bad_str_strip_call.py:67:12: PLE1310 String `rstrip` call contains duplicate characters (did you mean `removesuffix`?) - | -66 | # PLE1310 -67 | b''.rstrip(b'http://') - | ^^^^^^^^^^ PLE1310 -68 | -69 | # OK - | - -bad_str_strip_call.py:79:10: PLE1310 String `strip` call contains duplicate characters - | -78 | # Errors: Multiple backslashes -79 | ''.strip('\\b\\x09') - | ^^^^^^^^^^ PLE1310 -80 | ''.strip(r'\b\x09') -81 | ''.strip('\\\x5C') - | - -bad_str_strip_call.py:80:10: PLE1310 String `strip` call contains duplicate characters - | -78 | # Errors: Multiple backslashes -79 | ''.strip('\\b\\x09') -80 | ''.strip(r'\b\x09') - | ^^^^^^^^^ PLE1310 -81 | ''.strip('\\\x5C') - | - -bad_str_strip_call.py:81:10: PLE1310 String `strip` call contains duplicate characters - | -79 | ''.strip('\\b\\x09') -80 | ''.strip(r'\b\x09') -81 | ''.strip('\\\x5C') - | ^^^^^^^^ PLE1310 -82 | -83 | # Errors: Type inference - | - -bad_str_strip_call.py:85:9: PLE1310 String `strip` call contains duplicate characters - | -83 | # Errors: Type inference -84 | b = b'' -85 | b.strip(b'//') - | ^^^^^ PLE1310 -86 | -87 | # Errors: Type inference (preview) - | - -bad_str_strip_call.py:89:12: PLE1310 String `rstrip` call contains duplicate characters (did you mean `removesuffix`?) - | -87 | # Errors: Type inference (preview) -88 | foo: str = ""; bar: bytes = b"" -89 | foo.rstrip("//") - | ^^^^ PLE1310 -90 | bar.lstrip(b"//") - | - -bad_str_strip_call.py:90:12: PLE1310 String `lstrip` call contains duplicate characters (did you mean `removeprefix`?) - | -88 | foo: str = ""; bar: bytes = b"" -89 | foo.rstrip("//") -90 | bar.lstrip(b"//") - | ^^^^^ PLE1310 - | diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLR1714_repeated_equality_comparison.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLR1714_repeated_equality_comparison.py.snap deleted file mode 100644 index 8c988f02f717dc..00000000000000 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLR1714_repeated_equality_comparison.py.snap +++ /dev/null @@ -1,455 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pylint/mod.rs ---- -repeated_equality_comparison.py:2:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b"}`. - | -1 | # Errors. -2 | foo == "a" or foo == "b" - | ^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -3 | -4 | foo != "a" and foo != "b" - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -1 1 | # Errors. -2 |-foo == "a" or foo == "b" - 2 |+foo in {"a", "b"} -3 3 | -4 4 | foo != "a" and foo != "b" -5 5 | - -repeated_equality_comparison.py:4:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in {"a", "b"}`. - | -2 | foo == "a" or foo == "b" -3 | -4 | foo != "a" and foo != "b" - | ^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -5 | -6 | foo == "a" or foo == "b" or foo == "c" - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -1 1 | # Errors. -2 2 | foo == "a" or foo == "b" -3 3 | -4 |-foo != "a" and foo != "b" - 4 |+foo not in {"a", "b"} -5 5 | -6 6 | foo == "a" or foo == "b" or foo == "c" -7 7 | - -repeated_equality_comparison.py:6:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b", "c"}`. - | -4 | foo != "a" and foo != "b" -5 | -6 | foo == "a" or foo == "b" or foo == "c" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -7 | -8 | foo != "a" and foo != "b" and foo != "c" - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -3 3 | -4 4 | foo != "a" and foo != "b" -5 5 | -6 |-foo == "a" or foo == "b" or foo == "c" - 6 |+foo in {"a", "b", "c"} -7 7 | -8 8 | foo != "a" and foo != "b" and foo != "c" -9 9 | - -repeated_equality_comparison.py:8:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in {"a", "b", "c"}`. - | - 6 | foo == "a" or foo == "b" or foo == "c" - 7 | - 8 | foo != "a" and foo != "b" and foo != "c" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 - 9 | -10 | foo == a or foo == "b" or foo == 3 # Mixed types. - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -5 5 | -6 6 | foo == "a" or foo == "b" or foo == "c" -7 7 | -8 |-foo != "a" and foo != "b" and foo != "c" - 8 |+foo not in {"a", "b", "c"} -9 9 | -10 10 | foo == a or foo == "b" or foo == 3 # Mixed types. -11 11 | - -repeated_equality_comparison.py:10:1: PLR1714 [*] Consider merging multiple comparisons: `foo in (a, "b", 3)`. Use a `set` if the elements are hashable. - | - 8 | foo != "a" and foo != "b" and foo != "c" - 9 | -10 | foo == a or foo == "b" or foo == 3 # Mixed types. - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -11 | -12 | "a" == foo or "b" == foo or "c" == foo - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -7 7 | -8 8 | foo != "a" and foo != "b" and foo != "c" -9 9 | -10 |-foo == a or foo == "b" or foo == 3 # Mixed types. - 10 |+foo in (a, "b", 3) # Mixed types. -11 11 | -12 12 | "a" == foo or "b" == foo or "c" == foo -13 13 | - -repeated_equality_comparison.py:12:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b", "c"}`. - | -10 | foo == a or foo == "b" or foo == 3 # Mixed types. -11 | -12 | "a" == foo or "b" == foo or "c" == foo - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -13 | -14 | "a" != foo and "b" != foo and "c" != foo - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -9 9 | -10 10 | foo == a or foo == "b" or foo == 3 # Mixed types. -11 11 | -12 |-"a" == foo or "b" == foo or "c" == foo - 12 |+foo in {"a", "b", "c"} -13 13 | -14 14 | "a" != foo and "b" != foo and "c" != foo -15 15 | - -repeated_equality_comparison.py:14:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in {"a", "b", "c"}`. - | -12 | "a" == foo or "b" == foo or "c" == foo -13 | -14 | "a" != foo and "b" != foo and "c" != foo - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -15 | -16 | "a" == foo or foo == "b" or "c" == foo - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -11 11 | -12 12 | "a" == foo or "b" == foo or "c" == foo -13 13 | -14 |-"a" != foo and "b" != foo and "c" != foo - 14 |+foo not in {"a", "b", "c"} -15 15 | -16 16 | "a" == foo or foo == "b" or "c" == foo -17 17 | - -repeated_equality_comparison.py:16:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b", "c"}`. - | -14 | "a" != foo and "b" != foo and "c" != foo -15 | -16 | "a" == foo or foo == "b" or "c" == foo - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -17 | -18 | foo == bar or baz == foo or qux == foo - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -13 13 | -14 14 | "a" != foo and "b" != foo and "c" != foo -15 15 | -16 |-"a" == foo or foo == "b" or "c" == foo - 16 |+foo in {"a", "b", "c"} -17 17 | -18 18 | foo == bar or baz == foo or qux == foo -19 19 | - -repeated_equality_comparison.py:18:1: PLR1714 [*] Consider merging multiple comparisons: `foo in (bar, baz, qux)`. Use a `set` if the elements are hashable. - | -16 | "a" == foo or foo == "b" or "c" == foo -17 | -18 | foo == bar or baz == foo or qux == foo - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -19 | -20 | foo == "a" or "b" == foo or foo == "c" - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -15 15 | -16 16 | "a" == foo or foo == "b" or "c" == foo -17 17 | -18 |-foo == bar or baz == foo or qux == foo - 18 |+foo in (bar, baz, qux) -19 19 | -20 20 | foo == "a" or "b" == foo or foo == "c" -21 21 | - -repeated_equality_comparison.py:20:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b", "c"}`. - | -18 | foo == bar or baz == foo or qux == foo -19 | -20 | foo == "a" or "b" == foo or foo == "c" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -21 | -22 | foo != "a" and "b" != foo and foo != "c" - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -17 17 | -18 18 | foo == bar or baz == foo or qux == foo -19 19 | -20 |-foo == "a" or "b" == foo or foo == "c" - 20 |+foo in {"a", "b", "c"} -21 21 | -22 22 | foo != "a" and "b" != foo and foo != "c" -23 23 | - -repeated_equality_comparison.py:22:1: PLR1714 [*] Consider merging multiple comparisons: `foo not in {"a", "b", "c"}`. - | -20 | foo == "a" or "b" == foo or foo == "c" -21 | -22 | foo != "a" and "b" != foo and foo != "c" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -23 | -24 | foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -19 19 | -20 20 | foo == "a" or "b" == foo or foo == "c" -21 21 | -22 |-foo != "a" and "b" != foo and foo != "c" - 22 |+foo not in {"a", "b", "c"} -23 23 | -24 24 | foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets -25 25 | - -repeated_equality_comparison.py:24:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b"}`. - | -22 | foo != "a" and "b" != foo and foo != "c" -23 | -24 | foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -25 | -26 | foo.bar == "a" or foo.bar == "b" # Attributes. - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -21 21 | -22 22 | foo != "a" and "b" != foo and foo != "c" -23 23 | -24 |-foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets - 24 |+foo in {"a", "b"} or "c" == bar or "d" == bar # Multiple targets -25 25 | -26 26 | foo.bar == "a" or foo.bar == "b" # Attributes. -27 27 | - -repeated_equality_comparison.py:24:1: PLR1714 [*] Consider merging multiple comparisons: `bar in {"c", "d"}`. - | -22 | foo != "a" and "b" != foo and foo != "c" -23 | -24 | foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -25 | -26 | foo.bar == "a" or foo.bar == "b" # Attributes. - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -21 21 | -22 22 | foo != "a" and "b" != foo and foo != "c" -23 23 | -24 |-foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets - 24 |+foo == "a" or foo == "b" or bar in {"c", "d"} # Multiple targets -25 25 | -26 26 | foo.bar == "a" or foo.bar == "b" # Attributes. -27 27 | - -repeated_equality_comparison.py:26:1: PLR1714 [*] Consider merging multiple comparisons: `foo.bar in {"a", "b"}`. - | -24 | foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets -25 | -26 | foo.bar == "a" or foo.bar == "b" # Attributes. - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -27 | -28 | # OK - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -23 23 | -24 24 | foo == "a" or foo == "b" or "c" == bar or "d" == bar # Multiple targets -25 25 | -26 |-foo.bar == "a" or foo.bar == "b" # Attributes. - 26 |+foo.bar in {"a", "b"} # Attributes. -27 27 | -28 28 | # OK -29 29 | foo == "a" and foo == "b" and foo == "c" # `and` mixed with `==`. - -repeated_equality_comparison.py:61:16: PLR1714 [*] Consider merging multiple comparisons: `bar in {"c", "d"}`. - | -59 | foo == "a" or "c" == bar or foo == "b" or "d" == bar # Multiple targets -60 | -61 | foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets - | ^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -62 | -63 | foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -58 58 | -59 59 | foo == "a" or "c" == bar or foo == "b" or "d" == bar # Multiple targets -60 60 | -61 |-foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets - 61 |+foo == "a" or (bar in {"c", "d"}) or foo == "b" # Multiple targets -62 62 | -63 63 | foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets -64 64 | - -repeated_equality_comparison.py:63:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {"a", "b"}`. - | -61 | foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets -62 | -63 | foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -64 | -65 | foo == "a" or ("c" != bar and "d" != bar) or foo == "b" # Multiple targets - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -60 60 | -61 61 | foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets -62 62 | -63 |-foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets - 63 |+foo in {"a", "b"} or "c" != bar and "d" != bar # Multiple targets -64 64 | -65 65 | foo == "a" or ("c" != bar and "d" != bar) or foo == "b" # Multiple targets -66 66 | - -repeated_equality_comparison.py:63:29: PLR1714 [*] Consider merging multiple comparisons: `bar not in {"c", "d"}`. - | -61 | foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets -62 | -63 | foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets - | ^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -64 | -65 | foo == "a" or ("c" != bar and "d" != bar) or foo == "b" # Multiple targets - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -60 60 | -61 61 | foo == "a" or ("c" == bar or "d" == bar) or foo == "b" # Multiple targets -62 62 | -63 |-foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets - 63 |+foo == "a" or foo == "b" or bar not in {"c", "d"} # Multiple targets -64 64 | -65 65 | foo == "a" or ("c" != bar and "d" != bar) or foo == "b" # Multiple targets -66 66 | - -repeated_equality_comparison.py:65:16: PLR1714 [*] Consider merging multiple comparisons: `bar not in {"c", "d"}`. - | -63 | foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets -64 | -65 | foo == "a" or ("c" != bar and "d" != bar) or foo == "b" # Multiple targets - | ^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -66 | -67 | foo == "a" and "c" != bar or foo == "b" and "d" != bar # Multiple targets - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -62 62 | -63 63 | foo == "a" or foo == "b" or "c" != bar and "d" != bar # Multiple targets -64 64 | -65 |-foo == "a" or ("c" != bar and "d" != bar) or foo == "b" # Multiple targets - 65 |+foo == "a" or (bar not in {"c", "d"}) or foo == "b" # Multiple targets -66 66 | -67 67 | foo == "a" and "c" != bar or foo == "b" and "d" != bar # Multiple targets -68 68 | - -repeated_equality_comparison.py:69:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {1, True}`. - | -67 | foo == "a" and "c" != bar or foo == "b" and "d" != bar # Multiple targets -68 | -69 | foo == 1 or foo == True # Different types, same hashed value - | ^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -70 | -71 | foo == 1 or foo == 1.0 # Different types, same hashed value - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -66 66 | -67 67 | foo == "a" and "c" != bar or foo == "b" and "d" != bar # Multiple targets -68 68 | -69 |-foo == 1 or foo == True # Different types, same hashed value - 69 |+foo in {1, True} # Different types, same hashed value -70 70 | -71 71 | foo == 1 or foo == 1.0 # Different types, same hashed value -72 72 | - -repeated_equality_comparison.py:71:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {1, 1.0}`. - | -69 | foo == 1 or foo == True # Different types, same hashed value -70 | -71 | foo == 1 or foo == 1.0 # Different types, same hashed value - | ^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -72 | -73 | foo == False or foo == 0 # Different types, same hashed value - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -68 68 | -69 69 | foo == 1 or foo == True # Different types, same hashed value -70 70 | -71 |-foo == 1 or foo == 1.0 # Different types, same hashed value - 71 |+foo in {1, 1.0} # Different types, same hashed value -72 72 | -73 73 | foo == False or foo == 0 # Different types, same hashed value -74 74 | - -repeated_equality_comparison.py:73:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {False, 0}`. - | -71 | foo == 1 or foo == 1.0 # Different types, same hashed value -72 | -73 | foo == False or foo == 0 # Different types, same hashed value - | ^^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 -74 | -75 | foo == 0.0 or foo == 0j # Different types, same hashed value - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -70 70 | -71 71 | foo == 1 or foo == 1.0 # Different types, same hashed value -72 72 | -73 |-foo == False or foo == 0 # Different types, same hashed value - 73 |+foo in {False, 0} # Different types, same hashed value -74 74 | -75 75 | foo == 0.0 or foo == 0j # Different types, same hashed value - -repeated_equality_comparison.py:75:1: PLR1714 [*] Consider merging multiple comparisons: `foo in {0.0, 0j}`. - | -73 | foo == False or foo == 0 # Different types, same hashed value -74 | -75 | foo == 0.0 or foo == 0j # Different types, same hashed value - | ^^^^^^^^^^^^^^^^^^^^^^^ PLR1714 - | - = help: Merge multiple comparisons - -ℹ Unsafe fix -72 72 | -73 73 | foo == False or foo == 0 # Different types, same hashed value -74 74 | -75 |-foo == 0.0 or foo == 0j # Different types, same hashed value - 75 |+foo in {0.0, 0j} # Different types, same hashed value diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLW0211_bad_staticmethod_argument.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLW0211_bad_staticmethod_argument.py.snap deleted file mode 100644 index 9a18d7a33df55a..00000000000000 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLW0211_bad_staticmethod_argument.py.snap +++ /dev/null @@ -1,37 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pylint/mod.rs ---- -bad_staticmethod_argument.py:3:13: PLW0211 First argument of a static method should not be named `self` - | -1 | class Wolf: -2 | @staticmethod -3 | def eat(self): # [bad-staticmethod-argument] - | ^^^^ PLW0211 -4 | pass - | - -bad_staticmethod_argument.py:15:13: PLW0211 First argument of a static method should not be named `cls` - | -13 | class Sheep: -14 | @staticmethod -15 | def eat(cls, x, y, z): # [bad-staticmethod-argument] - | ^^^ PLW0211 -16 | pass - | - -bad_staticmethod_argument.py:19:15: PLW0211 First argument of a static method should not be named `self` - | -18 | @staticmethod -19 | def sleep(self, x, y, z): # [bad-staticmethod-argument] - | ^^^^ PLW0211 -20 | pass - | - -bad_staticmethod_argument.py:55:17: PLW0211 First argument of a static method should not be named `self` - | -53 | # `self` but not with `cls` as first argument - see above). -54 | class Foo: -55 | def __new__(self, x, y, z): # [bad-staticmethod-argument] - | ^^^^ PLW0211 -56 | pass - | diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLW1508_invalid_envvar_default.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLW1508_invalid_envvar_default.py.snap deleted file mode 100644 index 0ad2917d0ca3b5..00000000000000 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLW1508_invalid_envvar_default.py.snap +++ /dev/null @@ -1,62 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pylint/mod.rs ---- -invalid_envvar_default.py:3:29: PLW1508 Invalid type for environment variable default; expected `str` or `None` - | -1 | import os -2 | -3 | tempVar = os.getenv("TEST", 12) # [invalid-envvar-default] - | ^^ PLW1508 -4 | goodVar = os.getenv("TESTING", None) -5 | dictVarBad = os.getenv("AAA", {"a", 7}) # [invalid-envvar-default] - | - -invalid_envvar_default.py:5:31: PLW1508 Invalid type for environment variable default; expected `str` or `None` - | -3 | tempVar = os.getenv("TEST", 12) # [invalid-envvar-default] -4 | goodVar = os.getenv("TESTING", None) -5 | dictVarBad = os.getenv("AAA", {"a", 7}) # [invalid-envvar-default] - | ^^^^^^^^ PLW1508 -6 | print(os.getenv("TEST", False)) # [invalid-envvar-default] -7 | os.getenv("AA", "GOOD") - | - -invalid_envvar_default.py:6:25: PLW1508 Invalid type for environment variable default; expected `str` or `None` - | -4 | goodVar = os.getenv("TESTING", None) -5 | dictVarBad = os.getenv("AAA", {"a", 7}) # [invalid-envvar-default] -6 | print(os.getenv("TEST", False)) # [invalid-envvar-default] - | ^^^^^ PLW1508 -7 | os.getenv("AA", "GOOD") -8 | os.getenv("AA", f"GOOD") - | - -invalid_envvar_default.py:10:17: PLW1508 Invalid type for environment variable default; expected `str` or `None` - | - 8 | os.getenv("AA", f"GOOD") - 9 | os.getenv("AA", "GOOD" + "BAR") -10 | os.getenv("AA", "GOOD" + 1) - | ^^^^^^^^^^ PLW1508 -11 | os.getenv("AA", "GOOD %s" % "BAR") -12 | os.getenv("B", Z) - | - -invalid_envvar_default.py:14:17: PLW1508 Invalid type for environment variable default; expected `str` or `None` - | -12 | os.getenv("B", Z) -13 | os.getenv("AA", "GOOD" if Z else "BAR") -14 | os.getenv("AA", 1 if Z else "BAR") # [invalid-envvar-default] - | ^^^^^^^^^^^^^^^^^ PLW1508 -15 | os.environ.get("TEST", 12) # [invalid-envvar-default] -16 | os.environ.get("TEST", "AA" * 12) - | - -invalid_envvar_default.py:15:24: PLW1508 Invalid type for environment variable default; expected `str` or `None` - | -13 | os.getenv("AA", "GOOD" if Z else "BAR") -14 | os.getenv("AA", 1 if Z else "BAR") # [invalid-envvar-default] -15 | os.environ.get("TEST", 12) # [invalid-envvar-default] - | ^^ PLW1508 -16 | os.environ.get("TEST", "AA" * 12) -17 | os.environ.get("TEST", 13 * "AA") - | diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index 87b2434c881a6f..822e680acfceb4 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -119,25 +119,6 @@ mod tests { Ok(()) } - #[test_case(Rule::RedundantOpenModes, Path::new("UP015.py"))] - #[test_case(Rule::RedundantOpenModes, Path::new("UP015_1.py"))] - fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "preview__{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("pyupgrade").join(path).as_path(), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } - #[test] fn up007_preview() -> Result<()> { let diagnostics = test_path( @@ -285,7 +266,6 @@ mod tests { let diagnostics = test_path( Path::new("pyupgrade/UP044.py"), &settings::LinterSettings { - preview: PreviewMode::Enabled, unresolved_target_version: PythonVersion::PY311, ..settings::LinterSettings::for_rule(Rule::NonPEP646Unpack) }, diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs index e37e0d9bb7c5b0..c8f5f1b04b496b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs @@ -11,6 +11,7 @@ pub(crate) use format_literals::*; pub(crate) use lru_cache_with_maxsize_none::*; pub(crate) use lru_cache_without_parameters::*; pub(crate) use native_literals::*; +pub(crate) use non_pep646_unpack::*; pub(crate) use open_alias::*; pub(crate) use os_error_alias::*; pub(crate) use outdated_version_block::*; @@ -36,7 +37,6 @@ pub(crate) use unpacked_list_comprehension::*; pub(crate) use use_pep585_annotation::*; pub(crate) use use_pep604_annotation::*; pub(crate) use use_pep604_isinstance::*; -pub(crate) use use_pep646_unpack::*; pub(crate) use useless_metaclass_type::*; pub(crate) use useless_object_inheritance::*; pub(crate) use yield_in_for_loop::*; @@ -54,6 +54,7 @@ mod format_literals; mod lru_cache_with_maxsize_none; mod lru_cache_without_parameters; mod native_literals; +mod non_pep646_unpack; mod open_alias; mod os_error_alias; mod outdated_version_block; @@ -79,7 +80,6 @@ mod unpacked_list_comprehension; mod use_pep585_annotation; mod use_pep604_annotation; mod use_pep604_isinstance; -mod use_pep646_unpack; mod useless_metaclass_type; mod useless_object_inheritance; mod yield_in_for_loop; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep646_unpack.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/non_pep646_unpack.rs similarity index 100% rename from crates/ruff_linter/src/rules/pyupgrade/rules/use_pep646_unpack.rs rename to crates/ruff_linter/src/rules/pyupgrade/rules/non_pep646_unpack.rs diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs index 1451301913f9a2..43403c01630cb3 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs @@ -91,17 +91,11 @@ fn create_diagnostic( mode: OpenMode, checker: &Checker, ) -> Diagnostic { - let range = if checker.settings.preview.is_enabled() { - mode_arg.range() - } else { - call.range - }; - let mut diagnostic = Diagnostic::new( RedundantOpenModes { replacement: mode.to_string(), }, - range, + mode_arg.range(), ); if mode.is_empty() { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs index 067ee2acbd491b..4da657f4731b9d 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs @@ -33,6 +33,12 @@ impl CallKind { } } +/// ## Deprecation +/// This rule was deprecated as using [PEP 604] syntax in `isinstance` and `issubclass` calls +/// isn't recommended practice, and it incorrectly suggests that other typing syntaxes like [PEP 695] +/// would be supported by `isinstance` and `issubclass`. Using the [PEP 604] syntax +/// is also slightly slower. +/// /// ## What it does /// Checks for uses of `isinstance` and `issubclass` that take a tuple /// of types for comparison. @@ -64,6 +70,7 @@ impl CallKind { /// - [Python documentation: `issubclass`](https://docs.python.org/3/library/functions.html#issubclass) /// /// [PEP 604]: https://peps.python.org/pep-0604/ +/// [PEP 695]: https://peps.python.org/pep-0695/ #[derive(ViolationMetadata)] pub(crate) struct NonPEP604Isinstance { kind: CallKind, diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP015.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP015.py.snap index d171df8601f764..b265b7e8030fc6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP015.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP015.py.snap @@ -1,10 +1,10 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- -UP015.py:1:1: UP015 [*] Unnecessary mode argument +UP015.py:1:13: UP015 [*] Unnecessary mode argument | 1 | open("foo", "U") - | ^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 2 | open("foo", "Ur") 3 | open("foo", "Ub") | @@ -17,11 +17,11 @@ UP015.py:1:1: UP015 [*] Unnecessary mode argument 3 3 | open("foo", "Ub") 4 4 | open("foo", "rUb") -UP015.py:2:1: UP015 [*] Unnecessary mode argument +UP015.py:2:13: UP015 [*] Unnecessary mode argument | 1 | open("foo", "U") 2 | open("foo", "Ur") - | ^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 3 | open("foo", "Ub") 4 | open("foo", "rUb") | @@ -35,12 +35,12 @@ UP015.py:2:1: UP015 [*] Unnecessary mode argument 4 4 | open("foo", "rUb") 5 5 | open("foo", "r") -UP015.py:3:1: UP015 [*] Unnecessary modes, use `rb` +UP015.py:3:13: UP015 [*] Unnecessary modes, use `rb` | 1 | open("foo", "U") 2 | open("foo", "Ur") 3 | open("foo", "Ub") - | ^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 4 | open("foo", "rUb") 5 | open("foo", "r") | @@ -55,12 +55,12 @@ UP015.py:3:1: UP015 [*] Unnecessary modes, use `rb` 5 5 | open("foo", "r") 6 6 | open("foo", "rt") -UP015.py:4:1: UP015 [*] Unnecessary modes, use `rb` +UP015.py:4:13: UP015 [*] Unnecessary modes, use `rb` | 2 | open("foo", "Ur") 3 | open("foo", "Ub") 4 | open("foo", "rUb") - | ^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^^ UP015 5 | open("foo", "r") 6 | open("foo", "rt") | @@ -76,12 +76,12 @@ UP015.py:4:1: UP015 [*] Unnecessary modes, use `rb` 6 6 | open("foo", "rt") 7 7 | open("f", "r", encoding="UTF-8") -UP015.py:5:1: UP015 [*] Unnecessary mode argument +UP015.py:5:13: UP015 [*] Unnecessary mode argument | 3 | open("foo", "Ub") 4 | open("foo", "rUb") 5 | open("foo", "r") - | ^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 6 | open("foo", "rt") 7 | open("f", "r", encoding="UTF-8") | @@ -97,12 +97,12 @@ UP015.py:5:1: UP015 [*] Unnecessary mode argument 7 7 | open("f", "r", encoding="UTF-8") 8 8 | open("f", "wt") -UP015.py:6:1: UP015 [*] Unnecessary mode argument +UP015.py:6:13: UP015 [*] Unnecessary mode argument | 4 | open("foo", "rUb") 5 | open("foo", "r") 6 | open("foo", "rt") - | ^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 7 | open("f", "r", encoding="UTF-8") 8 | open("f", "wt") | @@ -118,12 +118,12 @@ UP015.py:6:1: UP015 [*] Unnecessary mode argument 8 8 | open("f", "wt") 9 9 | open("f", "tw") -UP015.py:7:1: UP015 [*] Unnecessary mode argument +UP015.py:7:11: UP015 [*] Unnecessary mode argument | 5 | open("foo", "r") 6 | open("foo", "rt") 7 | open("f", "r", encoding="UTF-8") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 8 | open("f", "wt") 9 | open("f", "tw") | @@ -139,12 +139,12 @@ UP015.py:7:1: UP015 [*] Unnecessary mode argument 9 9 | open("f", "tw") 10 10 | -UP015.py:8:1: UP015 [*] Unnecessary modes, use `w` +UP015.py:8:11: UP015 [*] Unnecessary modes, use `w` | 6 | open("foo", "rt") 7 | open("f", "r", encoding="UTF-8") 8 | open("f", "wt") - | ^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 9 | open("f", "tw") | = help: Replace with `w` @@ -159,12 +159,12 @@ UP015.py:8:1: UP015 [*] Unnecessary modes, use `w` 10 10 | 11 11 | with open("foo", "U") as f: -UP015.py:9:1: UP015 [*] Unnecessary modes, use `w` +UP015.py:9:11: UP015 [*] Unnecessary modes, use `w` | 7 | open("f", "r", encoding="UTF-8") 8 | open("f", "wt") 9 | open("f", "tw") - | ^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 10 | 11 | with open("foo", "U") as f: | @@ -180,12 +180,12 @@ UP015.py:9:1: UP015 [*] Unnecessary modes, use `w` 11 11 | with open("foo", "U") as f: 12 12 | pass -UP015.py:11:6: UP015 [*] Unnecessary mode argument +UP015.py:11:18: UP015 [*] Unnecessary mode argument | 9 | open("f", "tw") 10 | 11 | with open("foo", "U") as f: - | ^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 12 | pass 13 | with open("foo", "Ur") as f: | @@ -201,12 +201,12 @@ UP015.py:11:6: UP015 [*] Unnecessary mode argument 13 13 | with open("foo", "Ur") as f: 14 14 | pass -UP015.py:13:6: UP015 [*] Unnecessary mode argument +UP015.py:13:18: UP015 [*] Unnecessary mode argument | 11 | with open("foo", "U") as f: 12 | pass 13 | with open("foo", "Ur") as f: - | ^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 14 | pass 15 | with open("foo", "Ub") as f: | @@ -222,12 +222,12 @@ UP015.py:13:6: UP015 [*] Unnecessary mode argument 15 15 | with open("foo", "Ub") as f: 16 16 | pass -UP015.py:15:6: UP015 [*] Unnecessary modes, use `rb` +UP015.py:15:18: UP015 [*] Unnecessary modes, use `rb` | 13 | with open("foo", "Ur") as f: 14 | pass 15 | with open("foo", "Ub") as f: - | ^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 16 | pass 17 | with open("foo", "rUb") as f: | @@ -243,12 +243,12 @@ UP015.py:15:6: UP015 [*] Unnecessary modes, use `rb` 17 17 | with open("foo", "rUb") as f: 18 18 | pass -UP015.py:17:6: UP015 [*] Unnecessary modes, use `rb` +UP015.py:17:18: UP015 [*] Unnecessary modes, use `rb` | 15 | with open("foo", "Ub") as f: 16 | pass 17 | with open("foo", "rUb") as f: - | ^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^^ UP015 18 | pass 19 | with open("foo", "r") as f: | @@ -264,12 +264,12 @@ UP015.py:17:6: UP015 [*] Unnecessary modes, use `rb` 19 19 | with open("foo", "r") as f: 20 20 | pass -UP015.py:19:6: UP015 [*] Unnecessary mode argument +UP015.py:19:18: UP015 [*] Unnecessary mode argument | 17 | with open("foo", "rUb") as f: 18 | pass 19 | with open("foo", "r") as f: - | ^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 20 | pass 21 | with open("foo", "rt") as f: | @@ -285,12 +285,12 @@ UP015.py:19:6: UP015 [*] Unnecessary mode argument 21 21 | with open("foo", "rt") as f: 22 22 | pass -UP015.py:21:6: UP015 [*] Unnecessary mode argument +UP015.py:21:18: UP015 [*] Unnecessary mode argument | 19 | with open("foo", "r") as f: 20 | pass 21 | with open("foo", "rt") as f: - | ^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 22 | pass 23 | with open("foo", "r", encoding="UTF-8") as f: | @@ -306,12 +306,12 @@ UP015.py:21:6: UP015 [*] Unnecessary mode argument 23 23 | with open("foo", "r", encoding="UTF-8") as f: 24 24 | pass -UP015.py:23:6: UP015 [*] Unnecessary mode argument +UP015.py:23:18: UP015 [*] Unnecessary mode argument | 21 | with open("foo", "rt") as f: 22 | pass 23 | with open("foo", "r", encoding="UTF-8") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 24 | pass 25 | with open("foo", "wt") as f: | @@ -327,12 +327,12 @@ UP015.py:23:6: UP015 [*] Unnecessary mode argument 25 25 | with open("foo", "wt") as f: 26 26 | pass -UP015.py:25:6: UP015 [*] Unnecessary modes, use `w` +UP015.py:25:18: UP015 [*] Unnecessary modes, use `w` | 23 | with open("foo", "r", encoding="UTF-8") as f: 24 | pass 25 | with open("foo", "wt") as f: - | ^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 26 | pass | = help: Replace with `w` @@ -347,12 +347,12 @@ UP015.py:25:6: UP015 [*] Unnecessary modes, use `w` 27 27 | 28 28 | open(f("a", "b", "c"), "U") -UP015.py:28:1: UP015 [*] Unnecessary mode argument +UP015.py:28:24: UP015 [*] Unnecessary mode argument | 26 | pass 27 | 28 | open(f("a", "b", "c"), "U") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 29 | open(f("a", "b", "c"), "Ub") | = help: Remove mode argument @@ -367,11 +367,11 @@ UP015.py:28:1: UP015 [*] Unnecessary mode argument 30 30 | 31 31 | with open(f("a", "b", "c"), "U") as f: -UP015.py:29:1: UP015 [*] Unnecessary modes, use `rb` +UP015.py:29:24: UP015 [*] Unnecessary modes, use `rb` | 28 | open(f("a", "b", "c"), "U") 29 | open(f("a", "b", "c"), "Ub") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 30 | 31 | with open(f("a", "b", "c"), "U") as f: | @@ -387,12 +387,12 @@ UP015.py:29:1: UP015 [*] Unnecessary modes, use `rb` 31 31 | with open(f("a", "b", "c"), "U") as f: 32 32 | pass -UP015.py:31:6: UP015 [*] Unnecessary mode argument +UP015.py:31:29: UP015 [*] Unnecessary mode argument | 29 | open(f("a", "b", "c"), "Ub") 30 | 31 | with open(f("a", "b", "c"), "U") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 32 | pass 33 | with open(f("a", "b", "c"), "Ub") as f: | @@ -408,12 +408,12 @@ UP015.py:31:6: UP015 [*] Unnecessary mode argument 33 33 | with open(f("a", "b", "c"), "Ub") as f: 34 34 | pass -UP015.py:33:6: UP015 [*] Unnecessary modes, use `rb` +UP015.py:33:29: UP015 [*] Unnecessary modes, use `rb` | 31 | with open(f("a", "b", "c"), "U") as f: 32 | pass 33 | with open(f("a", "b", "c"), "Ub") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 34 | pass | = help: Replace with `rb` @@ -428,12 +428,12 @@ UP015.py:33:6: UP015 [*] Unnecessary modes, use `rb` 35 35 | 36 36 | with open("foo", "U") as fa, open("bar", "U") as fb: -UP015.py:36:6: UP015 [*] Unnecessary mode argument +UP015.py:36:18: UP015 [*] Unnecessary mode argument | 34 | pass 35 | 36 | with open("foo", "U") as fa, open("bar", "U") as fb: - | ^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 37 | pass 38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: | @@ -449,12 +449,12 @@ UP015.py:36:6: UP015 [*] Unnecessary mode argument 38 38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: 39 39 | pass -UP015.py:36:30: UP015 [*] Unnecessary mode argument +UP015.py:36:42: UP015 [*] Unnecessary mode argument | 34 | pass 35 | 36 | with open("foo", "U") as fa, open("bar", "U") as fb: - | ^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 37 | pass 38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: | @@ -470,12 +470,12 @@ UP015.py:36:30: UP015 [*] Unnecessary mode argument 38 38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: 39 39 | pass -UP015.py:38:6: UP015 [*] Unnecessary modes, use `rb` +UP015.py:38:18: UP015 [*] Unnecessary modes, use `rb` | 36 | with open("foo", "U") as fa, open("bar", "U") as fb: 37 | pass 38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: - | ^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 39 | pass | = help: Replace with `rb` @@ -490,12 +490,12 @@ UP015.py:38:6: UP015 [*] Unnecessary modes, use `rb` 40 40 | 41 41 | open("foo", mode="U") -UP015.py:38:31: UP015 [*] Unnecessary modes, use `rb` +UP015.py:38:43: UP015 [*] Unnecessary modes, use `rb` | 36 | with open("foo", "U") as fa, open("bar", "U") as fb: 37 | pass 38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: - | ^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 39 | pass | = help: Replace with `rb` @@ -510,12 +510,12 @@ UP015.py:38:31: UP015 [*] Unnecessary modes, use `rb` 40 40 | 41 41 | open("foo", mode="U") -UP015.py:41:1: UP015 [*] Unnecessary mode argument +UP015.py:41:18: UP015 [*] Unnecessary mode argument | 39 | pass 40 | 41 | open("foo", mode="U") - | ^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 42 | open(name="foo", mode="U") 43 | open(mode="U", name="foo") | @@ -531,11 +531,11 @@ UP015.py:41:1: UP015 [*] Unnecessary mode argument 43 43 | open(mode="U", name="foo") 44 44 | -UP015.py:42:1: UP015 [*] Unnecessary mode argument +UP015.py:42:23: UP015 [*] Unnecessary mode argument | 41 | open("foo", mode="U") 42 | open(name="foo", mode="U") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 43 | open(mode="U", name="foo") | = help: Remove mode argument @@ -550,12 +550,12 @@ UP015.py:42:1: UP015 [*] Unnecessary mode argument 44 44 | 45 45 | with open("foo", mode="U") as f: -UP015.py:43:1: UP015 [*] Unnecessary mode argument +UP015.py:43:11: UP015 [*] Unnecessary mode argument | 41 | open("foo", mode="U") 42 | open(name="foo", mode="U") 43 | open(mode="U", name="foo") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 44 | 45 | with open("foo", mode="U") as f: | @@ -571,12 +571,12 @@ UP015.py:43:1: UP015 [*] Unnecessary mode argument 45 45 | with open("foo", mode="U") as f: 46 46 | pass -UP015.py:45:6: UP015 [*] Unnecessary mode argument +UP015.py:45:23: UP015 [*] Unnecessary mode argument | 43 | open(mode="U", name="foo") 44 | 45 | with open("foo", mode="U") as f: - | ^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 46 | pass 47 | with open(name="foo", mode="U") as f: | @@ -592,12 +592,12 @@ UP015.py:45:6: UP015 [*] Unnecessary mode argument 47 47 | with open(name="foo", mode="U") as f: 48 48 | pass -UP015.py:47:6: UP015 [*] Unnecessary mode argument +UP015.py:47:28: UP015 [*] Unnecessary mode argument | 45 | with open("foo", mode="U") as f: 46 | pass 47 | with open(name="foo", mode="U") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 48 | pass 49 | with open(mode="U", name="foo") as f: | @@ -613,12 +613,12 @@ UP015.py:47:6: UP015 [*] Unnecessary mode argument 49 49 | with open(mode="U", name="foo") as f: 50 50 | pass -UP015.py:49:6: UP015 [*] Unnecessary mode argument +UP015.py:49:16: UP015 [*] Unnecessary mode argument | 47 | with open(name="foo", mode="U") as f: 48 | pass 49 | with open(mode="U", name="foo") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 50 | pass | = help: Remove mode argument @@ -633,12 +633,12 @@ UP015.py:49:6: UP015 [*] Unnecessary mode argument 51 51 | 52 52 | open("foo", mode="Ub") -UP015.py:52:1: UP015 [*] Unnecessary modes, use `rb` +UP015.py:52:18: UP015 [*] Unnecessary modes, use `rb` | 50 | pass 51 | 52 | open("foo", mode="Ub") - | ^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 53 | open(name="foo", mode="Ub") 54 | open(mode="Ub", name="foo") | @@ -654,11 +654,11 @@ UP015.py:52:1: UP015 [*] Unnecessary modes, use `rb` 54 54 | open(mode="Ub", name="foo") 55 55 | -UP015.py:53:1: UP015 [*] Unnecessary modes, use `rb` +UP015.py:53:23: UP015 [*] Unnecessary modes, use `rb` | 52 | open("foo", mode="Ub") 53 | open(name="foo", mode="Ub") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 54 | open(mode="Ub", name="foo") | = help: Replace with `rb` @@ -673,12 +673,12 @@ UP015.py:53:1: UP015 [*] Unnecessary modes, use `rb` 55 55 | 56 56 | with open("foo", mode="Ub") as f: -UP015.py:54:1: UP015 [*] Unnecessary modes, use `rb` +UP015.py:54:11: UP015 [*] Unnecessary modes, use `rb` | 52 | open("foo", mode="Ub") 53 | open(name="foo", mode="Ub") 54 | open(mode="Ub", name="foo") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 55 | 56 | with open("foo", mode="Ub") as f: | @@ -694,12 +694,12 @@ UP015.py:54:1: UP015 [*] Unnecessary modes, use `rb` 56 56 | with open("foo", mode="Ub") as f: 57 57 | pass -UP015.py:56:6: UP015 [*] Unnecessary modes, use `rb` +UP015.py:56:23: UP015 [*] Unnecessary modes, use `rb` | 54 | open(mode="Ub", name="foo") 55 | 56 | with open("foo", mode="Ub") as f: - | ^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 57 | pass 58 | with open(name="foo", mode="Ub") as f: | @@ -715,12 +715,12 @@ UP015.py:56:6: UP015 [*] Unnecessary modes, use `rb` 58 58 | with open(name="foo", mode="Ub") as f: 59 59 | pass -UP015.py:58:6: UP015 [*] Unnecessary modes, use `rb` +UP015.py:58:28: UP015 [*] Unnecessary modes, use `rb` | 56 | with open("foo", mode="Ub") as f: 57 | pass 58 | with open(name="foo", mode="Ub") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 59 | pass 60 | with open(mode="Ub", name="foo") as f: | @@ -736,12 +736,12 @@ UP015.py:58:6: UP015 [*] Unnecessary modes, use `rb` 60 60 | with open(mode="Ub", name="foo") as f: 61 61 | pass -UP015.py:60:6: UP015 [*] Unnecessary modes, use `rb` +UP015.py:60:16: UP015 [*] Unnecessary modes, use `rb` | 58 | with open(name="foo", mode="Ub") as f: 59 | pass 60 | with open(mode="Ub", name="foo") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 61 | pass | = help: Replace with `rb` @@ -756,12 +756,12 @@ UP015.py:60:6: UP015 [*] Unnecessary modes, use `rb` 62 62 | 63 63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -UP015.py:63:1: UP015 [*] Unnecessary mode argument +UP015.py:63:23: UP015 [*] Unnecessary mode argument | 61 | pass 62 | 63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') 65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) | @@ -777,11 +777,11 @@ UP015.py:63:1: UP015 [*] Unnecessary mode argument 65 65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) 66 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -UP015.py:64:1: UP015 [*] Unnecessary mode argument +UP015.py:64:106: UP015 [*] Unnecessary mode argument | 63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) 64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) | @@ -797,12 +797,12 @@ UP015.py:64:1: UP015 [*] Unnecessary mode argument 66 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) 67 67 | -UP015.py:65:1: UP015 [*] Unnecessary mode argument +UP015.py:65:65: UP015 [*] Unnecessary mode argument | 63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) 64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') 65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) | = help: Remove mode argument @@ -817,12 +817,12 @@ UP015.py:65:1: UP015 [*] Unnecessary mode argument 67 67 | 68 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -UP015.py:66:1: UP015 [*] Unnecessary mode argument +UP015.py:66:11: UP015 [*] Unnecessary mode argument | 64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') 65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 67 | 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) | @@ -838,12 +838,12 @@ UP015.py:66:1: UP015 [*] Unnecessary mode argument 68 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) 69 69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') -UP015.py:68:1: UP015 [*] Unnecessary modes, use `rb` +UP015.py:68:23: UP015 [*] Unnecessary modes, use `rb` | 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) 67 | 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') 70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) | @@ -859,11 +859,11 @@ UP015.py:68:1: UP015 [*] Unnecessary modes, use `rb` 70 70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) 71 71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -UP015.py:69:1: UP015 [*] Unnecessary modes, use `rb` +UP015.py:69:106: UP015 [*] Unnecessary modes, use `rb` | 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) 69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) 71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) | @@ -879,12 +879,12 @@ UP015.py:69:1: UP015 [*] Unnecessary modes, use `rb` 71 71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) 72 72 | -UP015.py:70:1: UP015 [*] Unnecessary modes, use `rb` +UP015.py:70:65: UP015 [*] Unnecessary modes, use `rb` | 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) 69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') 70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) | = help: Replace with `rb` @@ -899,12 +899,12 @@ UP015.py:70:1: UP015 [*] Unnecessary modes, use `rb` 72 72 | 73 73 | import aiofiles -UP015.py:71:1: UP015 [*] Unnecessary modes, use `rb` +UP015.py:71:11: UP015 [*] Unnecessary modes, use `rb` | 69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') 70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) 71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^^ UP015 72 | 73 | import aiofiles | @@ -920,12 +920,12 @@ UP015.py:71:1: UP015 [*] Unnecessary modes, use `rb` 73 73 | import aiofiles 74 74 | -UP015.py:75:1: UP015 [*] Unnecessary mode argument +UP015.py:75:22: UP015 [*] Unnecessary mode argument | 73 | import aiofiles 74 | 75 | aiofiles.open("foo", "U") - | ^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 76 | aiofiles.open("foo", "r") 77 | aiofiles.open("foo", mode="r") | @@ -941,11 +941,11 @@ UP015.py:75:1: UP015 [*] Unnecessary mode argument 77 77 | aiofiles.open("foo", mode="r") 78 78 | -UP015.py:76:1: UP015 [*] Unnecessary mode argument +UP015.py:76:22: UP015 [*] Unnecessary mode argument | 75 | aiofiles.open("foo", "U") 76 | aiofiles.open("foo", "r") - | ^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 77 | aiofiles.open("foo", mode="r") | = help: Remove mode argument @@ -960,12 +960,12 @@ UP015.py:76:1: UP015 [*] Unnecessary mode argument 78 78 | 79 79 | open("foo", "r+") -UP015.py:77:1: UP015 [*] Unnecessary mode argument +UP015.py:77:27: UP015 [*] Unnecessary mode argument | 75 | aiofiles.open("foo", "U") 76 | aiofiles.open("foo", "r") 77 | aiofiles.open("foo", mode="r") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 78 | 79 | open("foo", "r+") | diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP015_1.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP015_1.py.snap index e727ebbd897a24..a3f9d68f6cb431 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP015_1.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP015_1.py.snap @@ -1,12 +1,12 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- -UP015_1.py:3:5: UP015 [*] Unnecessary mode argument +UP015_1.py:3:17: UP015 [*] Unnecessary mode argument | 1 | # Not a valid type annotation but this test shouldn't result in a panic. 2 | # Refer: https://github.com/astral-sh/ruff/issues/11736 3 | x: 'open("foo", "r")' - | ^^^^^^^^^^^^^^^^ UP015 + | ^^^ UP015 | = help: Remove mode argument diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP015_UP015.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP015_UP015.py.snap deleted file mode 100644 index b265b7e8030fc6..00000000000000 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP015_UP015.py.snap +++ /dev/null @@ -1,982 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pyupgrade/mod.rs ---- -UP015.py:1:13: UP015 [*] Unnecessary mode argument - | -1 | open("foo", "U") - | ^^^ UP015 -2 | open("foo", "Ur") -3 | open("foo", "Ub") - | - = help: Remove mode argument - -ℹ Safe fix -1 |-open("foo", "U") - 1 |+open("foo") -2 2 | open("foo", "Ur") -3 3 | open("foo", "Ub") -4 4 | open("foo", "rUb") - -UP015.py:2:13: UP015 [*] Unnecessary mode argument - | -1 | open("foo", "U") -2 | open("foo", "Ur") - | ^^^^ UP015 -3 | open("foo", "Ub") -4 | open("foo", "rUb") - | - = help: Remove mode argument - -ℹ Safe fix -1 1 | open("foo", "U") -2 |-open("foo", "Ur") - 2 |+open("foo") -3 3 | open("foo", "Ub") -4 4 | open("foo", "rUb") -5 5 | open("foo", "r") - -UP015.py:3:13: UP015 [*] Unnecessary modes, use `rb` - | -1 | open("foo", "U") -2 | open("foo", "Ur") -3 | open("foo", "Ub") - | ^^^^ UP015 -4 | open("foo", "rUb") -5 | open("foo", "r") - | - = help: Replace with `rb` - -ℹ Safe fix -1 1 | open("foo", "U") -2 2 | open("foo", "Ur") -3 |-open("foo", "Ub") - 3 |+open("foo", "rb") -4 4 | open("foo", "rUb") -5 5 | open("foo", "r") -6 6 | open("foo", "rt") - -UP015.py:4:13: UP015 [*] Unnecessary modes, use `rb` - | -2 | open("foo", "Ur") -3 | open("foo", "Ub") -4 | open("foo", "rUb") - | ^^^^^ UP015 -5 | open("foo", "r") -6 | open("foo", "rt") - | - = help: Replace with `rb` - -ℹ Safe fix -1 1 | open("foo", "U") -2 2 | open("foo", "Ur") -3 3 | open("foo", "Ub") -4 |-open("foo", "rUb") - 4 |+open("foo", "rb") -5 5 | open("foo", "r") -6 6 | open("foo", "rt") -7 7 | open("f", "r", encoding="UTF-8") - -UP015.py:5:13: UP015 [*] Unnecessary mode argument - | -3 | open("foo", "Ub") -4 | open("foo", "rUb") -5 | open("foo", "r") - | ^^^ UP015 -6 | open("foo", "rt") -7 | open("f", "r", encoding="UTF-8") - | - = help: Remove mode argument - -ℹ Safe fix -2 2 | open("foo", "Ur") -3 3 | open("foo", "Ub") -4 4 | open("foo", "rUb") -5 |-open("foo", "r") - 5 |+open("foo") -6 6 | open("foo", "rt") -7 7 | open("f", "r", encoding="UTF-8") -8 8 | open("f", "wt") - -UP015.py:6:13: UP015 [*] Unnecessary mode argument - | -4 | open("foo", "rUb") -5 | open("foo", "r") -6 | open("foo", "rt") - | ^^^^ UP015 -7 | open("f", "r", encoding="UTF-8") -8 | open("f", "wt") - | - = help: Remove mode argument - -ℹ Safe fix -3 3 | open("foo", "Ub") -4 4 | open("foo", "rUb") -5 5 | open("foo", "r") -6 |-open("foo", "rt") - 6 |+open("foo") -7 7 | open("f", "r", encoding="UTF-8") -8 8 | open("f", "wt") -9 9 | open("f", "tw") - -UP015.py:7:11: UP015 [*] Unnecessary mode argument - | -5 | open("foo", "r") -6 | open("foo", "rt") -7 | open("f", "r", encoding="UTF-8") - | ^^^ UP015 -8 | open("f", "wt") -9 | open("f", "tw") - | - = help: Remove mode argument - -ℹ Safe fix -4 4 | open("foo", "rUb") -5 5 | open("foo", "r") -6 6 | open("foo", "rt") -7 |-open("f", "r", encoding="UTF-8") - 7 |+open("f", encoding="UTF-8") -8 8 | open("f", "wt") -9 9 | open("f", "tw") -10 10 | - -UP015.py:8:11: UP015 [*] Unnecessary modes, use `w` - | -6 | open("foo", "rt") -7 | open("f", "r", encoding="UTF-8") -8 | open("f", "wt") - | ^^^^ UP015 -9 | open("f", "tw") - | - = help: Replace with `w` - -ℹ Safe fix -5 5 | open("foo", "r") -6 6 | open("foo", "rt") -7 7 | open("f", "r", encoding="UTF-8") -8 |-open("f", "wt") - 8 |+open("f", "w") -9 9 | open("f", "tw") -10 10 | -11 11 | with open("foo", "U") as f: - -UP015.py:9:11: UP015 [*] Unnecessary modes, use `w` - | - 7 | open("f", "r", encoding="UTF-8") - 8 | open("f", "wt") - 9 | open("f", "tw") - | ^^^^ UP015 -10 | -11 | with open("foo", "U") as f: - | - = help: Replace with `w` - -ℹ Safe fix -6 6 | open("foo", "rt") -7 7 | open("f", "r", encoding="UTF-8") -8 8 | open("f", "wt") -9 |-open("f", "tw") - 9 |+open("f", "w") -10 10 | -11 11 | with open("foo", "U") as f: -12 12 | pass - -UP015.py:11:18: UP015 [*] Unnecessary mode argument - | - 9 | open("f", "tw") -10 | -11 | with open("foo", "U") as f: - | ^^^ UP015 -12 | pass -13 | with open("foo", "Ur") as f: - | - = help: Remove mode argument - -ℹ Safe fix -8 8 | open("f", "wt") -9 9 | open("f", "tw") -10 10 | -11 |-with open("foo", "U") as f: - 11 |+with open("foo") as f: -12 12 | pass -13 13 | with open("foo", "Ur") as f: -14 14 | pass - -UP015.py:13:18: UP015 [*] Unnecessary mode argument - | -11 | with open("foo", "U") as f: -12 | pass -13 | with open("foo", "Ur") as f: - | ^^^^ UP015 -14 | pass -15 | with open("foo", "Ub") as f: - | - = help: Remove mode argument - -ℹ Safe fix -10 10 | -11 11 | with open("foo", "U") as f: -12 12 | pass -13 |-with open("foo", "Ur") as f: - 13 |+with open("foo") as f: -14 14 | pass -15 15 | with open("foo", "Ub") as f: -16 16 | pass - -UP015.py:15:18: UP015 [*] Unnecessary modes, use `rb` - | -13 | with open("foo", "Ur") as f: -14 | pass -15 | with open("foo", "Ub") as f: - | ^^^^ UP015 -16 | pass -17 | with open("foo", "rUb") as f: - | - = help: Replace with `rb` - -ℹ Safe fix -12 12 | pass -13 13 | with open("foo", "Ur") as f: -14 14 | pass -15 |-with open("foo", "Ub") as f: - 15 |+with open("foo", "rb") as f: -16 16 | pass -17 17 | with open("foo", "rUb") as f: -18 18 | pass - -UP015.py:17:18: UP015 [*] Unnecessary modes, use `rb` - | -15 | with open("foo", "Ub") as f: -16 | pass -17 | with open("foo", "rUb") as f: - | ^^^^^ UP015 -18 | pass -19 | with open("foo", "r") as f: - | - = help: Replace with `rb` - -ℹ Safe fix -14 14 | pass -15 15 | with open("foo", "Ub") as f: -16 16 | pass -17 |-with open("foo", "rUb") as f: - 17 |+with open("foo", "rb") as f: -18 18 | pass -19 19 | with open("foo", "r") as f: -20 20 | pass - -UP015.py:19:18: UP015 [*] Unnecessary mode argument - | -17 | with open("foo", "rUb") as f: -18 | pass -19 | with open("foo", "r") as f: - | ^^^ UP015 -20 | pass -21 | with open("foo", "rt") as f: - | - = help: Remove mode argument - -ℹ Safe fix -16 16 | pass -17 17 | with open("foo", "rUb") as f: -18 18 | pass -19 |-with open("foo", "r") as f: - 19 |+with open("foo") as f: -20 20 | pass -21 21 | with open("foo", "rt") as f: -22 22 | pass - -UP015.py:21:18: UP015 [*] Unnecessary mode argument - | -19 | with open("foo", "r") as f: -20 | pass -21 | with open("foo", "rt") as f: - | ^^^^ UP015 -22 | pass -23 | with open("foo", "r", encoding="UTF-8") as f: - | - = help: Remove mode argument - -ℹ Safe fix -18 18 | pass -19 19 | with open("foo", "r") as f: -20 20 | pass -21 |-with open("foo", "rt") as f: - 21 |+with open("foo") as f: -22 22 | pass -23 23 | with open("foo", "r", encoding="UTF-8") as f: -24 24 | pass - -UP015.py:23:18: UP015 [*] Unnecessary mode argument - | -21 | with open("foo", "rt") as f: -22 | pass -23 | with open("foo", "r", encoding="UTF-8") as f: - | ^^^ UP015 -24 | pass -25 | with open("foo", "wt") as f: - | - = help: Remove mode argument - -ℹ Safe fix -20 20 | pass -21 21 | with open("foo", "rt") as f: -22 22 | pass -23 |-with open("foo", "r", encoding="UTF-8") as f: - 23 |+with open("foo", encoding="UTF-8") as f: -24 24 | pass -25 25 | with open("foo", "wt") as f: -26 26 | pass - -UP015.py:25:18: UP015 [*] Unnecessary modes, use `w` - | -23 | with open("foo", "r", encoding="UTF-8") as f: -24 | pass -25 | with open("foo", "wt") as f: - | ^^^^ UP015 -26 | pass - | - = help: Replace with `w` - -ℹ Safe fix -22 22 | pass -23 23 | with open("foo", "r", encoding="UTF-8") as f: -24 24 | pass -25 |-with open("foo", "wt") as f: - 25 |+with open("foo", "w") as f: -26 26 | pass -27 27 | -28 28 | open(f("a", "b", "c"), "U") - -UP015.py:28:24: UP015 [*] Unnecessary mode argument - | -26 | pass -27 | -28 | open(f("a", "b", "c"), "U") - | ^^^ UP015 -29 | open(f("a", "b", "c"), "Ub") - | - = help: Remove mode argument - -ℹ Safe fix -25 25 | with open("foo", "wt") as f: -26 26 | pass -27 27 | -28 |-open(f("a", "b", "c"), "U") - 28 |+open(f("a", "b", "c")) -29 29 | open(f("a", "b", "c"), "Ub") -30 30 | -31 31 | with open(f("a", "b", "c"), "U") as f: - -UP015.py:29:24: UP015 [*] Unnecessary modes, use `rb` - | -28 | open(f("a", "b", "c"), "U") -29 | open(f("a", "b", "c"), "Ub") - | ^^^^ UP015 -30 | -31 | with open(f("a", "b", "c"), "U") as f: - | - = help: Replace with `rb` - -ℹ Safe fix -26 26 | pass -27 27 | -28 28 | open(f("a", "b", "c"), "U") -29 |-open(f("a", "b", "c"), "Ub") - 29 |+open(f("a", "b", "c"), "rb") -30 30 | -31 31 | with open(f("a", "b", "c"), "U") as f: -32 32 | pass - -UP015.py:31:29: UP015 [*] Unnecessary mode argument - | -29 | open(f("a", "b", "c"), "Ub") -30 | -31 | with open(f("a", "b", "c"), "U") as f: - | ^^^ UP015 -32 | pass -33 | with open(f("a", "b", "c"), "Ub") as f: - | - = help: Remove mode argument - -ℹ Safe fix -28 28 | open(f("a", "b", "c"), "U") -29 29 | open(f("a", "b", "c"), "Ub") -30 30 | -31 |-with open(f("a", "b", "c"), "U") as f: - 31 |+with open(f("a", "b", "c")) as f: -32 32 | pass -33 33 | with open(f("a", "b", "c"), "Ub") as f: -34 34 | pass - -UP015.py:33:29: UP015 [*] Unnecessary modes, use `rb` - | -31 | with open(f("a", "b", "c"), "U") as f: -32 | pass -33 | with open(f("a", "b", "c"), "Ub") as f: - | ^^^^ UP015 -34 | pass - | - = help: Replace with `rb` - -ℹ Safe fix -30 30 | -31 31 | with open(f("a", "b", "c"), "U") as f: -32 32 | pass -33 |-with open(f("a", "b", "c"), "Ub") as f: - 33 |+with open(f("a", "b", "c"), "rb") as f: -34 34 | pass -35 35 | -36 36 | with open("foo", "U") as fa, open("bar", "U") as fb: - -UP015.py:36:18: UP015 [*] Unnecessary mode argument - | -34 | pass -35 | -36 | with open("foo", "U") as fa, open("bar", "U") as fb: - | ^^^ UP015 -37 | pass -38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: - | - = help: Remove mode argument - -ℹ Safe fix -33 33 | with open(f("a", "b", "c"), "Ub") as f: -34 34 | pass -35 35 | -36 |-with open("foo", "U") as fa, open("bar", "U") as fb: - 36 |+with open("foo") as fa, open("bar", "U") as fb: -37 37 | pass -38 38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: -39 39 | pass - -UP015.py:36:42: UP015 [*] Unnecessary mode argument - | -34 | pass -35 | -36 | with open("foo", "U") as fa, open("bar", "U") as fb: - | ^^^ UP015 -37 | pass -38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: - | - = help: Remove mode argument - -ℹ Safe fix -33 33 | with open(f("a", "b", "c"), "Ub") as f: -34 34 | pass -35 35 | -36 |-with open("foo", "U") as fa, open("bar", "U") as fb: - 36 |+with open("foo", "U") as fa, open("bar") as fb: -37 37 | pass -38 38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: -39 39 | pass - -UP015.py:38:18: UP015 [*] Unnecessary modes, use `rb` - | -36 | with open("foo", "U") as fa, open("bar", "U") as fb: -37 | pass -38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: - | ^^^^ UP015 -39 | pass - | - = help: Replace with `rb` - -ℹ Safe fix -35 35 | -36 36 | with open("foo", "U") as fa, open("bar", "U") as fb: -37 37 | pass -38 |-with open("foo", "Ub") as fa, open("bar", "Ub") as fb: - 38 |+with open("foo", "rb") as fa, open("bar", "Ub") as fb: -39 39 | pass -40 40 | -41 41 | open("foo", mode="U") - -UP015.py:38:43: UP015 [*] Unnecessary modes, use `rb` - | -36 | with open("foo", "U") as fa, open("bar", "U") as fb: -37 | pass -38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: - | ^^^^ UP015 -39 | pass - | - = help: Replace with `rb` - -ℹ Safe fix -35 35 | -36 36 | with open("foo", "U") as fa, open("bar", "U") as fb: -37 37 | pass -38 |-with open("foo", "Ub") as fa, open("bar", "Ub") as fb: - 38 |+with open("foo", "Ub") as fa, open("bar", "rb") as fb: -39 39 | pass -40 40 | -41 41 | open("foo", mode="U") - -UP015.py:41:18: UP015 [*] Unnecessary mode argument - | -39 | pass -40 | -41 | open("foo", mode="U") - | ^^^ UP015 -42 | open(name="foo", mode="U") -43 | open(mode="U", name="foo") - | - = help: Remove mode argument - -ℹ Safe fix -38 38 | with open("foo", "Ub") as fa, open("bar", "Ub") as fb: -39 39 | pass -40 40 | -41 |-open("foo", mode="U") - 41 |+open("foo") -42 42 | open(name="foo", mode="U") -43 43 | open(mode="U", name="foo") -44 44 | - -UP015.py:42:23: UP015 [*] Unnecessary mode argument - | -41 | open("foo", mode="U") -42 | open(name="foo", mode="U") - | ^^^ UP015 -43 | open(mode="U", name="foo") - | - = help: Remove mode argument - -ℹ Safe fix -39 39 | pass -40 40 | -41 41 | open("foo", mode="U") -42 |-open(name="foo", mode="U") - 42 |+open(name="foo") -43 43 | open(mode="U", name="foo") -44 44 | -45 45 | with open("foo", mode="U") as f: - -UP015.py:43:11: UP015 [*] Unnecessary mode argument - | -41 | open("foo", mode="U") -42 | open(name="foo", mode="U") -43 | open(mode="U", name="foo") - | ^^^ UP015 -44 | -45 | with open("foo", mode="U") as f: - | - = help: Remove mode argument - -ℹ Safe fix -40 40 | -41 41 | open("foo", mode="U") -42 42 | open(name="foo", mode="U") -43 |-open(mode="U", name="foo") - 43 |+open(name="foo") -44 44 | -45 45 | with open("foo", mode="U") as f: -46 46 | pass - -UP015.py:45:23: UP015 [*] Unnecessary mode argument - | -43 | open(mode="U", name="foo") -44 | -45 | with open("foo", mode="U") as f: - | ^^^ UP015 -46 | pass -47 | with open(name="foo", mode="U") as f: - | - = help: Remove mode argument - -ℹ Safe fix -42 42 | open(name="foo", mode="U") -43 43 | open(mode="U", name="foo") -44 44 | -45 |-with open("foo", mode="U") as f: - 45 |+with open("foo") as f: -46 46 | pass -47 47 | with open(name="foo", mode="U") as f: -48 48 | pass - -UP015.py:47:28: UP015 [*] Unnecessary mode argument - | -45 | with open("foo", mode="U") as f: -46 | pass -47 | with open(name="foo", mode="U") as f: - | ^^^ UP015 -48 | pass -49 | with open(mode="U", name="foo") as f: - | - = help: Remove mode argument - -ℹ Safe fix -44 44 | -45 45 | with open("foo", mode="U") as f: -46 46 | pass -47 |-with open(name="foo", mode="U") as f: - 47 |+with open(name="foo") as f: -48 48 | pass -49 49 | with open(mode="U", name="foo") as f: -50 50 | pass - -UP015.py:49:16: UP015 [*] Unnecessary mode argument - | -47 | with open(name="foo", mode="U") as f: -48 | pass -49 | with open(mode="U", name="foo") as f: - | ^^^ UP015 -50 | pass - | - = help: Remove mode argument - -ℹ Safe fix -46 46 | pass -47 47 | with open(name="foo", mode="U") as f: -48 48 | pass -49 |-with open(mode="U", name="foo") as f: - 49 |+with open(name="foo") as f: -50 50 | pass -51 51 | -52 52 | open("foo", mode="Ub") - -UP015.py:52:18: UP015 [*] Unnecessary modes, use `rb` - | -50 | pass -51 | -52 | open("foo", mode="Ub") - | ^^^^ UP015 -53 | open(name="foo", mode="Ub") -54 | open(mode="Ub", name="foo") - | - = help: Replace with `rb` - -ℹ Safe fix -49 49 | with open(mode="U", name="foo") as f: -50 50 | pass -51 51 | -52 |-open("foo", mode="Ub") - 52 |+open("foo", mode="rb") -53 53 | open(name="foo", mode="Ub") -54 54 | open(mode="Ub", name="foo") -55 55 | - -UP015.py:53:23: UP015 [*] Unnecessary modes, use `rb` - | -52 | open("foo", mode="Ub") -53 | open(name="foo", mode="Ub") - | ^^^^ UP015 -54 | open(mode="Ub", name="foo") - | - = help: Replace with `rb` - -ℹ Safe fix -50 50 | pass -51 51 | -52 52 | open("foo", mode="Ub") -53 |-open(name="foo", mode="Ub") - 53 |+open(name="foo", mode="rb") -54 54 | open(mode="Ub", name="foo") -55 55 | -56 56 | with open("foo", mode="Ub") as f: - -UP015.py:54:11: UP015 [*] Unnecessary modes, use `rb` - | -52 | open("foo", mode="Ub") -53 | open(name="foo", mode="Ub") -54 | open(mode="Ub", name="foo") - | ^^^^ UP015 -55 | -56 | with open("foo", mode="Ub") as f: - | - = help: Replace with `rb` - -ℹ Safe fix -51 51 | -52 52 | open("foo", mode="Ub") -53 53 | open(name="foo", mode="Ub") -54 |-open(mode="Ub", name="foo") - 54 |+open(mode="rb", name="foo") -55 55 | -56 56 | with open("foo", mode="Ub") as f: -57 57 | pass - -UP015.py:56:23: UP015 [*] Unnecessary modes, use `rb` - | -54 | open(mode="Ub", name="foo") -55 | -56 | with open("foo", mode="Ub") as f: - | ^^^^ UP015 -57 | pass -58 | with open(name="foo", mode="Ub") as f: - | - = help: Replace with `rb` - -ℹ Safe fix -53 53 | open(name="foo", mode="Ub") -54 54 | open(mode="Ub", name="foo") -55 55 | -56 |-with open("foo", mode="Ub") as f: - 56 |+with open("foo", mode="rb") as f: -57 57 | pass -58 58 | with open(name="foo", mode="Ub") as f: -59 59 | pass - -UP015.py:58:28: UP015 [*] Unnecessary modes, use `rb` - | -56 | with open("foo", mode="Ub") as f: -57 | pass -58 | with open(name="foo", mode="Ub") as f: - | ^^^^ UP015 -59 | pass -60 | with open(mode="Ub", name="foo") as f: - | - = help: Replace with `rb` - -ℹ Safe fix -55 55 | -56 56 | with open("foo", mode="Ub") as f: -57 57 | pass -58 |-with open(name="foo", mode="Ub") as f: - 58 |+with open(name="foo", mode="rb") as f: -59 59 | pass -60 60 | with open(mode="Ub", name="foo") as f: -61 61 | pass - -UP015.py:60:16: UP015 [*] Unnecessary modes, use `rb` - | -58 | with open(name="foo", mode="Ub") as f: -59 | pass -60 | with open(mode="Ub", name="foo") as f: - | ^^^^ UP015 -61 | pass - | - = help: Replace with `rb` - -ℹ Safe fix -57 57 | pass -58 58 | with open(name="foo", mode="Ub") as f: -59 59 | pass -60 |-with open(mode="Ub", name="foo") as f: - 60 |+with open(mode="rb", name="foo") as f: -61 61 | pass -62 62 | -63 63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - -UP015.py:63:23: UP015 [*] Unnecessary mode argument - | -61 | pass -62 | -63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | ^^^ UP015 -64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') -65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) - | - = help: Remove mode argument - -ℹ Safe fix -60 60 | with open(mode="Ub", name="foo") as f: -61 61 | pass -62 62 | -63 |-open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - 63 |+open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -64 64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') -65 65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) -66 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - -UP015.py:64:106: UP015 [*] Unnecessary mode argument - | -63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') - | ^^^ UP015 -65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) -66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | - = help: Remove mode argument - -ℹ Safe fix -61 61 | pass -62 62 | -63 63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -64 |-open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') - 64 |+open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -65 65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) -66 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -67 67 | - -UP015.py:65:65: UP015 [*] Unnecessary mode argument - | -63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') -65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) - | ^^^ UP015 -66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | - = help: Remove mode argument - -ℹ Safe fix -62 62 | -63 63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -64 64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') -65 |-open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) - 65 |+open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -66 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -67 67 | -68 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - -UP015.py:66:11: UP015 [*] Unnecessary mode argument - | -64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') -65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) -66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | ^^^ UP015 -67 | -68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | - = help: Remove mode argument - -ℹ Safe fix -63 63 | open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -64 64 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U') -65 65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) -66 |-open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - 66 |+open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -67 67 | -68 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -69 69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') - -UP015.py:68:23: UP015 [*] Unnecessary modes, use `rb` - | -66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -67 | -68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | ^^^^ UP015 -69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') -70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) - | - = help: Replace with `rb` - -ℹ Safe fix -65 65 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None) -66 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -67 67 | -68 |-open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - 68 |+open(file="foo", mode="rb", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -69 69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') -70 70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) -71 71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - -UP015.py:69:106: UP015 [*] Unnecessary modes, use `rb` - | -68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') - | ^^^^ UP015 -70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) -71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | - = help: Replace with `rb` - -ℹ Safe fix -66 66 | open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -67 67 | -68 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -69 |-open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') - 69 |+open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode="rb") -70 70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) -71 71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -72 72 | - -UP015.py:70:65: UP015 [*] Unnecessary modes, use `rb` - | -68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') -70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) - | ^^^^ UP015 -71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | - = help: Replace with `rb` - -ℹ Safe fix -67 67 | -68 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -69 69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') -70 |-open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) - 70 |+open(file="foo", buffering=-1, encoding=None, errors=None, mode="rb", newline=None, closefd=True, opener=None) -71 71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -72 72 | -73 73 | import aiofiles - -UP015.py:71:11: UP015 [*] Unnecessary modes, use `rb` - | -69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') -70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) -71 | open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - | ^^^^ UP015 -72 | -73 | import aiofiles - | - = help: Replace with `rb` - -ℹ Safe fix -68 68 | open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -69 69 | open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub') -70 70 | open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None) -71 |-open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - 71 |+open(mode="rb", file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -72 72 | -73 73 | import aiofiles -74 74 | - -UP015.py:75:22: UP015 [*] Unnecessary mode argument - | -73 | import aiofiles -74 | -75 | aiofiles.open("foo", "U") - | ^^^ UP015 -76 | aiofiles.open("foo", "r") -77 | aiofiles.open("foo", mode="r") - | - = help: Remove mode argument - -ℹ Safe fix -72 72 | -73 73 | import aiofiles -74 74 | -75 |-aiofiles.open("foo", "U") - 75 |+aiofiles.open("foo") -76 76 | aiofiles.open("foo", "r") -77 77 | aiofiles.open("foo", mode="r") -78 78 | - -UP015.py:76:22: UP015 [*] Unnecessary mode argument - | -75 | aiofiles.open("foo", "U") -76 | aiofiles.open("foo", "r") - | ^^^ UP015 -77 | aiofiles.open("foo", mode="r") - | - = help: Remove mode argument - -ℹ Safe fix -73 73 | import aiofiles -74 74 | -75 75 | aiofiles.open("foo", "U") -76 |-aiofiles.open("foo", "r") - 76 |+aiofiles.open("foo") -77 77 | aiofiles.open("foo", mode="r") -78 78 | -79 79 | open("foo", "r+") - -UP015.py:77:27: UP015 [*] Unnecessary mode argument - | -75 | aiofiles.open("foo", "U") -76 | aiofiles.open("foo", "r") -77 | aiofiles.open("foo", mode="r") - | ^^^ UP015 -78 | -79 | open("foo", "r+") - | - = help: Remove mode argument - -ℹ Safe fix -74 74 | -75 75 | aiofiles.open("foo", "U") -76 76 | aiofiles.open("foo", "r") -77 |-aiofiles.open("foo", mode="r") - 77 |+aiofiles.open("foo") -78 78 | -79 79 | open("foo", "r+") -80 80 | open("foo", "rb") diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP015_UP015_1.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP015_UP015_1.py.snap deleted file mode 100644 index a3f9d68f6cb431..00000000000000 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP015_UP015_1.py.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pyupgrade/mod.rs ---- -UP015_1.py:3:17: UP015 [*] Unnecessary mode argument - | -1 | # Not a valid type annotation but this test shouldn't result in a panic. -2 | # Refer: https://github.com/astral-sh/ruff/issues/11736 -3 | x: 'open("foo", "r")' - | ^^^ UP015 - | - = help: Remove mode argument - -ℹ Safe fix -1 1 | # Not a valid type annotation but this test shouldn't result in a panic. -2 2 | # Refer: https://github.com/astral-sh/ruff/issues/11736 -3 |-x: 'open("foo", "r")' - 3 |+x: 'open("foo")' -4 4 | diff --git a/crates/ruff_linter/src/rules/refurb/mod.rs b/crates/ruff_linter/src/rules/refurb/mod.rs index 964fa0100df41e..830581d49f74a3 100644 --- a/crates/ruff_linter/src/rules/refurb/mod.rs +++ b/crates/ruff_linter/src/rules/refurb/mod.rs @@ -12,7 +12,6 @@ mod tests { use test_case::test_case; use crate::registry::Rule; - use crate::settings::types::PreviewMode; use crate::test::test_path; use crate::{assert_messages, settings}; @@ -62,24 +61,6 @@ mod tests { Ok(()) } - #[test_case(Rule::TypeNoneComparison, Path::new("FURB169.py"))] - fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "preview__{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("refurb").join(path).as_path(), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } - #[test] fn write_whole_file_python_39() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs b/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs index 80ff6cc17bed71..c9a539918e01d3 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs @@ -13,9 +13,6 @@ use crate::rules::refurb::helpers::replace_with_identity_check; /// There is only ever one instance of `None`, so it is more efficient and /// readable to use the `is` operator to check if an object is `None`. /// -/// Only name expressions (e.g., `type(foo) == type(None)`) are reported. -/// In [preview], the rule will also report other kinds of expressions. -/// /// ## Example /// ```python /// type(obj) is type(None) @@ -34,8 +31,6 @@ use crate::rules::refurb::helpers::replace_with_identity_check; /// - [Python documentation: `None`](https://docs.python.org/3/library/constants.html#None) /// - [Python documentation: `type`](https://docs.python.org/3/library/functions.html#type) /// - [Python documentation: Identity comparisons](https://docs.python.org/3/reference/expressions.html#is-not) -/// -/// [preview]: https://docs.astral.sh/ruff/preview/ #[derive(ViolationMetadata)] pub(crate) struct TypeNoneComparison { replacement: IdentityCheck, @@ -80,12 +75,6 @@ pub(crate) fn type_none_comparison(checker: &Checker, compare: &ast::ExprCompare _ => return, }; - if checker.settings.preview.is_disabled() - && !matches!(other_arg, Expr::Name(_) | Expr::NoneLiteral(_)) - { - return; - } - let diagnostic = Diagnostic::new(TypeNoneComparison { replacement }, compare.range); let negate = replacement == IdentityCheck::IsNot; diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB169_FURB169.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB169_FURB169.py.snap index 3946594c8e19a1..4a711ebcfca179 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB169_FURB169.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB169_FURB169.py.snap @@ -251,4 +251,102 @@ FURB169.py:27:1: FURB169 [*] When checking against `None`, use `is not` instead 27 |+None is not None 28 28 | 29 29 | type(a.b) is type(None) -30 30 | +30 30 | + +FURB169.py:29:1: FURB169 [*] When checking against `None`, use `is` instead of comparison with `type(None)` + | +27 | type(None) != type(None) +28 | +29 | type(a.b) is type(None) + | ^^^^^^^^^^^^^^^^^^^^^^^ FURB169 +30 | +31 | type( + | + = help: Replace with `is None` + +ℹ Safe fix +26 26 | +27 27 | type(None) != type(None) +28 28 | +29 |-type(a.b) is type(None) + 29 |+a.b is None +30 30 | +31 31 | type( +32 32 | a( + +FURB169.py:31:1: FURB169 [*] When checking against `None`, use `is not` instead of comparison with `type(None)` + | +29 | type(a.b) is type(None) +30 | +31 | / type( +32 | | a( +33 | | # Comment +34 | | ) +35 | | ) != type(None) + | |_______________^ FURB169 +36 | +37 | type( + | + = help: Replace with `is not None` + +ℹ Unsafe fix +28 28 | +29 29 | type(a.b) is type(None) +30 30 | +31 |-type( +32 |- a( +33 |- # Comment +34 |- ) +35 |-) != type(None) + 31 |+a() is not None +36 32 | +37 33 | type( +38 34 | a := 1 + +FURB169.py:37:1: FURB169 [*] When checking against `None`, use `is` instead of comparison with `type(None)` + | +35 | ) != type(None) +36 | +37 | / type( +38 | | a := 1 +39 | | ) == type(None) + | |_______________^ FURB169 +40 | +41 | type( + | + = help: Replace with `is None` + +ℹ Safe fix +34 34 | ) +35 35 | ) != type(None) +36 36 | +37 |-type( +38 |- a := 1 +39 |-) == type(None) + 37 |+(a := 1) is None +40 38 | +41 39 | type( +42 40 | a for a in range(0) + +FURB169.py:41:1: FURB169 [*] When checking against `None`, use `is not` instead of comparison with `type(None)` + | +39 | ) == type(None) +40 | +41 | / type( +42 | | a for a in range(0) +43 | | ) is not type(None) + | |___________________^ FURB169 + | + = help: Replace with `is not None` + +ℹ Safe fix +38 38 | a := 1 +39 39 | ) == type(None) +40 40 | +41 |-type( +42 |- a for a in range(0) +43 |-) is not type(None) + 41 |+(a for a in range(0)) is not None +44 42 | +45 43 | +46 44 | # Ok. diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB169_FURB169.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB169_FURB169.py.snap deleted file mode 100644 index 4a711ebcfca179..00000000000000 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB169_FURB169.py.snap +++ /dev/null @@ -1,352 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/refurb/mod.rs ---- -FURB169.py:5:1: FURB169 [*] When checking against `None`, use `is` instead of comparison with `type(None)` - | -3 | # Error. -4 | -5 | type(foo) is type(None) - | ^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -6 | -7 | type(None) is type(foo) - | - = help: Replace with `is None` - -ℹ Safe fix -2 2 | -3 3 | # Error. -4 4 | -5 |-type(foo) is type(None) - 5 |+foo is None -6 6 | -7 7 | type(None) is type(foo) -8 8 | - -FURB169.py:7:1: FURB169 [*] When checking against `None`, use `is` instead of comparison with `type(None)` - | -5 | type(foo) is type(None) -6 | -7 | type(None) is type(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -8 | -9 | type(None) is type(None) - | - = help: Replace with `is None` - -ℹ Safe fix -4 4 | -5 5 | type(foo) is type(None) -6 6 | -7 |-type(None) is type(foo) - 7 |+foo is None -8 8 | -9 9 | type(None) is type(None) -10 10 | - -FURB169.py:9:1: FURB169 [*] When checking against `None`, use `is` instead of comparison with `type(None)` - | - 7 | type(None) is type(foo) - 8 | - 9 | type(None) is type(None) - | ^^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -10 | -11 | type(foo) is not type(None) - | - = help: Replace with `is None` - -ℹ Safe fix -6 6 | -7 7 | type(None) is type(foo) -8 8 | -9 |-type(None) is type(None) - 9 |+None is None -10 10 | -11 11 | type(foo) is not type(None) -12 12 | - -FURB169.py:11:1: FURB169 [*] When checking against `None`, use `is not` instead of comparison with `type(None)` - | - 9 | type(None) is type(None) -10 | -11 | type(foo) is not type(None) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -12 | -13 | type(None) is not type(foo) - | - = help: Replace with `is not None` - -ℹ Safe fix -8 8 | -9 9 | type(None) is type(None) -10 10 | -11 |-type(foo) is not type(None) - 11 |+foo is not None -12 12 | -13 13 | type(None) is not type(foo) -14 14 | - -FURB169.py:13:1: FURB169 [*] When checking against `None`, use `is not` instead of comparison with `type(None)` - | -11 | type(foo) is not type(None) -12 | -13 | type(None) is not type(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -14 | -15 | type(None) is not type(None) - | - = help: Replace with `is not None` - -ℹ Safe fix -10 10 | -11 11 | type(foo) is not type(None) -12 12 | -13 |-type(None) is not type(foo) - 13 |+foo is not None -14 14 | -15 15 | type(None) is not type(None) -16 16 | - -FURB169.py:15:1: FURB169 [*] When checking against `None`, use `is not` instead of comparison with `type(None)` - | -13 | type(None) is not type(foo) -14 | -15 | type(None) is not type(None) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -16 | -17 | type(foo) == type(None) - | - = help: Replace with `is not None` - -ℹ Safe fix -12 12 | -13 13 | type(None) is not type(foo) -14 14 | -15 |-type(None) is not type(None) - 15 |+None is not None -16 16 | -17 17 | type(foo) == type(None) -18 18 | - -FURB169.py:17:1: FURB169 [*] When checking against `None`, use `is` instead of comparison with `type(None)` - | -15 | type(None) is not type(None) -16 | -17 | type(foo) == type(None) - | ^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -18 | -19 | type(None) == type(foo) - | - = help: Replace with `is None` - -ℹ Safe fix -14 14 | -15 15 | type(None) is not type(None) -16 16 | -17 |-type(foo) == type(None) - 17 |+foo is None -18 18 | -19 19 | type(None) == type(foo) -20 20 | - -FURB169.py:19:1: FURB169 [*] When checking against `None`, use `is` instead of comparison with `type(None)` - | -17 | type(foo) == type(None) -18 | -19 | type(None) == type(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -20 | -21 | type(None) == type(None) - | - = help: Replace with `is None` - -ℹ Safe fix -16 16 | -17 17 | type(foo) == type(None) -18 18 | -19 |-type(None) == type(foo) - 19 |+foo is None -20 20 | -21 21 | type(None) == type(None) -22 22 | - -FURB169.py:21:1: FURB169 [*] When checking against `None`, use `is` instead of comparison with `type(None)` - | -19 | type(None) == type(foo) -20 | -21 | type(None) == type(None) - | ^^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -22 | -23 | type(foo) != type(None) - | - = help: Replace with `is None` - -ℹ Safe fix -18 18 | -19 19 | type(None) == type(foo) -20 20 | -21 |-type(None) == type(None) - 21 |+None is None -22 22 | -23 23 | type(foo) != type(None) -24 24 | - -FURB169.py:23:1: FURB169 [*] When checking against `None`, use `is not` instead of comparison with `type(None)` - | -21 | type(None) == type(None) -22 | -23 | type(foo) != type(None) - | ^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -24 | -25 | type(None) != type(foo) - | - = help: Replace with `is not None` - -ℹ Safe fix -20 20 | -21 21 | type(None) == type(None) -22 22 | -23 |-type(foo) != type(None) - 23 |+foo is not None -24 24 | -25 25 | type(None) != type(foo) -26 26 | - -FURB169.py:25:1: FURB169 [*] When checking against `None`, use `is not` instead of comparison with `type(None)` - | -23 | type(foo) != type(None) -24 | -25 | type(None) != type(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -26 | -27 | type(None) != type(None) - | - = help: Replace with `is not None` - -ℹ Safe fix -22 22 | -23 23 | type(foo) != type(None) -24 24 | -25 |-type(None) != type(foo) - 25 |+foo is not None -26 26 | -27 27 | type(None) != type(None) -28 28 | - -FURB169.py:27:1: FURB169 [*] When checking against `None`, use `is not` instead of comparison with `type(None)` - | -25 | type(None) != type(foo) -26 | -27 | type(None) != type(None) - | ^^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -28 | -29 | type(a.b) is type(None) - | - = help: Replace with `is not None` - -ℹ Safe fix -24 24 | -25 25 | type(None) != type(foo) -26 26 | -27 |-type(None) != type(None) - 27 |+None is not None -28 28 | -29 29 | type(a.b) is type(None) -30 30 | - -FURB169.py:29:1: FURB169 [*] When checking against `None`, use `is` instead of comparison with `type(None)` - | -27 | type(None) != type(None) -28 | -29 | type(a.b) is type(None) - | ^^^^^^^^^^^^^^^^^^^^^^^ FURB169 -30 | -31 | type( - | - = help: Replace with `is None` - -ℹ Safe fix -26 26 | -27 27 | type(None) != type(None) -28 28 | -29 |-type(a.b) is type(None) - 29 |+a.b is None -30 30 | -31 31 | type( -32 32 | a( - -FURB169.py:31:1: FURB169 [*] When checking against `None`, use `is not` instead of comparison with `type(None)` - | -29 | type(a.b) is type(None) -30 | -31 | / type( -32 | | a( -33 | | # Comment -34 | | ) -35 | | ) != type(None) - | |_______________^ FURB169 -36 | -37 | type( - | - = help: Replace with `is not None` - -ℹ Unsafe fix -28 28 | -29 29 | type(a.b) is type(None) -30 30 | -31 |-type( -32 |- a( -33 |- # Comment -34 |- ) -35 |-) != type(None) - 31 |+a() is not None -36 32 | -37 33 | type( -38 34 | a := 1 - -FURB169.py:37:1: FURB169 [*] When checking against `None`, use `is` instead of comparison with `type(None)` - | -35 | ) != type(None) -36 | -37 | / type( -38 | | a := 1 -39 | | ) == type(None) - | |_______________^ FURB169 -40 | -41 | type( - | - = help: Replace with `is None` - -ℹ Safe fix -34 34 | ) -35 35 | ) != type(None) -36 36 | -37 |-type( -38 |- a := 1 -39 |-) == type(None) - 37 |+(a := 1) is None -40 38 | -41 39 | type( -42 40 | a for a in range(0) - -FURB169.py:41:1: FURB169 [*] When checking against `None`, use `is not` instead of comparison with `type(None)` - | -39 | ) == type(None) -40 | -41 | / type( -42 | | a for a in range(0) -43 | | ) is not type(None) - | |___________________^ FURB169 - | - = help: Replace with `is not None` - -ℹ Safe fix -38 38 | a := 1 -39 39 | ) == type(None) -40 40 | -41 |-type( -42 |- a for a in range(0) -43 |-) is not type(None) - 41 |+(a for a in range(0)) is not None -44 42 | -45 43 | -46 44 | # Ok. diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 6831ca274a1a5a..1cd53a00014471 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -83,10 +83,13 @@ mod tests { #[test_case(Rule::InvalidAssertMessageLiteralArgument, Path::new("RUF040.py"))] #[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.py"))] #[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.pyi"))] + #[test_case(Rule::UnnecessaryCastToInt, Path::new("RUF046.py"))] #[test_case(Rule::NeedlessElse, Path::new("RUF047_if.py"))] #[test_case(Rule::NeedlessElse, Path::new("RUF047_for.py"))] #[test_case(Rule::NeedlessElse, Path::new("RUF047_while.py"))] #[test_case(Rule::NeedlessElse, Path::new("RUF047_try.py"))] + #[test_case(Rule::MapIntVersionParsing, Path::new("RUF048.py"))] + #[test_case(Rule::MapIntVersionParsing, Path::new("RUF048_1.py"))] #[test_case(Rule::IfKeyInDictDel, Path::new("RUF051.py"))] #[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))] #[test_case(Rule::FalsyDictGetFallback, Path::new("RUF056.py"))] @@ -113,8 +116,6 @@ mod tests { &LinterSettings { ruff: super::settings::Settings { parenthesize_tuple_in_subscript: true, - extend_markup_names: vec![], - allowed_markup_calls: vec![], }, ..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript) }, @@ -130,8 +131,6 @@ mod tests { &LinterSettings { ruff: super::settings::Settings { parenthesize_tuple_in_subscript: false, - extend_markup_names: vec![], - allowed_markup_calls: vec![], }, unresolved_target_version: PythonVersion::PY310, ..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript) @@ -423,15 +422,11 @@ mod tests { Ok(()) } - #[test_case(Rule::UnsafeMarkupUse, Path::new("RUF035.py"))] - #[test_case(Rule::MapIntVersionParsing, Path::new("RUF048.py"))] - #[test_case(Rule::MapIntVersionParsing, Path::new("RUF048_1.py"))] #[test_case(Rule::UnrawRePattern, Path::new("RUF039.py"))] #[test_case(Rule::UnrawRePattern, Path::new("RUF039_concat.py"))] #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_0.py"))] #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_1.py"))] #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_2.py"))] - #[test_case(Rule::UnnecessaryCastToInt, Path::new("RUF046.py"))] #[test_case(Rule::PytestRaisesAmbiguousPattern, Path::new("RUF043.py"))] #[test_case(Rule::UnnecessaryRound, Path::new("RUF057.py"))] #[test_case(Rule::DataclassEnum, Path::new("RUF049.py"))] @@ -457,53 +452,6 @@ mod tests { Ok(()) } - #[test_case(Rule::UnsafeMarkupUse, Path::new("RUF035_extend_markup_names.py"))] - #[test_case(Rule::UnsafeMarkupUse, Path::new("RUF035_skip_early_out.py"))] - fn extend_allowed_callable(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "extend_allow_callables__{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("ruff").join(path).as_path(), - &LinterSettings { - ruff: super::settings::Settings { - parenthesize_tuple_in_subscript: true, - extend_markup_names: vec!["webhelpers.html.literal".to_string()], - allowed_markup_calls: vec![], - }, - preview: PreviewMode::Enabled, - ..LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } - - #[test_case(Rule::UnsafeMarkupUse, Path::new("RUF035_whitelisted_markup_calls.py"))] - fn whitelisted_markup_calls(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "whitelisted_markup_calls__{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("ruff").join(path).as_path(), - &LinterSettings { - ruff: super::settings::Settings { - parenthesize_tuple_in_subscript: true, - extend_markup_names: vec![], - allowed_markup_calls: vec!["bleach.clean".to_string()], - }, - preview: PreviewMode::Enabled, - ..LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } - #[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"^_+", 1)] #[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"", 2)] fn custom_regexp_preset( diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs index 5ff849717a0e45..c343e6a97eedb2 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs @@ -6,7 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; /// ## What it does -/// Checks for invalid use of literals in assert message argument. +/// Checks for invalid use of literals in assert message arguments. /// /// ## Why is this bad? /// An assert message which is a non-string literal was likely intended diff --git a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs index 05f2391e9c24ca..ac2bcf90dc0af7 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs @@ -2,7 +2,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_text_size::Ranged; -use crate::noqa::{Codes, Directive, FileNoqaDirectives, NoqaDirectives, ParsedFileExemption}; +use crate::noqa::{Codes, Directive, FileNoqaDirectives, NoqaDirectives}; use crate::rule_redirects::get_redirect_target; /// ## What it does @@ -59,7 +59,7 @@ pub(crate) fn redirected_file_noqa( noqa_directives: &FileNoqaDirectives, ) { for line in noqa_directives.lines() { - let ParsedFileExemption::Codes(codes) = &line.parsed_file_exemption else { + let Directive::Codes(codes) = &line.parsed_file_exemption else { continue; }; diff --git a/crates/ruff_linter/src/rules/ruff/rules/unsafe_markup_use.rs b/crates/ruff_linter/src/rules/ruff/rules/unsafe_markup_use.rs index 70d4f013adf5f0..25769b88c759c9 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unsafe_markup_use.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unsafe_markup_use.rs @@ -1,13 +1,10 @@ -use ruff_python_ast::{Expr, ExprCall}; - -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::name::QualifiedName; -use ruff_python_semantic::{Modules, SemanticModel}; -use ruff_text_size::Ranged; - -use crate::{checkers::ast::Checker, settings::LinterSettings}; +/// ## Removed +/// This rule was implemented in `bandit` and has been remapped to +/// [S704](unsafe-markup-use.md) +/// /// ## What it does /// Checks for non-literal strings being passed to [`markupsafe.Markup`][markupsafe-markup]. /// @@ -18,13 +15,13 @@ use crate::{checkers::ast::Checker, settings::LinterSettings}; /// /// Instead you should interpolate the `Markup` object. /// -/// Using [`lint.ruff.extend-markup-names`] additional objects can be +/// Using [`lint.flake8-bandit.extend-markup-names`] additional objects can be /// treated like `Markup`. /// /// This rule was originally inspired by [flake8-markupsafe] but doesn't carve /// out any exceptions for i18n related calls by default. /// -/// You can use [`lint.ruff.allowed-markup-calls`] to specify exceptions. +/// You can use [`lint.flake8-bandit.allowed-markup-calls`] to specify exceptions. /// /// ## Example /// Given: @@ -65,92 +62,24 @@ use crate::{checkers::ast::Checker, settings::LinterSettings}; /// html = Markup("
").join(lines) # Safe /// ``` /// ## Options -/// - `lint.ruff.extend-markup-names` -/// - `lint.ruff.allowed-markup-calls` +/// - `lint.flake8-bandit.extend-markup-names` +/// - `lint.flake8-bandit.allowed-markup-calls` /// /// ## References -/// - [MarkupSafe](https://pypi.org/project/MarkupSafe/) -/// - [`markupsafe.Markup`](https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup) +/// - [MarkupSafe on PyPI](https://pypi.org/project/MarkupSafe/) +/// - [`markupsafe.Markup` API documentation](https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup) /// /// [markupsafe-markup]: https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup /// [flake8-markupsafe]: https://github.com/vmagamedov/flake8-markupsafe #[derive(ViolationMetadata)] -pub(crate) struct UnsafeMarkupUse { +pub(crate) struct RuffUnsafeMarkupUse { name: String, } -impl Violation for UnsafeMarkupUse { +impl Violation for RuffUnsafeMarkupUse { #[derive_message_formats] fn message(&self) -> String { - let UnsafeMarkupUse { name } = self; + let RuffUnsafeMarkupUse { name } = self; format!("Unsafe use of `{name}` detected") } } - -/// RUF035 -pub(crate) fn unsafe_markup_call(checker: &Checker, call: &ExprCall) { - if checker.settings.ruff.extend_markup_names.is_empty() - && !(checker.semantic().seen_module(Modules::MARKUPSAFE) - || checker.semantic().seen_module(Modules::FLASK)) - { - return; - } - - if !is_unsafe_call(call, checker.semantic(), checker.settings) { - return; - } - - let Some(qualified_name) = checker.semantic().resolve_qualified_name(&call.func) else { - return; - }; - - if !is_markup_call(&qualified_name, checker.settings) { - return; - } - - checker.report_diagnostic(Diagnostic::new( - UnsafeMarkupUse { - name: qualified_name.to_string(), - }, - call.range(), - )); -} - -fn is_markup_call(qualified_name: &QualifiedName, settings: &LinterSettings) -> bool { - matches!( - qualified_name.segments(), - ["markupsafe" | "flask", "Markup"] - ) || settings - .ruff - .extend_markup_names - .iter() - .map(|target| QualifiedName::from_dotted_name(target)) - .any(|target| *qualified_name == target) -} - -fn is_unsafe_call(call: &ExprCall, semantic: &SemanticModel, settings: &LinterSettings) -> bool { - // technically this could be circumvented by using a keyword argument - // but without type-inference we can't really know which keyword argument - // corresponds to the first positional argument and either way it is - // unlikely that someone will actually use a keyword argument here - // TODO: Eventually we may want to allow dynamic values, as long as they - // have a __html__ attribute, since that is part of the API - matches!(&*call.arguments.args, [first] if !first.is_string_literal_expr() && !first.is_bytes_literal_expr() && !is_whitelisted_call(first, semantic, settings)) -} - -fn is_whitelisted_call(expr: &Expr, semantic: &SemanticModel, settings: &LinterSettings) -> bool { - let Expr::Call(ExprCall { func, .. }) = expr else { - return false; - }; - - let Some(qualified_name) = semantic.resolve_qualified_name(func) else { - return false; - }; - - settings - .ruff - .allowed_markup_calls - .iter() - .map(|target| QualifiedName::from_dotted_name(target)) - .any(|target| qualified_name == target) -} diff --git a/crates/ruff_linter/src/rules/ruff/settings.rs b/crates/ruff_linter/src/rules/ruff/settings.rs index 19c8e66596b2d7..c6768121f054f2 100644 --- a/crates/ruff_linter/src/rules/ruff/settings.rs +++ b/crates/ruff_linter/src/rules/ruff/settings.rs @@ -7,8 +7,6 @@ use std::fmt; #[derive(Debug, Clone, CacheKey, Default)] pub struct Settings { pub parenthesize_tuple_in_subscript: bool, - pub extend_markup_names: Vec, - pub allowed_markup_calls: Vec, } impl fmt::Display for Settings { @@ -18,8 +16,6 @@ impl fmt::Display for Settings { namespace = "linter.ruff", fields = [ self.parenthesize_tuple_in_subscript, - self.extend_markup_names | array, - self.allowed_markup_calls | array, ] } Ok(()) diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF046_RUF046.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF046_RUF046.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF046_RUF046.py.snap rename to crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF046_RUF046.py.snap diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF048_RUF048.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF048_RUF048.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF048_RUF048.py.snap rename to crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF048_RUF048.py.snap diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF048_RUF048_1.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF048_RUF048_1.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF048_RUF048_1.py.snap rename to crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF048_RUF048_1.py.snap diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__extend_allow_callables__RUF035_RUF035_extend_markup_names.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__extend_allow_callables__RUF035_RUF035_extend_markup_names.py.snap deleted file mode 100644 index 460278d5519961..00000000000000 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__extend_allow_callables__RUF035_RUF035_extend_markup_names.py.snap +++ /dev/null @@ -1,19 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/mod.rs -snapshot_kind: text ---- -RUF035_extend_markup_names.py:5:1: RUF035 Unsafe use of `markupsafe.Markup` detected - | -4 | content = "" -5 | Markup(f"unsafe {content}") # RUF035 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035 -6 | literal(f"unsafe {content}") # RUF035 - | - -RUF035_extend_markup_names.py:6:1: RUF035 Unsafe use of `webhelpers.html.literal` detected - | -4 | content = "" -5 | Markup(f"unsafe {content}") # RUF035 -6 | literal(f"unsafe {content}") # RUF035 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035 - | diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__extend_allow_callables__RUF035_RUF035_skip_early_out.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__extend_allow_callables__RUF035_RUF035_skip_early_out.py.snap deleted file mode 100644 index 2572b25493a9dc..00000000000000 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__extend_allow_callables__RUF035_RUF035_skip_early_out.py.snap +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/mod.rs -snapshot_kind: text ---- -RUF035_skip_early_out.py:7:1: RUF035 Unsafe use of `webhelpers.html.literal` detected - | -5 | # markupsafe or flask first. -6 | content = "" -7 | literal(f"unsafe {content}") # RUF035 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035 - | diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__noqa.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__noqa.snap index 1e8d507c8460da..e56c55ecce86a3 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__noqa.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__noqa.snap @@ -1,19 +1,26 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs -snapshot_kind: text --- -noqa.py:23:5: F841 [*] Local variable `I` is assigned to but never used +noqa.py:24:5: E741 Ambiguous variable name: `I` | -21 | def f(): -22 | # Only `E741` should be ignored by the `noqa`. -23 | I = 1 # noqa: E741.F841 +22 | # Neither of these are ignored and warning is +23 | # logged to user +24 | I = 1 # noqa: E741.F841 + | ^ E741 + | + +noqa.py:24:5: F841 [*] Local variable `I` is assigned to but never used + | +22 | # Neither of these are ignored and warning is +23 | # logged to user +24 | I = 1 # noqa: E741.F841 | ^ F841 | = help: Remove assignment to unused variable `I` ℹ Unsafe fix -20 20 | 21 21 | def f(): -22 22 | # Only `E741` should be ignored by the `noqa`. -23 |- I = 1 # noqa: E741.F841 - 23 |+ pass # noqa: E741.F841 +22 22 | # Neither of these are ignored and warning is +23 23 | # logged to user +24 |- I = 1 # noqa: E741.F841 + 24 |+ pass # noqa: E741.F841 diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF035_RUF035.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF035_RUF035.py.snap deleted file mode 100644 index 1e6f5ae8115289..00000000000000 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF035_RUF035.py.snap +++ /dev/null @@ -1,59 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/mod.rs -snapshot_kind: text ---- -RUF035.py:5:1: RUF035 Unsafe use of `markupsafe.Markup` detected - | -4 | content = "" -5 | Markup(f"unsafe {content}") # RUF035 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035 -6 | flask.Markup("unsafe {}".format(content)) # RUF035 -7 | Markup("safe {}").format(content) - | - -RUF035.py:6:1: RUF035 Unsafe use of `flask.Markup` detected - | -4 | content = "" -5 | Markup(f"unsafe {content}") # RUF035 -6 | flask.Markup("unsafe {}".format(content)) # RUF035 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035 -7 | Markup("safe {}").format(content) -8 | flask.Markup(b"safe {}", encoding='utf-8').format(content) - | - -RUF035.py:10:1: RUF035 Unsafe use of `markupsafe.Markup` detected - | - 8 | flask.Markup(b"safe {}", encoding='utf-8').format(content) - 9 | escape(content) -10 | Markup(content) # RUF035 - | ^^^^^^^^^^^^^^^ RUF035 -11 | flask.Markup("unsafe %s" % content) # RUF035 -12 | Markup(object="safe") - | - -RUF035.py:11:1: RUF035 Unsafe use of `flask.Markup` detected - | - 9 | escape(content) -10 | Markup(content) # RUF035 -11 | flask.Markup("unsafe %s" % content) # RUF035 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035 -12 | Markup(object="safe") -13 | Markup(object="unsafe {}".format(content)) # Not currently detected - | - -RUF035.py:17:1: RUF035 Unsafe use of `markupsafe.Markup` detected - | -15 | # NOTE: We may be able to get rid of these false positives with red-knot -16 | # if it includes comprehensive constant expression detection/evaluation. -17 | Markup("*" * 8) # RUF035 (false positive) - | ^^^^^^^^^^^^^^^ RUF035 -18 | flask.Markup("hello {}".format("world")) # RUF035 (false positive) - | - -RUF035.py:18:1: RUF035 Unsafe use of `flask.Markup` detected - | -16 | # if it includes comprehensive constant expression detection/evaluation. -17 | Markup("*" * 8) # RUF035 (false positive) -18 | flask.Markup("hello {}".format("world")) # RUF035 (false positive) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035 - | diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__whitelisted_markup_calls__RUF035_RUF035_whitelisted_markup_calls.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__whitelisted_markup_calls__RUF035_RUF035_whitelisted_markup_calls.py.snap deleted file mode 100644 index e3f18921f3870e..00000000000000 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__whitelisted_markup_calls__RUF035_RUF035_whitelisted_markup_calls.py.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/mod.rs ---- -RUF035_whitelisted_markup_calls.py:9:1: RUF035 Unsafe use of `markupsafe.Markup` detected - | -7 | # indirect assignments are currently not supported -8 | cleaned = clean(content) -9 | Markup(cleaned) # RUF035 - | ^^^^^^^^^^^^^^^ RUF035 - | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__undefined_name.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__undefined_name.snap index d72f236dcd3c7b..88cd59f63cbf13 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__undefined_name.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__undefined_name.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/linter.rs -snapshot_kind: text --- undefined_name.ipynb:cell 3:1:7: F821 Undefined name `undefined` | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__unused_variable.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__unused_variable.snap index 90dde196f80aed..f59bd1aea2f500 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__unused_variable.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__unused_variable.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/linter.rs -snapshot_kind: text --- unused_variable.ipynb:cell 1:2:5: F841 [*] Local variable `foo1` is assigned to but never used | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap deleted file mode 100644 index d2833468243f39..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text ---- -Ok( - Some( - All, - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap deleted file mode 100644 index d2833468243f39..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text ---- -Ok( - Some( - All, - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap deleted file mode 100644 index d2833468243f39..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text ---- -Ok( - Some( - All, - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap deleted file mode 100644 index 88c0df09bc840f..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..26, - codes: [ - Code { - code: "F401", - range: 16..20, - }, - Code { - code: "F841", - range: 22..26, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap deleted file mode 100644 index 782d5edf44b237..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - All( - All { - range: 0..6, - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap deleted file mode 100644 index 782d5edf44b237..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - All( - All { - range: 0..6, - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap deleted file mode 100644 index 84d884a7fde122..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - All( - All { - range: 35..41, - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap deleted file mode 100644 index 502ca4bc6ef6ec..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - All( - All { - range: 0..7, - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap deleted file mode 100644 index ae2fb324997eeb..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - All( - All { - range: 0..5, - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap deleted file mode 100644 index 782d5edf44b237..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - All( - All { - range: 0..6, - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap deleted file mode 100644 index 83438279899b18..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap deleted file mode 100644 index 83438279899b18..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap deleted file mode 100644 index 8b10f3792a94b9..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 35..47, - codes: [ - Code { - code: "F401", - range: 43..47, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap deleted file mode 100644 index 365e539cfc39ba..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..13, - codes: [ - Code { - code: "F401", - range: 9..13, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap deleted file mode 100644 index 70c91d5d7899b6..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..10, - codes: [ - Code { - code: "F401", - range: 6..10, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap deleted file mode 100644 index 83438279899b18..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap deleted file mode 100644 index 8c67e30e0d12c0..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 14..18, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap deleted file mode 100644 index 8c67e30e0d12c0..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 14..18, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap deleted file mode 100644 index 57e9816701038b..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 35..53, - codes: [ - Code { - code: "F401", - range: 43..47, - }, - Code { - code: "F841", - range: 49..53, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap deleted file mode 100644 index 608946baaefc8b..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..20, - codes: [ - Code { - code: "F401", - range: 9..13, - }, - Code { - code: "F841", - range: 16..20, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap deleted file mode 100644 index 4a052fcde14614..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..15, - codes: [ - Code { - code: "F401", - range: 6..10, - }, - Code { - code: "F841", - range: 11..15, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap deleted file mode 100644 index 8c67e30e0d12c0..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 14..18, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap deleted file mode 100644 index 8c67e30e0d12c0..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 14..18, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap deleted file mode 100644 index f10d46ecb5857e..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..19, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 15..19, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_invalid_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_invalid_codes.snap deleted file mode 100644 index 5555a95e593817..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_invalid_codes.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Err( - MissingCodes, -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_invalid_suffix.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_invalid_suffix.snap deleted file mode 100644 index a2a1dc315f84d6..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_invalid_suffix.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Err( - InvalidSuffix, -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap deleted file mode 100644 index 2392249bda4957..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 4..16, - codes: [ - Code { - code: "F401", - range: 12..16, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap deleted file mode 100644 index 83438279899b18..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap deleted file mode 100644 index d66ab807ba6b1f..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..16, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 12..16, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap deleted file mode 100644 index 83438279899b18..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap deleted file mode 100644 index d2833468243f39..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text ---- -Ok( - Some( - All, - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap deleted file mode 100644 index d2833468243f39..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text ---- -Ok( - Some( - All, - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap deleted file mode 100644 index d2833468243f39..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text ---- -Ok( - Some( - All, - ), -) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap deleted file mode 100644 index d9533e0f5f8840..00000000000000 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text ---- -Ok( - Some( - Codes( - Codes { - range: 0..24, - codes: [ - Code { - code: "F401", - range: 14..18, - }, - Code { - code: "F841", - range: 20..24, - }, - ], - }, - ), - ), -) diff --git a/crates/ruff_python_formatter/src/other/with_item.rs b/crates/ruff_python_formatter/src/other/with_item.rs index d6f8ed9fb4db5b..c81fb0fa77cebf 100644 --- a/crates/ruff_python_formatter/src/other/with_item.rs +++ b/crates/ruff_python_formatter/src/other/with_item.rs @@ -6,7 +6,6 @@ use crate::expression::parentheses::{ is_expression_parenthesized, parenthesized, Parentheses, Parenthesize, }; use crate::prelude::*; -use crate::preview::is_with_single_target_parentheses_enabled; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum WithItemLayout { @@ -118,11 +117,7 @@ impl FormatNodeRule for FormatWithItem { // ...except if the with item is parenthesized and it's not the only with item or it has a target. // Then use the context expression as a preferred breaking point. let prefer_breaking_context_expression = - if is_with_single_target_parentheses_enabled(f.context()) { - (optional_vars.is_some() || !single) && is_parenthesized - } else { - is_parenthesized - }; + (optional_vars.is_some() || !single) && is_parenthesized; if prefer_breaking_context_expression { maybe_parenthesize_expression( diff --git a/crates/ruff_python_formatter/src/preview.rs b/crates/ruff_python_formatter/src/preview.rs index c129420ff8b836..3cc6ce46eef09e 100644 --- a/crates/ruff_python_formatter/src/preview.rs +++ b/crates/ruff_python_formatter/src/preview.rs @@ -13,10 +13,3 @@ pub(crate) const fn is_hug_parens_with_braces_and_square_brackets_enabled( ) -> bool { context.is_preview() } - -/// Returns `true` if the bugfix for single-with items with a trailing comment targeting Python 3.9 or newer is enabled. -/// -/// See [#14001](https://github.com/astral-sh/ruff/issues/14001) -pub(crate) fn is_with_single_target_parentheses_enabled(context: &PyFormatContext) -> bool { - context.is_preview() -} diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__with.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__with.py.snap index d72ffe623ab358..bb04ae95605847 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__with.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__with.py.snap @@ -771,113 +771,42 @@ if True: # Regression test for https://github.com/astral-sh/ruff/issues/14001 with ( - ( - open( - "some/path.txt", - "rb", - ) - if True - else open("other/path.txt") + open( + "some/path.txt", + "rb", ) + if True + else open("other/path.txt") # Bar ): pass with ( # trailing comment - ( - open( - "some/path.txt", - "rb", - ) - if True - else open("other/path.txt") + open( + "some/path.txt", + "rb", ) + if True + else open("other/path.txt") # Bar ): pass with ( - ( - open( - "some/path.txt", - "rb", - ) - if True - else open("other/path.txt") + open( + "some/path.txt", + "rb", ) + if True + else open("other/path.txt") # Bar ): pass ``` -#### Preview changes -```diff ---- Stable -+++ Preview -@@ -377,42 +377,36 @@ - - # Regression test for https://github.com/astral-sh/ruff/issues/14001 - with ( -- ( -- open( -- "some/path.txt", -- "rb", -- ) -- if True -- else open("other/path.txt") -+ open( -+ "some/path.txt", -+ "rb", - ) -+ if True -+ else open("other/path.txt") - # Bar - ): - pass - - - with ( # trailing comment -- ( -- open( -- "some/path.txt", -- "rb", -- ) -- if True -- else open("other/path.txt") -+ open( -+ "some/path.txt", -+ "rb", - ) -+ if True -+ else open("other/path.txt") - # Bar - ): - pass - - - with ( -- ( -- open( -- "some/path.txt", -- "rb", -- ) -- if True -- else open("other/path.txt") -+ open( -+ "some/path.txt", -+ "rb", - ) -+ if True -+ else open("other/path.txt") - # Bar - ): - pass -``` - - ### Output 2 ``` indent-style = space diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index fc801c40ca89be..2c2be80283cb0f 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -1,10 +1,10 @@ //! Analysis rules for the `typing` module. -use ruff_python_ast::helpers::{any_over_expr, is_const_false, map_subscript}; +use ruff_python_ast::helpers::{any_over_expr, map_subscript}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{ - self as ast, Expr, ExprCall, ExprName, Int, Operator, ParameterWithDefault, Parameters, Stmt, + self as ast, Expr, ExprCall, ExprName, Operator, ParameterWithDefault, Parameters, Stmt, StmtAssign, }; use ruff_python_stdlib::typing::{ @@ -391,44 +391,19 @@ pub fn is_mutable_expr(expr: &Expr, semantic: &SemanticModel) -> bool { pub fn is_type_checking_block(stmt: &ast::StmtIf, semantic: &SemanticModel) -> bool { let ast::StmtIf { test, .. } = stmt; - if semantic.use_new_type_checking_block_detection_semantics() { - return match test.as_ref() { - // As long as the symbol's name is "TYPE_CHECKING" we will treat it like `typing.TYPE_CHECKING` - // for this specific check even if it's defined somewhere else, like the current module. - // Ex) `if TYPE_CHECKING:` - Expr::Name(ast::ExprName { id, .. }) => { - id == "TYPE_CHECKING" + match test.as_ref() { + // As long as the symbol's name is "TYPE_CHECKING" we will treat it like `typing.TYPE_CHECKING` + // for this specific check even if it's defined somewhere else, like the current module. + // Ex) `if TYPE_CHECKING:` + Expr::Name(ast::ExprName { id, .. }) => { + id == "TYPE_CHECKING" // Ex) `if TC:` with `from typing import TYPE_CHECKING as TC` || semantic.match_typing_expr(test, "TYPE_CHECKING") - } - // Ex) `if typing.TYPE_CHECKING:` - Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr == "TYPE_CHECKING", - _ => false, - }; - } - - // Ex) `if False:` - if is_const_false(test) { - return true; - } - - // Ex) `if 0:` - if matches!( - test.as_ref(), - Expr::NumberLiteral(ast::ExprNumberLiteral { - value: ast::Number::Int(Int::ZERO), - .. - }) - ) { - return true; - } - - // Ex) `if typing.TYPE_CHECKING:` - if semantic.match_typing_expr(test, "TYPE_CHECKING") { - return true; + } + // Ex) `if typing.TYPE_CHECKING:` + Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr == "TYPE_CHECKING", + _ => false, } - - false } /// Returns `true` if the [`ast::StmtIf`] is a version-checking block (e.g., `if sys.version_info >= ...:`). diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 62145f4234da59..e1b25670d53a75 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -2014,18 +2014,6 @@ impl<'a> SemanticModel<'a> { .intersects(SemanticModelFlags::DEFERRED_CLASS_BASE) } - /// Return `true` if we should use the new semantics to recognize - /// type checking blocks. Previously we only recognized type checking - /// blocks if `TYPE_CHECKING` was imported from a typing module. - /// - /// With this feature flag enabled we recognize any symbol named - /// `TYPE_CHECKING`, regardless of where it comes from to mirror - /// what mypy and pyright do. - pub const fn use_new_type_checking_block_detection_semantics(&self) -> bool { - self.flags - .intersects(SemanticModelFlags::NEW_TYPE_CHECKING_BLOCK_DETECTION) - } - /// Return an iterator over all bindings shadowed by the given [`BindingId`], within the /// containing scope, and across scopes. pub fn shadowed_bindings( @@ -2557,14 +2545,6 @@ bitflags! { /// [#13824]: https://github.com/astral-sh/ruff/issues/13824 const NO_TYPE_CHECK = 1 << 30; - /// The model special-cases any symbol named `TYPE_CHECKING`. - /// - /// Previously we only recognized `TYPE_CHECKING` if it was part of - /// one of the configured `typing` modules. This flag exists to - /// test out the semantic change only in preview. This flag will go - /// away once this change has been stabilized. - const NEW_TYPE_CHECKING_BLOCK_DETECTION = 1 << 31; - /// The context is in any type annotation. const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits(); diff --git a/crates/ruff_server/src/server/api/requests/execute_command.rs b/crates/ruff_server/src/server/api/requests/execute_command.rs index be4801e099296b..2888e1c85e165c 100644 --- a/crates/ruff_server/src/server/api/requests/execute_command.rs +++ b/crates/ruff_server/src/server/api/requests/execute_command.rs @@ -38,7 +38,7 @@ impl super::RequestHandler for ExecuteCommand { impl super::SyncRequestHandler for ExecuteCommand { fn run( session: &mut Session, - notifier: client::Notifier, + _notifier: client::Notifier, requester: &mut client::Requester, params: types::ExecuteCommandParams, ) -> server::Result> { @@ -46,19 +46,16 @@ impl super::SyncRequestHandler for ExecuteCommand { .with_failure_code(ErrorCode::InvalidParams)?; if command == SupportedCommand::Debug { + // TODO: Currently we only use the first argument i.e., the first document that's + // provided but we could expand this to consider all *open* documents. let argument: DebugCommandArgument = params.arguments.into_iter().next().map_or_else( || Ok(DebugCommandArgument::default()), |value| serde_json::from_value(value).with_failure_code(ErrorCode::InvalidParams), )?; - let output = debug_information(session, argument.text_document) - .with_failure_code(ErrorCode::InternalError)?; - notifier - .notify::(types::LogMessageParams { - message: output.clone(), - typ: types::MessageType::INFO, - }) - .with_failure_code(ErrorCode::InternalError)?; - return Ok(Some(serde_json::Value::String(output))); + return Ok(Some(serde_json::Value::String( + debug_information(session, argument.text_document) + .with_failure_code(ErrorCode::InternalError)?, + ))); } // check if we can apply a workspace edit diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 49d63b40c630c4..260f4edc689869 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -235,13 +235,6 @@ impl Configuration { let rules = lint.as_rule_table(lint_preview)?; - let flake8_builtins = lint - .flake8_builtins - .map(|builtins| builtins.into_settings(lint_preview)) - .unwrap_or_else(|| { - ruff_linter::rules::flake8_builtins::settings::Settings::new(lint_preview) - }); - // LinterSettings validation let isort = lint .isort @@ -334,7 +327,7 @@ impl Configuration { .unwrap_or_default(), flake8_bandit: lint .flake8_bandit - .map(Flake8BanditOptions::into_settings) + .map(|flake8_bandit| flake8_bandit.into_settings(lint.ruff.as_ref())) .unwrap_or_default(), flake8_boolean_trap: lint .flake8_boolean_trap @@ -344,7 +337,10 @@ impl Configuration { .flake8_bugbear .map(Flake8BugbearOptions::into_settings) .unwrap_or_default(), - flake8_builtins, + flake8_builtins: lint + .flake8_builtins + .map(Flake8BuiltinsOptions::into_settings) + .unwrap_or_default(), flake8_comprehensions: lint .flake8_comprehensions .map(Flake8ComprehensionsOptions::into_settings) diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 01d9ada89cf3c8..1a9bb972b5abbc 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -28,7 +28,7 @@ use ruff_linter::rules::{ pycodestyle, pydoclint, pydocstyle, pyflakes, pylint, pyupgrade, ruff, }; use ruff_linter::settings::types::{ - IdentifierPattern, OutputFormat, PreviewMode, PythonVersion, RequiredVersion, + IdentifierPattern, OutputFormat, PythonVersion, RequiredVersion, }; use ruff_linter::{warn_user_once, RuleSelector}; use ruff_macros::{CombineOptions, OptionsMetadata}; @@ -1070,10 +1070,57 @@ pub struct Flake8BanditOptions { example = "check-typed-exception = true" )] pub check_typed_exception: Option, + + /// A list of additional callable names that behave like + /// [`markupsafe.Markup`](https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup). + /// + /// Expects to receive a list of fully-qualified names (e.g., `webhelpers.html.literal`, rather than + /// `literal`). + #[option( + default = "[]", + value_type = "list[str]", + example = "extend-markup-names = [\"webhelpers.html.literal\", \"my_package.Markup\"]" + )] + pub extend_markup_names: Option>, + + /// A list of callable names, whose result may be safely passed into + /// [`markupsafe.Markup`](https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup). + /// + /// Expects to receive a list of fully-qualified names (e.g., `bleach.clean`, rather than `clean`). + /// + /// This setting helps you avoid false positives in code like: + /// + /// ```python + /// from bleach import clean + /// from markupsafe import Markup + /// + /// cleaned_markup = Markup(clean(some_user_input)) + /// ``` + /// + /// Where the use of [`bleach.clean`](https://bleach.readthedocs.io/en/latest/clean.html) + /// usually ensures that there's no XSS vulnerability. + /// + /// Although it is not recommended, you may also use this setting to whitelist other + /// kinds of calls, e.g. calls to i18n translation functions, where how safe that is + /// will depend on the implementation and how well the translations are audited. + /// + /// Another common use-case is to wrap the output of functions that generate markup + /// like [`xml.etree.ElementTree.tostring`](https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring) + /// or template rendering engines where sanitization of potential user input is either + /// already baked in or has to happen before rendering. + #[option( + default = "[]", + value_type = "list[str]", + example = "allowed-markup-calls = [\"bleach.clean\", \"my_package.sanitize\"]" + )] + pub allowed_markup_calls: Option>, } impl Flake8BanditOptions { - pub fn into_settings(self) -> ruff_linter::rules::flake8_bandit::settings::Settings { + pub fn into_settings( + self, + ruff_options: Option<&RuffOptions>, + ) -> ruff_linter::rules::flake8_bandit::settings::Settings { ruff_linter::rules::flake8_bandit::settings::Settings { hardcoded_tmp_directory: self .hardcoded_tmp_directory @@ -1082,6 +1129,20 @@ impl Flake8BanditOptions { .chain(self.hardcoded_tmp_directory_extend.unwrap_or_default()) .collect(), check_typed_exception: self.check_typed_exception.unwrap_or(false), + extend_markup_names: self + .extend_markup_names + .or_else(|| { + #[allow(deprecated)] + ruff_options.and_then(|options| options.extend_markup_names.clone()) + }) + .unwrap_or_default(), + allowed_markup_calls: self + .allowed_markup_calls + .or_else(|| { + #[allow(deprecated)] + ruff_options.and_then(|options| options.allowed_markup_calls.clone()) + }) + .unwrap_or_default(), } } } @@ -1152,45 +1213,98 @@ impl Flake8BugbearOptions { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Flake8BuiltinsOptions { + /// DEPRECATED: This option has been renamed to `ignorelist`. Use `ignorelist` instead. + /// + /// Ignore list of builtins. + /// + /// This option is ignored if both `ignorelist` and `builtins-ignorelist` are set. #[option( default = r#"[]"#, value_type = "list[str]", example = "builtins-ignorelist = [\"id\"]" )] - /// Ignore list of builtins. + #[deprecated( + since = "0.10.0", + note = "`builtins-ignorelist` has been renamed to `ignorelist`. Use that instead." + )] pub builtins_ignorelist: Option>, + + /// Ignore list of builtins. #[option( default = r#"[]"#, value_type = "list[str]", - example = "builtins-allowed-modules = [\"secrets\"]" + example = "ignorelist = [\"id\"]" )] + pub ignorelist: Option>, + + /// DEPRECATED: This option has been renamed to `allowed-modules`. Use `allowed-modules` instead. + /// /// List of builtin module names to allow. + /// + /// This option is ignored if both `allowed-modules` and `builtins-allowed-modules` are set. + #[option( + default = r#"[]"#, + value_type = "list[str]", + example = "builtins-allowed-modules = [\"secrets\"]" + )] + #[deprecated( + since = "0.10.0", + note = "`builtins-allowed-modules` has been renamed to `allowed-modules`. Use that instead." + )] pub builtins_allowed_modules: Option>, + + /// List of builtin module names to allow. #[option( - default = r#"true"#, + default = r#"[]"#, + value_type = "list[str]", + example = "allowed-modules = [\"secrets\"]" + )] + pub allowed_modules: Option>, + + /// DEPRECATED: This option has been renamed to `strict-checking`. Use `strict-checking` instead. + /// + /// Compare module names instead of full module paths. + /// + /// This option is ignored if both `strict-checking` and `builtins-strict-checking` are set. + #[option( + default = r#"false"#, value_type = "bool", - example = "builtins-strict-checking = false" + example = "builtins-strict-checking = true" )] + #[deprecated( + since = "0.10.0", + note = "`builtins-strict-checking` has been renamed to `strict-checking`. Use that instead." + )] + pub builtins_strict_checking: Option, + /// Compare module names instead of full module paths. /// /// Used by [`A005` - `stdlib-module-shadowing`](https://docs.astral.sh/ruff/rules/stdlib-module-shadowing/). - /// - /// In preview mode the default value is `false` rather than `true`. - pub builtins_strict_checking: Option, + #[option( + default = r#"false"#, + value_type = "bool", + example = "strict-checking = true" + )] + pub strict_checking: Option, } impl Flake8BuiltinsOptions { - pub fn into_settings( - self, - preview: PreviewMode, - ) -> ruff_linter::rules::flake8_builtins::settings::Settings { + pub fn into_settings(self) -> ruff_linter::rules::flake8_builtins::settings::Settings { + #[allow(deprecated)] ruff_linter::rules::flake8_builtins::settings::Settings { - builtins_ignorelist: self.builtins_ignorelist.unwrap_or_default(), - builtins_allowed_modules: self.builtins_allowed_modules.unwrap_or_default(), - builtins_strict_checking: self - .builtins_strict_checking + ignorelist: self + .ignorelist + .or(self.builtins_ignorelist) + .unwrap_or_default(), + allowed_modules: self + .allowed_modules + .or(self.builtins_allowed_modules) + .unwrap_or_default(), + strict_checking: self + .strict_checking + .or(self.builtins_strict_checking) // use the old default of true on non-preview - .unwrap_or(preview.is_disabled()), + .unwrap_or_default(), } } } @@ -3279,6 +3393,10 @@ pub struct RuffOptions { value_type = "list[str]", example = "extend-markup-names = [\"webhelpers.html.literal\", \"my_package.Markup\"]" )] + #[deprecated( + since = "0.10.0", + note = "The `extend-markup-names` option has been moved to the `flake8-bandit` section of the configuration." + )] pub extend_markup_names: Option>, /// A list of callable names, whose result may be safely passed into @@ -3311,6 +3429,10 @@ pub struct RuffOptions { value_type = "list[str]", example = "allowed-markup-calls = [\"bleach.clean\", \"my_package.sanitize\"]" )] + #[deprecated( + since = "0.10.0", + note = "The `allowed-markup-names` option has been moved to the `flake8-bandit` section of the configuration." + )] pub allowed_markup_calls: Option>, } @@ -3320,8 +3442,6 @@ impl RuffOptions { parenthesize_tuple_in_subscript: self .parenthesize_tuple_in_subscript .unwrap_or_default(), - extend_markup_names: self.extend_markup_names.unwrap_or_default(), - allowed_markup_calls: self.allowed_markup_calls.unwrap_or_default(), } } } diff --git a/crates/ruff_workspace/src/pyproject.rs b/crates/ruff_workspace/src/pyproject.rs index 75d11cf38fe4b2..aa92b186bd4841 100644 --- a/crates/ruff_workspace/src/pyproject.rs +++ b/crates/ruff_workspace/src/pyproject.rs @@ -212,7 +212,7 @@ mod tests { use ruff_linter::line_width::LineLength; use ruff_linter::settings::types::PatternPrefixPair; - use crate::options::{LintCommonOptions, LintOptions, Options}; + use crate::options::{Flake8BuiltinsOptions, LintCommonOptions, LintOptions, Options}; use crate::pyproject::{find_settings_toml, parse_pyproject_toml, Pyproject, Tools}; #[test] @@ -323,6 +323,55 @@ ignore = ["E501"] }) ); + let pyproject: Pyproject = toml::from_str( + r#" +[tool.ruff.lint.flake8-builtins] +builtins-allowed-modules = ["asyncio"] +builtins-ignorelist = ["argparse", 'typing'] +builtins-strict-checking = true +allowed-modules = ['sys'] +ignorelist = ["os", 'io'] +strict-checking = false +"#, + )?; + + #[allow(deprecated)] + let expected = Flake8BuiltinsOptions { + builtins_allowed_modules: Some(vec!["asyncio".to_string()]), + allowed_modules: Some(vec!["sys".to_string()]), + + builtins_ignorelist: Some(vec!["argparse".to_string(), "typing".to_string()]), + ignorelist: Some(vec!["os".to_string(), "io".to_string()]), + + builtins_strict_checking: Some(true), + strict_checking: Some(false), + }; + + assert_eq!( + pyproject.tool, + Some(Tools { + ruff: Some(Options { + lint: Some(LintOptions { + common: LintCommonOptions { + flake8_builtins: Some(expected.clone()), + ..LintCommonOptions::default() + }, + ..LintOptions::default() + }), + ..Options::default() + }) + }) + ); + + let settings = expected.into_settings(); + + assert_eq!(settings.allowed_modules, vec!["sys".to_string()]); + assert_eq!( + settings.ignorelist, + vec!["os".to_string(), "io".to_string()] + ); + assert!(!settings.strict_checking); + assert!(toml::from_str::( r" [tool.black] diff --git a/ruff.schema.json b/ruff.schema.json index 05d88ec10aa95a..919189643efc3a 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -947,6 +947,16 @@ "description": "Options for the `flake8-bandit` plugin.", "type": "object", "properties": { + "allowed-markup-calls": { + "description": "A list of callable names, whose result may be safely passed into [`markupsafe.Markup`](https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup).\n\nExpects to receive a list of fully-qualified names (e.g., `bleach.clean`, rather than `clean`).\n\nThis setting helps you avoid false positives in code like:\n\n```python from bleach import clean from markupsafe import Markup\n\ncleaned_markup = Markup(clean(some_user_input)) ```\n\nWhere the use of [`bleach.clean`](https://bleach.readthedocs.io/en/latest/clean.html) usually ensures that there's no XSS vulnerability.\n\nAlthough it is not recommended, you may also use this setting to whitelist other kinds of calls, e.g. calls to i18n translation functions, where how safe that is will depend on the implementation and how well the translations are audited.\n\nAnother common use-case is to wrap the output of functions that generate markup like [`xml.etree.ElementTree.tostring`](https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring) or template rendering engines where sanitization of potential user input is either already baked in or has to happen before rendering.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "check-typed-exception": { "description": "Whether to disallow `try`-`except`-`pass` (`S110`) for specific exception types. By default, `try`-`except`-`pass` is only disallowed for `Exception` and `BaseException`.", "type": [ @@ -954,6 +964,16 @@ "null" ] }, + "extend-markup-names": { + "description": "A list of additional callable names that behave like [`markupsafe.Markup`](https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup).\n\nExpects to receive a list of fully-qualified names (e.g., `webhelpers.html.literal`, rather than `literal`).", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "hardcoded-tmp-directory": { "description": "A list of directories to consider temporary (see `S108`).", "type": [ @@ -1015,7 +1035,7 @@ "description": "Options for the `flake8-builtins` plugin.", "type": "object", "properties": { - "builtins-allowed-modules": { + "allowed-modules": { "description": "List of builtin module names to allow.", "type": [ "array", @@ -1025,8 +1045,20 @@ "type": "string" } }, + "builtins-allowed-modules": { + "description": "DEPRECATED: This option has been renamed to `allowed-modules`. Use `allowed-modules` instead.\n\nList of builtin module names to allow.\n\nThis option is ignored if both `allowed-modules` and `builtins-allowed-modules` are set.", + "deprecated": true, + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "builtins-ignorelist": { - "description": "Ignore list of builtins.", + "description": "DEPRECATED: This option has been renamed to `ignorelist`. Use `ignorelist` instead.\n\nIgnore list of builtins.\n\nThis option is ignored if both `ignorelist` and `builtins-ignorelist` are set.", + "deprecated": true, "type": [ "array", "null" @@ -1036,7 +1068,25 @@ } }, "builtins-strict-checking": { - "description": "Compare module names instead of full module paths.\n\nUsed by [`A005` - `stdlib-module-shadowing`](https://docs.astral.sh/ruff/rules/stdlib-module-shadowing/).\n\nIn preview mode the default value is `false` rather than `true`.", + "description": "DEPRECATED: This option has been renamed to `strict-checking`. Use `strict-checking` instead.\n\nCompare module names instead of full module paths.\n\nThis option is ignored if both `strict-checking` and `builtins-strict-checking` are set.", + "deprecated": true, + "type": [ + "boolean", + "null" + ] + }, + "ignorelist": { + "description": "Ignore list of builtins.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "strict-checking": { + "description": "Compare module names instead of full module paths.\n\nUsed by [`A005` - `stdlib-module-shadowing`](https://docs.astral.sh/ruff/rules/stdlib-module-shadowing/).", "type": [ "boolean", "null" @@ -2847,6 +2897,7 @@ "properties": { "allowed-markup-calls": { "description": "A list of callable names, whose result may be safely passed into [`markupsafe.Markup`](https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup).\n\nExpects to receive a list of fully-qualified names (e.g., `bleach.clean`, rather than `clean`).\n\nThis setting helps you avoid false positives in code like:\n\n```python from bleach import clean from markupsafe import Markup\n\ncleaned_markup = Markup(clean(some_user_input)) ```\n\nWhere the use of [`bleach.clean`](https://bleach.readthedocs.io/en/latest/clean.html) usually ensures that there's no XSS vulnerability.\n\nAlthough it is not recommended, you may also use this setting to whitelist other kinds of calls, e.g. calls to i18n translation functions, where how safe that is will depend on the implementation and how well the translations are audited.\n\nAnother common use-case is to wrap the output of functions that generate markup like [`xml.etree.ElementTree.tostring`](https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring) or template rendering engines where sanitization of potential user input is either already baked in or has to happen before rendering.", + "deprecated": true, "type": [ "array", "null" @@ -2857,6 +2908,7 @@ }, "extend-markup-names": { "description": "A list of additional callable names that behave like [`markupsafe.Markup`](https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup).\n\nExpects to receive a list of fully-qualified names (e.g., `webhelpers.html.literal`, rather than `literal`).", + "deprecated": true, "type": [ "array", "null" @@ -3949,7 +4001,6 @@ "RUF032", "RUF033", "RUF034", - "RUF035", "RUF036", "RUF037", "RUF038", @@ -4071,6 +4122,7 @@ "S70", "S701", "S702", + "S704", "SIM", "SIM1", "SIM10",