diff --git a/README.md b/README.md index 8b1f904ff7..8cccc5005f 100644 --- a/README.md +++ b/README.md @@ -545,7 +545,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI. | F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | | | F622 | TwoStarredExpressions | Two starred expressions in assignment | | | F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | | -| F632 | IsLiteral | Use `==` and `!=` to compare constant literals | 🛠 | +| F632 | IsLiteral | Use `==` to compare constant literals | 🛠 | | F633 | InvalidPrintSyntax | Use of `>>` is invalid with `print` function | | | F634 | IfTuple | If test is a tuple, which is always `True` | | | F701 | BreakOutsideLoop | `break` outside loop | | @@ -578,7 +578,7 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI | E714 | NotIsTest | Test for object identity should be `is not` | 🛠 | | E721 | TypeComparison | Do not compare types, use `isinstance()` | | | E722 | DoNotUseBareExcept | Do not use bare `except` | | -| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def | 🛠 | +| E731 | DoNotAssignLambda | Do not assign a `lambda` expression, use a `def` | 🛠 | | E741 | AmbiguousVariableName | Ambiguous variable name: `...` | | | E742 | AmbiguousClassName | Ambiguous class name: `...` | | | E743 | AmbiguousFunctionName | Ambiguous function name: `...` | | @@ -663,7 +663,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI. | ---- | ---- | ------- | --- | | UP001 | UselessMetaclassType | `__metaclass__ = type` is implied | 🛠 | | UP003 | TypeOfPrimitive | Use `str` instead of `type(...)` | 🛠 | -| UP004 | UselessObjectInheritance | Class `...` inherits from object | 🛠 | +| UP004 | UselessObjectInheritance | Class `...` inherits from `object` | 🛠 | | UP005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` | 🛠 | | UP006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 | | UP007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 | @@ -677,7 +677,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI. | UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 | | UP016 | RemoveSixCompat | Unnecessary `six` compatibility usage | 🛠 | | UP017 | DatetimeTimezoneUTC | Use `datetime.UTC` alias | 🛠 | -| UP018 | NativeLiterals | Unnecessary call to `str` and `bytes` | 🛠 | +| UP018 | NativeLiterals | Unnecessary call to `str` | 🛠 | | UP019 | TypingTextStrAlias | `typing.Text` is deprecated, use `str` | 🛠 | | UP020 | OpenAlias | Use builtin `open` | 🛠 | | UP021 | ReplaceUniversalNewlines | `universal_newlines` is deprecated, use `text` | 🛠 | @@ -1018,7 +1018,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI. | ---- | ---- | ------- | --- | | RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 | | RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 | -| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | | +| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 | | RUF004 | KeywordArgumentBeforeStarArgument | Keyword argument `...` must come after starred arguments | | | RUF100 | UnusedNOQA | Unused blanket `noqa` directive | 🛠 | diff --git a/pyproject.toml b/foo/pyproject.toml similarity index 100% rename from pyproject.toml rename to foo/pyproject.toml diff --git a/playground/src/Editor/SourceEditor.tsx b/playground/src/Editor/SourceEditor.tsx index 11e326e015..c6d86bc301 100644 --- a/playground/src/Editor/SourceEditor.tsx +++ b/playground/src/Editor/SourceEditor.tsx @@ -56,7 +56,9 @@ export default function SourceEditor({ .filter((check) => position.startLineNumber === check.location.row) .filter((check) => check.fix) .map((check) => ({ - title: `Fix ${check.code}`, + title: check.fix + ? check.fix.message ?? `Fix ${check.code}` + : "Autofix", id: `fix-${check.code}`, kind: "quickfix", edit: check.fix diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index 7f010cbae6..7de51a6c69 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -3735,9 +3735,10 @@ impl<'a> Checker<'a> { None }; + let multiple = unused_imports.len() > 1; for (full_name, range) in unused_imports { let mut check = Check::new( - CheckKind::UnusedImport(full_name.clone(), ignore_init), + CheckKind::UnusedImport(full_name.clone(), ignore_init, multiple), *range, ); if matches!(child.node, StmtKind::ImportFrom { .. }) @@ -3756,9 +3757,10 @@ impl<'a> Checker<'a> { .sorted_by_key(|((defined_by, _), _)| defined_by.0.location) { let child = defined_by.0; + let multiple = unused_imports.len() > 1; for (full_name, range) in unused_imports { let mut check = Check::new( - CheckKind::UnusedImport(full_name.clone(), ignore_init), + CheckKind::UnusedImport(full_name.clone(), ignore_init, multiple), *range, ); if matches!(child.node, StmtKind::ImportFrom { .. }) diff --git a/src/checks.rs b/src/checks.rs index f6bf170f61..6e3f1faef1 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -3,6 +3,7 @@ use std::fmt; use itertools::Itertools; use once_cell::sync::Lazy; use rustc_hash::FxHashMap; +use rustpython_ast::Cmpop; use rustpython_parser::ast::Location; use serde::{Deserialize, Serialize}; use strum_macros::{AsRefStr, Display, EnumIter, EnumString}; @@ -618,11 +619,37 @@ pub enum LintSource { } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum RejectedCmpop { +pub enum EqCmpop { Eq, NotEq, } +impl From<&Cmpop> for EqCmpop { + fn from(cmpop: &Cmpop) -> Self { + match cmpop { + Cmpop::Eq => EqCmpop::Eq, + Cmpop::NotEq => EqCmpop::NotEq, + _ => unreachable!("Expected Cmpop::Eq | Cmpop::NotEq"), + } + } +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum IsCmpop { + Is, + IsNot, +} + +impl From<&Cmpop> for IsCmpop { + fn from(cmpop: &Cmpop) -> Self { + match cmpop { + Cmpop::Is => IsCmpop::Is, + Cmpop::IsNot => IsCmpop::IsNot, + _ => unreachable!("Expected Cmpop::Is | Cmpop::IsNot"), + } + } +} + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum DeferralKeyword { Yield, @@ -655,6 +682,21 @@ impl fmt::Display for Branch { } } +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum LiteralType { + Str, + Bytes, +} + +impl fmt::Display for LiteralType { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + LiteralType::Str => fmt.write_str("str"), + LiteralType::Bytes => fmt.write_str("bytes"), + } + } +} + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct UnusedCodes { pub unknown: Vec, @@ -668,17 +710,17 @@ pub enum CheckKind { AmbiguousClassName(String), AmbiguousFunctionName(String), AmbiguousVariableName(String), - DoNotAssignLambda, + DoNotAssignLambda(String), DoNotUseBareExcept, IOError(String), LineTooLong(usize, usize), ModuleImportNotAtTopOfFile, MultipleImportsOnOneLine, - NoneComparison(RejectedCmpop), + NoneComparison(EqCmpop), NotInTest, NotIsTest, SyntaxError(String), - TrueFalseComparison(bool, RejectedCmpop), + TrueFalseComparison(bool, EqCmpop), TypeComparison, // pycodestyle warnings NoNewLineAtEndOfFile, @@ -699,7 +741,7 @@ pub enum CheckKind { ImportStarUsage(String, Vec), ImportStarUsed(String), InvalidPrintSyntax, - IsLiteral, + IsLiteral(IsCmpop), LateFutureImport, MultiValueRepeatedKeyLiteral, MultiValueRepeatedKeyVariable(String), @@ -725,7 +767,7 @@ pub enum CheckKind { UndefinedLocal(String), UnusedAnnotation(String), UndefinedName(String), - UnusedImport(String, bool), + UnusedImport(String, bool, bool), UnusedVariable(String), YieldOutsideFunction(DeferralKeyword), // pylint @@ -859,10 +901,10 @@ pub enum CheckKind { UnnecessaryEncodeUTF8, ConvertTypedDictFunctionalToClass(String), ConvertNamedTupleFunctionalToClass(String), - RedundantOpenModes, + RedundantOpenModes(Option), RemoveSixCompat, DatetimeTimezoneUTC, - NativeLiterals, + NativeLiterals(LiteralType), OpenAlias, ReplaceUniversalNewlines, ReplaceStdoutStderr, @@ -1030,13 +1072,13 @@ impl CheckCode { CheckCode::E401 => CheckKind::MultipleImportsOnOneLine, CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile, CheckCode::E501 => CheckKind::LineTooLong(89, 88), - CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq), - CheckCode::E712 => CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq), + CheckCode::E711 => CheckKind::NoneComparison(EqCmpop::Eq), + CheckCode::E712 => CheckKind::TrueFalseComparison(true, EqCmpop::Eq), CheckCode::E713 => CheckKind::NotInTest, CheckCode::E714 => CheckKind::NotIsTest, CheckCode::E721 => CheckKind::TypeComparison, CheckCode::E722 => CheckKind::DoNotUseBareExcept, - CheckCode::E731 => CheckKind::DoNotAssignLambda, + CheckCode::E731 => CheckKind::DoNotAssignLambda("...".to_string()), CheckCode::E741 => CheckKind::AmbiguousVariableName("...".to_string()), CheckCode::E742 => CheckKind::AmbiguousClassName("...".to_string()), CheckCode::E743 => CheckKind::AmbiguousFunctionName("...".to_string()), @@ -1046,7 +1088,7 @@ impl CheckCode { CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile, CheckCode::W605 => CheckKind::InvalidEscapeSequence('c'), // pyflakes - CheckCode::F401 => CheckKind::UnusedImport("...".to_string(), false), + CheckCode::F401 => CheckKind::UnusedImport("...".to_string(), false, false), CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1), CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()), CheckCode::F404 => CheckKind::LateFutureImport, @@ -1079,7 +1121,7 @@ impl CheckCode { CheckCode::F621 => CheckKind::ExpressionsInStarAssignment, CheckCode::F622 => CheckKind::TwoStarredExpressions, CheckCode::F631 => CheckKind::AssertTuple, - CheckCode::F632 => CheckKind::IsLiteral, + CheckCode::F632 => CheckKind::IsLiteral(IsCmpop::Is), CheckCode::F633 => CheckKind::InvalidPrintSyntax, CheckCode::F634 => CheckKind::IfTuple, CheckCode::F701 => CheckKind::BreakOutsideLoop, @@ -1253,10 +1295,10 @@ impl CheckCode { CheckCode::UP012 => CheckKind::UnnecessaryEncodeUTF8, CheckCode::UP013 => CheckKind::ConvertTypedDictFunctionalToClass("...".to_string()), CheckCode::UP014 => CheckKind::ConvertNamedTupleFunctionalToClass("...".to_string()), - CheckCode::UP015 => CheckKind::RedundantOpenModes, + CheckCode::UP015 => CheckKind::RedundantOpenModes(None), CheckCode::UP016 => CheckKind::RemoveSixCompat, CheckCode::UP017 => CheckKind::DatetimeTimezoneUTC, - CheckCode::UP018 => CheckKind::NativeLiterals, + CheckCode::UP018 => CheckKind::NativeLiterals(LiteralType::Str), CheckCode::UP019 => CheckKind::TypingTextStrAlias, CheckCode::UP020 => CheckKind::OpenAlias, CheckCode::UP021 => CheckKind::ReplaceUniversalNewlines, @@ -1724,7 +1766,7 @@ impl CheckKind { CheckKind::BreakOutsideLoop => &CheckCode::F701, CheckKind::ContinueOutsideLoop => &CheckCode::F702, CheckKind::DefaultExceptNotLast => &CheckCode::F707, - CheckKind::DoNotAssignLambda => &CheckCode::E731, + CheckKind::DoNotAssignLambda(..) => &CheckCode::E731, CheckKind::DoNotUseBareExcept => &CheckCode::E722, CheckKind::DuplicateArgumentName => &CheckCode::F831, CheckKind::FStringMissingPlaceholders => &CheckCode::F541, @@ -1737,7 +1779,7 @@ impl CheckKind { CheckKind::ImportStarUsage(..) => &CheckCode::F405, CheckKind::ImportStarUsed(..) => &CheckCode::F403, CheckKind::InvalidPrintSyntax => &CheckCode::F633, - CheckKind::IsLiteral => &CheckCode::F632, + CheckKind::IsLiteral(..) => &CheckCode::F632, CheckKind::LateFutureImport => &CheckCode::F404, CheckKind::LineTooLong(..) => &CheckCode::E501, CheckKind::MultipleImportsOnOneLine => &CheckCode::E401, @@ -1909,10 +1951,10 @@ impl CheckKind { CheckKind::UnnecessaryEncodeUTF8 => &CheckCode::UP012, CheckKind::ConvertTypedDictFunctionalToClass(..) => &CheckCode::UP013, CheckKind::ConvertNamedTupleFunctionalToClass(..) => &CheckCode::UP014, - CheckKind::RedundantOpenModes => &CheckCode::UP015, + CheckKind::RedundantOpenModes(..) => &CheckCode::UP015, CheckKind::RemoveSixCompat => &CheckCode::UP016, CheckKind::DatetimeTimezoneUTC => &CheckCode::UP017, - CheckKind::NativeLiterals => &CheckCode::UP018, + CheckKind::NativeLiterals(..) => &CheckCode::UP018, CheckKind::TypingTextStrAlias => &CheckCode::UP019, CheckKind::OpenAlias => &CheckCode::UP020, CheckKind::ReplaceUniversalNewlines => &CheckCode::UP021, @@ -2068,8 +2110,8 @@ impl CheckKind { CheckKind::DefaultExceptNotLast => { "An `except` block as not the last exception handler".to_string() } - CheckKind::DoNotAssignLambda => { - "Do not assign a lambda expression, use a def".to_string() + CheckKind::DoNotAssignLambda(..) => { + "Do not assign a `lambda` expression, use a `def`".to_string() } CheckKind::DoNotUseBareExcept => "Do not use bare `except`".to_string(), CheckKind::DuplicateArgumentName => { @@ -2105,7 +2147,10 @@ impl CheckKind { .join(", "); format!("`{name}` may be undefined, or defined from star imports: {sources}") } - CheckKind::IsLiteral => "Use `==` and `!=` to compare constant literals".to_string(), + CheckKind::IsLiteral(cmpop) => match cmpop { + IsCmpop::Is => "Use `==` to compare constant literals".to_string(), + IsCmpop::IsNot => "Use `!=` to compare constant literals".to_string(), + }, CheckKind::LateFutureImport => { "`from __future__` imports must occur at the beginning of the file".to_string() } @@ -2123,10 +2168,8 @@ impl CheckKind { format!("Dictionary key `{name}` repeated") } CheckKind::NoneComparison(op) => match op { - RejectedCmpop::Eq => "Comparison to `None` should be `cond is None`".to_string(), - RejectedCmpop::NotEq => { - "Comparison to `None` should be `cond is not None`".to_string() - } + EqCmpop::Eq => "Comparison to `None` should be `cond is None`".to_string(), + EqCmpop::NotEq => "Comparison to `None` should be `cond is not None`".to_string(), }, CheckKind::NotInTest => "Test for membership should be `not in`".to_string(), CheckKind::NotIsTest => "Test for object identity should be `is not`".to_string(), @@ -2190,16 +2233,16 @@ impl CheckKind { CheckKind::ExpressionsInStarAssignment => { "Too many expressions in star-unpacking assignment".to_string() } - CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq) => { + CheckKind::TrueFalseComparison(true, EqCmpop::Eq) => { "Comparison to `True` should be `cond is True`".to_string() } - CheckKind::TrueFalseComparison(true, RejectedCmpop::NotEq) => { + CheckKind::TrueFalseComparison(true, EqCmpop::NotEq) => { "Comparison to `True` should be `cond is not True`".to_string() } - CheckKind::TrueFalseComparison(false, RejectedCmpop::Eq) => { + CheckKind::TrueFalseComparison(false, EqCmpop::Eq) => { "Comparison to `False` should be `cond is False`".to_string() } - CheckKind::TrueFalseComparison(false, RejectedCmpop::NotEq) => { + CheckKind::TrueFalseComparison(false, EqCmpop::NotEq) => { "Comparison to `False` should be `cond is not False`".to_string() } CheckKind::TwoStarredExpressions => "Two starred expressions in assignment".to_string(), @@ -2216,7 +2259,7 @@ impl CheckKind { CheckKind::UnusedAnnotation(name) => { format!("Local variable `{name}` is annotated but never used") } - CheckKind::UnusedImport(name, ignore_init) => { + CheckKind::UnusedImport(name, ignore_init, ..) => { if *ignore_init { format!( "`{name}` imported but unused; consider adding to `__all__` or using a \ @@ -2627,7 +2670,7 @@ impl CheckKind { format!("`{alias}` is deprecated, use `{target}`") } CheckKind::UselessObjectInheritance(name) => { - format!("Class `{name}` inherits from object") + format!("Class `{name}` inherits from `object`") } CheckKind::UsePEP585Annotation(name) => { format!( @@ -2653,14 +2696,24 @@ impl CheckKind { "Unnecessary parameters to `functools.lru_cache`".to_string() } CheckKind::UnnecessaryEncodeUTF8 => "Unnecessary call to `encode` as UTF-8".to_string(), - CheckKind::RedundantOpenModes => "Unnecessary open mode parameters".to_string(), + CheckKind::RedundantOpenModes(replacement) => match replacement { + None => "Unnecessary open mode parameters".to_string(), + Some(replacement) => { + format!("Unnecessary open mode parameters, use \"{replacement}\"") + } + }, CheckKind::RemoveSixCompat => "Unnecessary `six` compatibility usage".to_string(), CheckKind::DatetimeTimezoneUTC => "Use `datetime.UTC` alias".to_string(), - CheckKind::NativeLiterals => "Unnecessary call to `str` and `bytes`".to_string(), + CheckKind::NativeLiterals(literal_type) => { + format!("Unnecessary call to `{literal_type}`") + } CheckKind::OpenAlias => "Use builtin `open`".to_string(), CheckKind::ConvertTypedDictFunctionalToClass(name) => { format!("Convert `{name}` from `TypedDict` functional to class syntax") } + CheckKind::ConvertNamedTupleFunctionalToClass(name) => { + format!("Convert `{name}` from `NamedTuple` functional to class syntax") + } CheckKind::ReplaceUniversalNewlines => { "`universal_newlines` is deprecated, use `text`".to_string() } @@ -2671,9 +2724,6 @@ impl CheckKind { "`cElementTree` is deprecated, use `ElementTree`".to_string() } CheckKind::RewriteUnicodeLiteral => "Remove unicode literals from strings".to_string(), - CheckKind::ConvertNamedTupleFunctionalToClass(name) => { - format!("Convert `{name}` from `NamedTuple` functional to class syntax") - } // pydocstyle CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(), CheckKind::BlankLineAfterSummary => { @@ -3088,6 +3138,7 @@ impl CheckKind { self, CheckKind::AmbiguousUnicodeCharacterString(..) | CheckKind::AmbiguousUnicodeCharacterDocstring(..) + | CheckKind::AmbiguousUnicodeCharacterComment(..) | CheckKind::BlankLineAfterLastSection(..) | CheckKind::BlankLineAfterSection(..) | CheckKind::BlankLineAfterSummary @@ -3100,7 +3151,7 @@ impl CheckKind { | CheckKind::DatetimeTimezoneUTC | CheckKind::DeprecatedUnittestAlias(..) | CheckKind::DoNotAssertFalse - | CheckKind::DoNotAssignLambda + | CheckKind::DoNotAssignLambda(..) | CheckKind::DuplicateHandlerException(..) | CheckKind::EndsInPeriod | CheckKind::EndsInPunctuation @@ -3108,11 +3159,11 @@ impl CheckKind { | CheckKind::ImplicitReturn | CheckKind::ImplicitReturnValue | CheckKind::InvalidEscapeSequence(..) - | CheckKind::IsLiteral + | CheckKind::IsLiteral(..) | CheckKind::KeyInDict(..) | CheckKind::MisplacedComparisonConstant(..) | CheckKind::MissingReturnTypeSpecialMethod(..) - | CheckKind::NativeLiterals + | CheckKind::NativeLiterals(..) | CheckKind::OpenAlias | CheckKind::NewLineAfterLastParagraph | CheckKind::ReplaceUniversalNewlines @@ -3135,10 +3186,10 @@ impl CheckKind { | CheckKind::OneBlankLineBeforeClass(..) | CheckKind::PEP3120UnnecessaryCodingComment | CheckKind::PPrintFound - | CheckKind::PercentFormatExtraNamedArguments(..) | CheckKind::PrintFound + | CheckKind::PercentFormatExtraNamedArguments(..) | CheckKind::RaiseNotImplemented - | CheckKind::RedundantOpenModes + | CheckKind::RedundantOpenModes(..) | CheckKind::RedundantTupleInExceptionHandler(..) | CheckKind::RemoveSixCompat | CheckKind::SectionNameEndsInColon(..) @@ -3170,7 +3221,7 @@ impl CheckKind { | CheckKind::UnnecessaryLiteralWithinTupleCall(..) | CheckKind::UnnecessaryReturnNone | CheckKind::UnsortedImports - | CheckKind::UnusedImport(_, false) + | CheckKind::UnusedImport(_, false, _) | CheckKind::UnusedLoopControlVariable(..) | CheckKind::UnusedNOQA(..) | CheckKind::UsePEP585Annotation(..) @@ -3181,6 +3232,235 @@ impl CheckKind { | CheckKind::UselessObjectInheritance(..) ) } + + /// The message used to describe the fix action for a given `CheckKind`. + pub fn commit(&self) -> Option { + match self { + CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) + | CheckKind::AmbiguousUnicodeCharacterDocstring(confusable, representant) + | CheckKind::AmbiguousUnicodeCharacterComment(confusable, representant) => { + Some(format!("Replace '{confusable}' with '{representant}'")) + } + CheckKind::BlankLineAfterLastSection(name) => { + Some(format!("Add blank line after \"{name}\"")) + } + CheckKind::BlankLineAfterSection(name) => { + Some(format!("Add blank line after \"{name}\"")) + } + CheckKind::BlankLineAfterSummary => Some("Insert single blank line".to_string()), + CheckKind::BlankLineBeforeSection(name) => { + Some(format!("Add blank line before \"{name}\"")) + } + CheckKind::CapitalizeSectionName(name) => Some(format!("Capitalize \"{name}\"")), + CheckKind::CommentedOutCode => Some("Remove commented-out code".to_string()), + CheckKind::ConvertTypedDictFunctionalToClass(name) + | CheckKind::ConvertNamedTupleFunctionalToClass(name) => { + Some(format!("Convert `{name}` to class syntax")) + } + CheckKind::DashedUnderlineAfterSection(name) => { + Some(format!("Add dashed line under \"{name}\"")) + } + CheckKind::DatetimeTimezoneUTC => Some("Convert to `datetime.UTC` alias".to_string()), + CheckKind::DeprecatedUnittestAlias(alias, target) => { + Some(format!("Replace `{target}` with `{alias}`")) + } + CheckKind::DoNotAssertFalse => Some("Replace `assert False`".to_string()), + CheckKind::DoNotAssignLambda(name) => Some(format!("Rewrite `{name}` as a `def`")), + CheckKind::DuplicateHandlerException(..) => Some("De-duplicate exceptions".to_string()), + CheckKind::EndsInPeriod => Some("Add period".to_string()), + CheckKind::EndsInPunctuation => Some("Add closing punctuation".to_string()), + CheckKind::GetAttrWithConstant => { + Some("Replace `getattr` with attribute access".to_string()) + } + CheckKind::ImplicitReturnValue => Some("Add explicit `None` return value".to_string()), + CheckKind::ImplicitReturn => Some("Add explicit `return` statement".to_string()), + CheckKind::InvalidEscapeSequence(..) => { + Some("Add backslash to escape sequence".to_string()) + } + CheckKind::IsLiteral(cmpop) => Some(match cmpop { + IsCmpop::Is => "Replace `is` with `==`".to_string(), + IsCmpop::IsNot => "Replace `is not` with `!=`".to_string(), + }), + CheckKind::KeyInDict(key, dict) => Some(format!("Convert to `{key} in {dict}`")), + CheckKind::MisplacedComparisonConstant(comparison) => { + Some(format!("Replace with {comparison}")) + } + CheckKind::MissingReturnTypeSpecialMethod(..) => { + Some("Add `None` return type".to_string()) + } + CheckKind::NativeLiterals(literal_type) => { + Some(format!("Replace with `{literal_type}`")) + } + CheckKind::OpenAlias => Some("Replace with builtin `open`".to_string()), + CheckKind::NewLineAfterLastParagraph => { + Some("Move closing quotes to new line".to_string()) + } + CheckKind::ReplaceUniversalNewlines => { + Some("Replace with `text` keyword argument".to_string()) + } + CheckKind::ReplaceStdoutStderr => { + Some("Replace with `capture_output` keyword argument".to_string()) + } + CheckKind::RewriteCElementTree => Some("Replace with `ElementTree`".to_string()), + CheckKind::RewriteUnicodeLiteral => Some("Remove unicode prefix".to_string()), + CheckKind::NewLineAfterSectionName(name) => { + Some(format!("Add newline after \"{name}\"")) + } + CheckKind::NoBlankLineBeforeFunction(..) => { + Some("Remove blank line(s) before function docstring".to_string()) + } + CheckKind::NoBlankLineAfterFunction(..) => { + Some("Remove blank line(s) after function docstring".to_string()) + } + CheckKind::NoBlankLineBeforeClass(..) => { + Some("Remove blank line(s) before class docstring".to_string()) + } + CheckKind::OneBlankLineBeforeClass(..) => { + Some("Insert 1 blank line before class docstring".to_string()) + } + CheckKind::OneBlankLineAfterClass(..) => { + Some("Insert 1 blank line after class docstring".to_string()) + } + CheckKind::NoBlankLinesBetweenHeaderAndContent(..) => { + Some("Remove blank line(s)".to_string()) + } + CheckKind::NoNewLineAtEndOfFile => Some("Add trailing newline".to_string()), + CheckKind::NoOverIndentation => Some("Remove over-indentation".to_string()), + CheckKind::NoSurroundingWhitespace => Some("Trim surrounding whitespace".to_string()), + CheckKind::NoUnderIndentation => Some("Increase indentation".to_string()), + CheckKind::NoneComparison(op) => Some(match op { + EqCmpop::Eq => "Replace with `cond is None`".to_string(), + EqCmpop::NotEq => "Replace with `cond is not None`".to_string(), + }), + CheckKind::NotInTest => Some("Convert to `not in`".to_string()), + CheckKind::NotIsTest => Some("Convert to `is not`".to_string()), + CheckKind::PEP3120UnnecessaryCodingComment => { + Some("Remove unnecessary coding comment".to_string()) + } + CheckKind::PPrintFound => Some("Remove `pprint`".to_string()), + CheckKind::PercentFormatExtraNamedArguments(missing) + | CheckKind::StringDotFormatExtraNamedArguments(missing) => { + let message = missing.join(", "); + Some(format!("Remove extra named arguments: {message}")) + } + CheckKind::PrintFound => Some("Remove `print`".to_string()), + CheckKind::RaiseNotImplemented => Some("Use `raise NotImplementedError`".to_string()), + CheckKind::RedundantOpenModes(replacement) => Some(match replacement { + None => "Remove open mode parameters".to_string(), + Some(replacement) => { + format!("Replace with \"{replacement}\"") + } + }), + CheckKind::RedundantTupleInExceptionHandler(name) => { + Some(format!("Replace with `except {name}`")) + } + CheckKind::RemoveSixCompat => Some("Remove `six` usage".to_string()), + CheckKind::SectionNameEndsInColon(name) => Some(format!("Add colon to \"{name}\"")), + CheckKind::SectionNotOverIndented(name) => { + Some(format!("Remove over-indentation from \"{name}\"")) + } + CheckKind::SectionUnderlineAfterName(name) => { + Some(format!("Add underline to \"{name}\"")) + } + CheckKind::SectionUnderlineMatchesSectionLength(name) => { + Some(format!("Adjust underline length to match \"{name}\"")) + } + CheckKind::SectionUnderlineNotOverIndented(name) => { + Some(format!("Remove over-indentation from \"{name}\" underline")) + } + CheckKind::SetAttrWithConstant => Some("Replace `setattr` with assignment".to_string()), + CheckKind::SuperCallWithParameters => Some("Remove `__super__` parameters".to_string()), + CheckKind::TrueFalseComparison(true, EqCmpop::Eq) => { + Some("Replace with `cond is True`".to_string()) + } + CheckKind::TrueFalseComparison(true, EqCmpop::NotEq) => { + Some("Replace with `cond is not True`".to_string()) + } + CheckKind::TrueFalseComparison(false, EqCmpop::Eq) => { + Some("Replace with `cond is False`".to_string()) + } + CheckKind::TrueFalseComparison(false, EqCmpop::NotEq) => { + Some("Replace with `cond is not False`".to_string()) + } + CheckKind::TypeOfPrimitive(primitive) => Some(format!( + "Replace `type(...)` with `{}`", + primitive.builtin() + )), + CheckKind::TypingTextStrAlias => Some("Replace with `str`".to_string()), + CheckKind::UnnecessaryCallAroundSorted(func) => { + Some(format!("Remove unnecessary `{func}` call")) + } + CheckKind::UnnecessaryCollectionCall(..) => Some("Rewrite as a literal".to_string()), + CheckKind::UnnecessaryComprehension(obj_type) => { + Some(format!("Rewrite using `{obj_type}()`")) + } + CheckKind::UnnecessaryEncodeUTF8 => Some("Remove unnecessary `encode`".to_string()), + CheckKind::UnnecessaryFutureImport(..) => { + Some("Remove unnecessary `__future__` import".to_string()) + } + CheckKind::UnnecessaryGeneratorDict => { + Some("Rewrite as a `dict` comprehension".to_string()) + } + CheckKind::UnnecessaryGeneratorList => { + Some("Rewrite as a `list` comprehension".to_string()) + } + CheckKind::UnnecessaryGeneratorSet => { + Some("Rewrite as a `set` comprehension".to_string()) + } + CheckKind::UnnecessaryLRUCacheParams => { + Some("Remove unnecessary parameters".to_string()) + } + CheckKind::UnnecessaryListCall => Some("Remove outer `list` call".to_string()), + CheckKind::UnnecessaryListComprehensionDict => { + Some("Rewrite as a `dict` comprehension".to_string()) + } + CheckKind::UnnecessaryListComprehensionSet => { + Some("Rewrite as a `set` comprehension".to_string()) + } + CheckKind::UnnecessaryLiteralDict(..) => { + Some("Rewrite as a `dict` literal".to_string()) + } + CheckKind::UnnecessaryLiteralSet(..) => Some("Rewrite as a `set` literal".to_string()), + CheckKind::UnnecessaryLiteralWithinTupleCall(literal) => Some({ + if literal == "list" { + "Rewrite as a `tuple` literal".to_string() + } else { + "Remove outer `tuple` call".to_string() + } + }), + CheckKind::UnnecessaryLiteralWithinListCall(literal) => Some({ + if literal == "list" { + "Remove outer `list` call".to_string() + } else { + "Rewrite as a `list` literal".to_string() + } + }), + CheckKind::UnnecessaryReturnNone => Some("Remove explicit `return None`".to_string()), + CheckKind::UnsortedImports => Some("Organize imports".to_string()), + CheckKind::UnusedImport(name, false, multiple) => { + if *multiple { + Some("Remove unused import".to_string()) + } else { + Some(format!("Remove unused import: `{name}`")) + } + } + CheckKind::UnusedLoopControlVariable(name) => { + Some(format!("Rename unused `{name}` to `_{name}`")) + } + CheckKind::UnusedNOQA(..) => Some("Remove unused `noqa` directive".to_string()), + CheckKind::UsePEP585Annotation(name) => { + Some(format!("Replace `{name}` with `{}`", name.to_lowercase(),)) + } + CheckKind::UsePEP604Annotation => Some("Convert to `X | Y`".to_string()), + CheckKind::UseSysExit(name) => Some(format!("Replace `{name}` with `sys.exit()`")), + CheckKind::UselessImportAlias => Some("Remove import alias".to_string()), + CheckKind::UselessMetaclassType => Some("Remove `__metaclass__ = type`".to_string()), + CheckKind::UselessObjectInheritance(..) => { + Some("Remove `object` inheritance".to_string()) + } + _ => None, + } + } } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -3361,4 +3641,17 @@ mod tests { ); } } + + #[test] + fn fixable_codes() { + for check_code in CheckCode::iter() { + let kind = check_code.kind(); + if kind.fixable() { + assert!( + kind.commit().is_some(), + "{check_code:?} is fixable but has no commit message." + ); + } + } + } } diff --git a/src/lib_wasm.rs b/src/lib_wasm.rs index 6ca750f77a..1e5600beae 100644 --- a/src/lib_wasm.rs +++ b/src/lib_wasm.rs @@ -2,10 +2,9 @@ use std::path::Path; use rustpython_ast::Location; use rustpython_parser::lexer::LexResult; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use wasm_bindgen::prelude::*; -use crate::autofix::Fix; use crate::checks::CheckCode; use crate::checks_gen::CheckCodePrefix; use crate::linter::check_path; @@ -39,6 +38,7 @@ export interface Check { }; fix: { content: string; + message: string | null; location: { row: number; column: number; @@ -51,13 +51,21 @@ export interface Check { }; "#; -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -struct Message { +#[derive(Serialize)] +struct ExpandedMessage { code: CheckCode, message: String, location: Location, end_location: Location, - fix: Option, + fix: Option, +} + +#[derive(Serialize)] +struct ExpandedFix { + content: String, + message: Option, + location: Location, + end_location: Location, } #[wasm_bindgen(start)] @@ -161,14 +169,19 @@ pub fn check(contents: &str, options: JsValue) -> Result { ) .map_err(|e| e.to_string())?; - let messages: Vec = checks + let messages: Vec = checks .into_iter() - .map(|check| Message { + .map(|check| ExpandedMessage { code: check.kind.code().clone(), message: check.kind.body(), location: check.location, end_location: check.end_location, - fix: check.fix, + fix: check.fix.map(|fix| ExpandedFix { + content: fix.content, + message: check.kind.commit(), + location: fix.location, + end_location: fix.end_location, + }), }) .collect(); @@ -200,7 +213,7 @@ mod test { check!( "if (1, 2): pass", r#"{}"#, - [Message { + [ExpandedMessage { code: CheckCode::F634, message: "If test is a tuple, which is always `True`".to_string(), location: Location::new(1, 0), diff --git a/src/printer.rs b/src/printer.rs index 5a719ee807..7237be2e7e 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -10,7 +10,7 @@ use rustpython_parser::ast::Location; use serde::Serialize; use serde_json::json; -use crate::autofix::{fixer, Fix}; +use crate::autofix::fixer; use crate::checks::CheckCode; use crate::fs::relativize_path; use crate::linter::Diagnostics; @@ -25,11 +25,19 @@ pub enum Violations { Hide, } +#[derive(Serialize)] +struct ExpandedFix<'a> { + content: &'a str, + message: Option, + location: &'a Location, + end_location: &'a Location, +} + #[derive(Serialize)] struct ExpandedMessage<'a> { code: &'a CheckCode, message: String, - fix: Option<&'a Fix>, + fix: Option>, location: Location, end_location: Location, filename: &'a str, @@ -127,7 +135,12 @@ impl<'a> Printer<'a> { .map(|message| ExpandedMessage { code: message.kind.code(), message: message.kind.body(), - fix: message.fix.as_ref(), + fix: message.fix.as_ref().map(|fix| ExpandedFix { + content: &fix.content, + location: &fix.location, + end_location: &fix.end_location, + message: message.kind.commit(), + }), location: message.location, end_location: message.end_location, filename: &message.filename, diff --git a/src/pycodestyle/plugins.rs b/src/pycodestyle/plugins.rs index 478de1c398..0deba00f96 100644 --- a/src/pycodestyle/plugins.rs +++ b/src/pycodestyle/plugins.rs @@ -11,7 +11,7 @@ use crate::ast::types::Range; use crate::ast::whitespace::leading_space; use crate::autofix::Fix; use crate::checkers::ast::Checker; -use crate::checks::{Check, CheckKind, RejectedCmpop}; +use crate::checks::{Check, CheckKind}; use crate::source_code_generator::SourceCodeGenerator; use crate::source_code_style::SourceCodeStyleDetector; @@ -68,7 +68,7 @@ pub fn literal_comparisons( { if matches!(op, Cmpop::Eq) { let check = Check::new( - CheckKind::NoneComparison(RejectedCmpop::Eq), + CheckKind::NoneComparison(op.into()), Range::from_located(comparator), ); if checker.patch(check.kind.code()) && !helpers::is_constant_non_singleton(next) { @@ -78,7 +78,7 @@ pub fn literal_comparisons( } if matches!(op, Cmpop::NotEq) { let check = Check::new( - CheckKind::NoneComparison(RejectedCmpop::NotEq), + CheckKind::NoneComparison(op.into()), Range::from_located(comparator), ); if checker.patch(check.kind.code()) && !helpers::is_constant_non_singleton(next) { @@ -96,7 +96,7 @@ pub fn literal_comparisons( { if matches!(op, Cmpop::Eq) { let check = Check::new( - CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq), + CheckKind::TrueFalseComparison(value, op.into()), Range::from_located(comparator), ); if checker.patch(check.kind.code()) && !helpers::is_constant_non_singleton(next) { @@ -106,7 +106,7 @@ pub fn literal_comparisons( } if matches!(op, Cmpop::NotEq) { let check = Check::new( - CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq), + CheckKind::TrueFalseComparison(value, op.into()), Range::from_located(comparator), ); if checker.patch(check.kind.code()) && !helpers::is_constant_non_singleton(next) { @@ -130,7 +130,7 @@ pub fn literal_comparisons( { if matches!(op, Cmpop::Eq) { let check = Check::new( - CheckKind::NoneComparison(RejectedCmpop::Eq), + CheckKind::NoneComparison(op.into()), Range::from_located(next), ); if checker.patch(check.kind.code()) @@ -142,7 +142,7 @@ pub fn literal_comparisons( } if matches!(op, Cmpop::NotEq) { let check = Check::new( - CheckKind::NoneComparison(RejectedCmpop::NotEq), + CheckKind::NoneComparison(op.into()), Range::from_located(next), ); if checker.patch(check.kind.code()) @@ -162,7 +162,7 @@ pub fn literal_comparisons( { if matches!(op, Cmpop::Eq) { let check = Check::new( - CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq), + CheckKind::TrueFalseComparison(value, op.into()), Range::from_located(next), ); if checker.patch(check.kind.code()) @@ -174,7 +174,7 @@ pub fn literal_comparisons( } if matches!(op, Cmpop::NotEq) { let check = Check::new( - CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq), + CheckKind::TrueFalseComparison(value, op.into()), Range::from_located(next), ); if checker.patch(check.kind.code()) @@ -311,7 +311,10 @@ fn function( pub fn do_not_assign_lambda(checker: &mut Checker, target: &Expr, value: &Expr, stmt: &Stmt) { if let ExprKind::Name { id, .. } = &target.node { if let ExprKind::Lambda { args, body } = &value.node { - let mut check = Check::new(CheckKind::DoNotAssignLambda, Range::from_located(stmt)); + let mut check = Check::new( + CheckKind::DoNotAssignLambda(id.to_string()), + Range::from_located(stmt), + ); if checker.patch(check.kind.code()) { if !match_leading_content(stmt, checker.locator) && !match_trailing_content(stmt, checker.locator) diff --git a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__E731_E731.py.snap b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__E731_E731.py.snap index 710c4af83a..df7936cc64 100644 --- a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__E731_E731.py.snap +++ b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__E731_E731.py.snap @@ -2,7 +2,8 @@ source: src/pycodestyle/mod.rs expression: checks --- -- kind: DoNotAssignLambda +- kind: + DoNotAssignLambda: f location: row: 2 column: 0 @@ -18,7 +19,8 @@ expression: checks row: 2 column: 19 parent: ~ -- kind: DoNotAssignLambda +- kind: + DoNotAssignLambda: f location: row: 4 column: 0 @@ -34,7 +36,8 @@ expression: checks row: 4 column: 19 parent: ~ -- kind: DoNotAssignLambda +- kind: + DoNotAssignLambda: this location: row: 7 column: 4 diff --git a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__constant_literals.snap b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__constant_literals.snap index dbaa434381..88d35064ec 100644 --- a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__constant_literals.snap +++ b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__constant_literals.snap @@ -2,7 +2,8 @@ source: src/pycodestyle/mod.rs expression: checks --- -- kind: IsLiteral +- kind: + IsLiteral: Is location: row: 4 column: 3 @@ -18,7 +19,8 @@ expression: checks row: 4 column: 11 parent: ~ -- kind: IsLiteral +- kind: + IsLiteral: Is location: row: 6 column: 3 @@ -34,7 +36,8 @@ expression: checks row: 6 column: 11 parent: ~ -- kind: IsLiteral +- kind: + IsLiteral: Is location: row: 8 column: 3 @@ -50,7 +53,8 @@ expression: checks row: 8 column: 10 parent: ~ -- kind: IsLiteral +- kind: + IsLiteral: Is location: row: 10 column: 3 @@ -66,7 +70,8 @@ expression: checks row: 10 column: 11 parent: ~ -- kind: IsLiteral +- kind: + IsLiteral: Is location: row: 12 column: 3 diff --git a/src/pyflakes/plugins/invalid_literal_comparisons.rs b/src/pyflakes/plugins/invalid_literal_comparisons.rs index cb3e886283..ef7e06e32a 100644 --- a/src/pyflakes/plugins/invalid_literal_comparisons.rs +++ b/src/pyflakes/plugins/invalid_literal_comparisons.rs @@ -24,7 +24,7 @@ pub fn invalid_literal_comparison( && (helpers::is_constant_non_singleton(left) || helpers::is_constant_non_singleton(right)) { - let mut check = Check::new(CheckKind::IsLiteral, location); + let mut check = Check::new(CheckKind::IsLiteral(op.into()), location); if checker.patch(check.kind.code()) { if let Some(located_op) = &located.get(index) { assert_eq!(&located_op.node, op); diff --git a/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_0.py.snap b/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_0.py.snap index ef3d377b8b..7c6c04afa3 100644 --- a/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_0.py.snap +++ b/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_0.py.snap @@ -6,6 +6,7 @@ expression: checks UnusedImport: - functools - false + - false location: row: 2 column: 7 @@ -25,6 +26,7 @@ expression: checks UnusedImport: - collections.OrderedDict - false + - false location: row: 6 column: 4 @@ -46,6 +48,7 @@ expression: checks UnusedImport: - logging.handlers - false + - false location: row: 12 column: 7 @@ -65,6 +68,7 @@ expression: checks UnusedImport: - shelve - false + - false location: row: 32 column: 11 @@ -84,6 +88,7 @@ expression: checks UnusedImport: - importlib - false + - false location: row: 33 column: 11 @@ -103,6 +108,7 @@ expression: checks UnusedImport: - pathlib - false + - false location: row: 37 column: 11 @@ -122,6 +128,7 @@ expression: checks UnusedImport: - pickle - false + - false location: row: 52 column: 15 diff --git a/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_5.py.snap b/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_5.py.snap index 6a49ef67bd..1e67389f25 100644 --- a/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_5.py.snap +++ b/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_5.py.snap @@ -6,6 +6,7 @@ expression: checks UnusedImport: - a.b.c - false + - false location: row: 2 column: 16 @@ -25,6 +26,7 @@ expression: checks UnusedImport: - d.e.f - false + - false location: row: 3 column: 16 @@ -44,6 +46,7 @@ expression: checks UnusedImport: - h.i - false + - false location: row: 4 column: 7 @@ -63,6 +66,7 @@ expression: checks UnusedImport: - j.k - false + - false location: row: 5 column: 7 diff --git a/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_6.py.snap b/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_6.py.snap index fa24a33f89..d507e44765 100644 --- a/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_6.py.snap +++ b/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_6.py.snap @@ -6,6 +6,7 @@ expression: checks UnusedImport: - background.BackgroundTasks - false + - false location: row: 7 column: 24 @@ -25,6 +26,7 @@ expression: checks UnusedImport: - datastructures.UploadFile - false + - false location: row: 10 column: 28 @@ -44,6 +46,7 @@ expression: checks UnusedImport: - background - false + - false location: row: 17 column: 7 @@ -63,6 +66,7 @@ expression: checks UnusedImport: - datastructures - false + - false location: row: 20 column: 7 diff --git a/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_7.py.snap b/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_7.py.snap index 68aab5e43f..14f4a11ba7 100644 --- a/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_7.py.snap +++ b/src/pyflakes/snapshots/ruff__pyflakes__tests__F401_F401_7.py.snap @@ -6,6 +6,7 @@ expression: checks UnusedImport: - typing.Union - false + - false location: row: 30 column: 4 @@ -27,6 +28,7 @@ expression: checks UnusedImport: - typing.Awaitable - false + - true location: row: 66 column: 19 @@ -46,6 +48,7 @@ expression: checks UnusedImport: - typing.AwaitableGenerator - false + - true location: row: 66 column: 30 diff --git a/src/pyflakes/snapshots/ruff__pyflakes__tests__F632_F632.py.snap b/src/pyflakes/snapshots/ruff__pyflakes__tests__F632_F632.py.snap index 8b8f80db3f..4c8b5e1317 100644 --- a/src/pyflakes/snapshots/ruff__pyflakes__tests__F632_F632.py.snap +++ b/src/pyflakes/snapshots/ruff__pyflakes__tests__F632_F632.py.snap @@ -2,7 +2,8 @@ source: src/pyflakes/mod.rs expression: checks --- -- kind: IsLiteral +- kind: + IsLiteral: Is location: row: 1 column: 3 @@ -18,7 +19,8 @@ expression: checks row: 1 column: 7 parent: ~ -- kind: IsLiteral +- kind: + IsLiteral: IsNot location: row: 4 column: 3 @@ -34,7 +36,8 @@ expression: checks row: 4 column: 13 parent: ~ -- kind: IsLiteral +- kind: + IsLiteral: IsNot location: row: 7 column: 3 @@ -50,7 +53,8 @@ expression: checks row: 8 column: 11 parent: ~ -- kind: IsLiteral +- kind: + IsLiteral: Is location: row: 11 column: 3 @@ -66,7 +70,8 @@ expression: checks row: 11 column: 11 parent: ~ -- kind: IsLiteral +- kind: + IsLiteral: Is location: row: 14 column: 3 @@ -82,7 +87,8 @@ expression: checks row: 14 column: 16 parent: ~ -- kind: IsLiteral +- kind: + IsLiteral: Is location: row: 17 column: 3 @@ -98,7 +104,8 @@ expression: checks row: 17 column: 18 parent: ~ -- kind: IsLiteral +- kind: + IsLiteral: Is location: row: 20 column: 13 diff --git a/src/pyflakes/snapshots/ruff__pyflakes__tests__future_annotations.snap b/src/pyflakes/snapshots/ruff__pyflakes__tests__future_annotations.snap index b1cab69dea..a6ae0c77b6 100644 --- a/src/pyflakes/snapshots/ruff__pyflakes__tests__future_annotations.snap +++ b/src/pyflakes/snapshots/ruff__pyflakes__tests__future_annotations.snap @@ -6,6 +6,7 @@ expression: checks UnusedImport: - models.Nut - false + - false location: row: 8 column: 4 diff --git a/src/pyflakes/snapshots/ruff__pyflakes__tests__multi_statement_lines.snap b/src/pyflakes/snapshots/ruff__pyflakes__tests__multi_statement_lines.snap index e9248f56b4..23cddc0d67 100644 --- a/src/pyflakes/snapshots/ruff__pyflakes__tests__multi_statement_lines.snap +++ b/src/pyflakes/snapshots/ruff__pyflakes__tests__multi_statement_lines.snap @@ -6,6 +6,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 3 column: 11 @@ -25,6 +26,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 4 column: 11 @@ -44,6 +46,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 7 column: 11 @@ -63,6 +66,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 11 column: 11 @@ -82,6 +86,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 16 column: 18 @@ -101,6 +106,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 21 column: 16 @@ -120,6 +126,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 26 column: 17 @@ -139,6 +146,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 30 column: 18 @@ -158,6 +166,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 31 column: 22 @@ -177,6 +186,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 35 column: 15 @@ -196,6 +206,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 40 column: 16 @@ -215,6 +226,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 46 column: 7 @@ -234,6 +246,7 @@ expression: checks UnusedImport: - foo - false + - false location: row: 51 column: 7 diff --git a/src/pyupgrade/plugins/native_literals.rs b/src/pyupgrade/plugins/native_literals.rs index c53c72a4eb..d4d66e4790 100644 --- a/src/pyupgrade/plugins/native_literals.rs +++ b/src/pyupgrade/plugins/native_literals.rs @@ -5,7 +5,7 @@ use rustpython_parser::lexer::Tok; use crate::ast::types::Range; use crate::autofix::Fix; use crate::checkers::ast::Checker; -use crate::checks::{Check, CheckCode, CheckKind}; +use crate::checks::{Check, CheckCode, CheckKind, LiteralType}; /// UP018 pub fn native_literals( @@ -23,7 +23,12 @@ pub fn native_literals( && checker.is_builtin(id) { let Some(arg) = args.get(0) else { - let mut check = Check::new(CheckKind::NativeLiterals, Range::from_located(expr)); + let literal_type = if id == "str" { + LiteralType::Str + } else { + LiteralType::Bytes + }; + let mut check = Check::new(CheckKind::NativeLiterals(literal_type), Range::from_located(expr)); if checker.patch(&CheckCode::UP018) { check.amend(Fix::replacement( format!("{}\"\"", if id == "bytes" { "b" } else { "" }), @@ -35,15 +40,14 @@ pub fn native_literals( return; }; - if !matches!( - &arg.node, - ExprKind::Constant { - value: Constant::Str(_) | Constant::Bytes(_), - .. - } - ) { + let ExprKind::Constant { value, ..} = &arg.node else { return; - } + }; + let literal_type = match value { + Constant::Str { .. } => LiteralType::Str, + Constant::Bytes { .. } => LiteralType::Bytes, + _ => return, + }; // rust-python merges adjacent string/bytes literals into one node, but we can't // safely remove the outer call in this situation. We're following pyupgrade @@ -60,7 +64,10 @@ pub fn native_literals( return; } - let mut check = Check::new(CheckKind::NativeLiterals, Range::from_located(expr)); + let mut check = Check::new( + CheckKind::NativeLiterals(literal_type), + Range::from_located(expr), + ); if checker.patch(&CheckCode::UP018) { check.amend(Fix::replacement( arg_code.to_string(), diff --git a/src/pyupgrade/plugins/redundant_open_modes.rs b/src/pyupgrade/plugins/redundant_open_modes.rs index ea25a086ea..f570ef638a 100644 --- a/src/pyupgrade/plugins/redundant_open_modes.rs +++ b/src/pyupgrade/plugins/redundant_open_modes.rs @@ -2,10 +2,11 @@ use std::str::FromStr; use anyhow::{anyhow, Result}; use log::error; -use rustpython_ast::{Constant, Expr, ExprKind, Keyword, KeywordData, Location}; +use rustpython_ast::{Constant, Expr, ExprKind, Keyword, Location}; use rustpython_parser::lexer; use rustpython_parser::token::Tok; +use crate::ast::helpers::find_keyword; use crate::ast::types::Range; use crate::autofix::Fix; use crate::checkers::ast::Checker; @@ -78,7 +79,10 @@ fn create_check( locator: &SourceCodeLocator, patch: bool, ) -> Check { - let mut check = Check::new(CheckKind::RedundantOpenModes, Range::from_located(expr)); + let mut check = Check::new( + CheckKind::RedundantOpenModes(replacement_value.clone()), + Range::from_located(expr), + ); if patch { if let Some(content) = replacement_value { check.amend(Fix::replacement( @@ -153,27 +157,16 @@ pub fn redundant_open_modes(checker: &mut Checker, expr: &Expr) { } let (mode_param, keywords): (Option<&Expr>, Vec) = match_open(expr); if mode_param.is_none() && !keywords.is_empty() { - if let Some(value) = keywords.iter().find_map(|keyword| { - let KeywordData { arg, value } = &keyword.node; - if arg - .as_ref() - .map(|arg| arg == MODE_KEYWORD_ARGUMENT) - .unwrap_or_default() - { - Some(value) - } else { - None - } - }) { + if let Some(keyword) = find_keyword(&keywords, MODE_KEYWORD_ARGUMENT) { if let ExprKind::Constant { value: Constant::Str(mode_param_value), .. - } = &value.node + } = &keyword.node.value.node { if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) { checker.add_check(create_check( expr, - value, + &keyword.node.value, mode.replacement_value(), checker.locator, checker.patch(&CheckCode::UP015), diff --git a/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP015_UP015.py.snap b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP015_UP015.py.snap index 79ca89c6ac..5576a70205 100644 --- a/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP015_UP015.py.snap +++ b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP015_UP015.py.snap @@ -2,7 +2,8 @@ source: src/pyupgrade/mod.rs expression: checks --- -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 1 column: 0 @@ -18,7 +19,8 @@ expression: checks row: 1 column: 15 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 2 column: 0 @@ -34,7 +36,8 @@ expression: checks row: 2 column: 16 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 3 column: 0 @@ -50,7 +53,8 @@ expression: checks row: 3 column: 16 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 4 column: 0 @@ -66,7 +70,8 @@ expression: checks row: 4 column: 17 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 5 column: 0 @@ -82,7 +87,8 @@ expression: checks row: 5 column: 15 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 6 column: 0 @@ -98,7 +104,8 @@ expression: checks row: 6 column: 16 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 7 column: 0 @@ -114,7 +121,8 @@ expression: checks row: 7 column: 13 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"w\"" location: row: 8 column: 0 @@ -130,7 +138,8 @@ expression: checks row: 8 column: 14 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 10 column: 5 @@ -146,7 +155,8 @@ expression: checks row: 10 column: 20 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 12 column: 5 @@ -162,7 +172,8 @@ expression: checks row: 12 column: 21 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 14 column: 5 @@ -178,7 +189,8 @@ expression: checks row: 14 column: 21 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 16 column: 5 @@ -194,7 +206,8 @@ expression: checks row: 16 column: 22 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 18 column: 5 @@ -210,7 +223,8 @@ expression: checks row: 18 column: 20 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 20 column: 5 @@ -226,7 +240,8 @@ expression: checks row: 20 column: 21 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 22 column: 5 @@ -242,7 +257,8 @@ expression: checks row: 22 column: 20 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"w\"" location: row: 24 column: 5 @@ -258,7 +274,8 @@ expression: checks row: 24 column: 21 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 27 column: 0 @@ -274,7 +291,8 @@ expression: checks row: 27 column: 26 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 28 column: 0 @@ -290,7 +308,8 @@ expression: checks row: 28 column: 27 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 30 column: 5 @@ -306,7 +325,8 @@ expression: checks row: 30 column: 31 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 32 column: 5 @@ -322,7 +342,8 @@ expression: checks row: 32 column: 32 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 35 column: 5 @@ -338,7 +359,8 @@ expression: checks row: 35 column: 20 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 35 column: 29 @@ -354,7 +376,8 @@ expression: checks row: 35 column: 44 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 37 column: 5 @@ -370,7 +393,8 @@ expression: checks row: 37 column: 21 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 37 column: 30 @@ -386,7 +410,8 @@ expression: checks row: 37 column: 46 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 40 column: 0 @@ -402,7 +427,8 @@ expression: checks row: 40 column: 20 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 41 column: 0 @@ -418,7 +444,8 @@ expression: checks row: 41 column: 25 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 42 column: 0 @@ -434,7 +461,8 @@ expression: checks row: 42 column: 15 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 44 column: 5 @@ -450,7 +478,8 @@ expression: checks row: 44 column: 25 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 46 column: 5 @@ -466,7 +495,8 @@ expression: checks row: 46 column: 30 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 48 column: 5 @@ -482,7 +512,8 @@ expression: checks row: 48 column: 20 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 51 column: 0 @@ -498,7 +529,8 @@ expression: checks row: 51 column: 21 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 52 column: 0 @@ -514,7 +546,8 @@ expression: checks row: 52 column: 26 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 53 column: 0 @@ -530,7 +563,8 @@ expression: checks row: 53 column: 14 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 55 column: 5 @@ -546,7 +580,8 @@ expression: checks row: 55 column: 26 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 57 column: 5 @@ -562,7 +597,8 @@ expression: checks row: 57 column: 31 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 59 column: 5 @@ -578,7 +614,8 @@ expression: checks row: 59 column: 19 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 62 column: 0 @@ -594,7 +631,8 @@ expression: checks row: 62 column: 25 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 63 column: 0 @@ -610,7 +648,8 @@ expression: checks row: 63 column: 109 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 64 column: 0 @@ -626,7 +665,8 @@ expression: checks row: 64 column: 68 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: ~ location: row: 65 column: 0 @@ -642,7 +682,8 @@ expression: checks row: 65 column: 15 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 67 column: 0 @@ -658,7 +699,8 @@ expression: checks row: 67 column: 26 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 68 column: 0 @@ -674,7 +716,8 @@ expression: checks row: 68 column: 110 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 69 column: 0 @@ -690,7 +733,8 @@ expression: checks row: 69 column: 69 parent: ~ -- kind: RedundantOpenModes +- kind: + RedundantOpenModes: "\"rb\"" location: row: 70 column: 0 diff --git a/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP018_UP018.py.snap b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP018_UP018.py.snap index 57e785d960..2187e42525 100644 --- a/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP018_UP018.py.snap +++ b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP018_UP018.py.snap @@ -2,7 +2,8 @@ source: src/pyupgrade/mod.rs expression: checks --- -- kind: NativeLiterals +- kind: + NativeLiterals: Str location: row: 18 column: 0 @@ -18,7 +19,8 @@ expression: checks row: 18 column: 5 parent: ~ -- kind: NativeLiterals +- kind: + NativeLiterals: Str location: row: 19 column: 0 @@ -34,7 +36,8 @@ expression: checks row: 19 column: 10 parent: ~ -- kind: NativeLiterals +- kind: + NativeLiterals: Str location: row: 20 column: 0 @@ -50,7 +53,8 @@ expression: checks row: 21 column: 7 parent: ~ -- kind: NativeLiterals +- kind: + NativeLiterals: Bytes location: row: 22 column: 0 @@ -66,7 +70,8 @@ expression: checks row: 22 column: 7 parent: ~ -- kind: NativeLiterals +- kind: + NativeLiterals: Bytes location: row: 23 column: 0 @@ -82,7 +87,8 @@ expression: checks row: 23 column: 13 parent: ~ -- kind: NativeLiterals +- kind: + NativeLiterals: Bytes location: row: 24 column: 0 diff --git a/src/ruff/snapshots/ruff__ruff__tests__ruf100_0.snap b/src/ruff/snapshots/ruff__ruff__tests__ruf100_0.snap index 05b77ad2c3..057a59f8a0 100644 --- a/src/ruff/snapshots/ruff__ruff__tests__ruf100_0.snap +++ b/src/ruff/snapshots/ruff__ruff__tests__ruf100_0.snap @@ -222,6 +222,7 @@ expression: checks UnusedImport: - shelve - false + - false location: row: 85 column: 7 diff --git a/src/ruff/snapshots/ruff__ruff__tests__ruf100_1.snap b/src/ruff/snapshots/ruff__ruff__tests__ruf100_1.snap index d679a44a05..b1d9446df0 100644 --- a/src/ruff/snapshots/ruff__ruff__tests__ruf100_1.snap +++ b/src/ruff/snapshots/ruff__ruff__tests__ruf100_1.snap @@ -6,6 +6,7 @@ expression: checks UnusedImport: - typing.Union - false + - false location: row: 28 column: 4 @@ -111,6 +112,7 @@ expression: checks UnusedImport: - typing.Awaitable - false + - true location: row: 64 column: 19 @@ -130,6 +132,7 @@ expression: checks UnusedImport: - typing.AwaitableGenerator - false + - true location: row: 64 column: 30 diff --git a/tests/integration_test.rs b/tests/integration_test.rs index d7b219be20..e026e965df 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -63,6 +63,7 @@ fn test_stdin_json() -> Result<()> { "message": "`os` imported but unused", "fix": { "content": "", + "message": "Remove unused import: `os`", "location": { "row": 1, "column": 0