From 4ad8db3d6135cee30d53e3c24c7df49f413363da Mon Sep 17 00:00:00 2001 From: Colin Delahunty <72827203+colin99d@users.noreply.github.com> Date: Sat, 31 Dec 2022 21:36:05 +0000 Subject: [PATCH] Pyupgrade: Turn errors into OSError (#1434) --- .gitignore | 1 + README.md | 1 + resources/test/fixtures/pyupgrade/UP024_0.py | 98 +++++ resources/test/fixtures/pyupgrade/UP024_1.py | 17 + resources/test/fixtures/pyupgrade/UP024_2.py | 50 +++ ruff.schema.json | 1 + src/ast/helpers.rs | 67 +--- src/ast/types.rs | 12 +- src/ast/whitespace.rs | 8 +- src/autofix/fixer.rs | 5 +- src/checkers/ast.rs | 27 +- src/checkers/noqa.rs | 10 +- src/checks.rs | 25 +- src/checks_gen.rs | 9 + src/directives.rs | 5 +- src/docstrings/constants.rs | 2 +- src/eradicate/checks.rs | 13 +- src/flake8_quotes/checks.rs | 25 +- src/flake8_simplify/plugins/key_in_dict.rs | 5 +- src/isort/comments.rs | 5 +- src/isort/plugins.rs | 26 +- src/linter.rs | 5 +- src/message.rs | 26 +- src/noqa.rs | 25 +- src/pycodestyle/checks.rs | 23 +- src/pycodestyle/plugins.rs | 8 +- src/pydocstyle/plugins.rs | 34 +- src/pygrep_hooks/plugins/blanket_noqa.rs | 8 +- .../plugins/blanket_type_ignore.rs | 8 +- src/pyupgrade/checks.rs | 10 +- src/pyupgrade/mod.rs | 3 + src/pyupgrade/plugins/mod.rs | 2 + src/pyupgrade/plugins/os_error_alias.rs | 236 ++++++++++++ src/pyupgrade/plugins/redundant_open_modes.rs | 6 +- src/pyupgrade/plugins/remove_six_compat.rs | 8 +- .../plugins/replace_stdout_stderr.rs | 10 +- .../plugins/replace_universal_newlines.rs | 8 +- .../plugins/unnecessary_encode_utf8.rs | 8 +- ...f__pyupgrade__tests__UP024_UP024_0.py.snap | 209 +++++++++++ ...f__pyupgrade__tests__UP024_UP024_1.py.snap | 56 +++ ...f__pyupgrade__tests__UP024_UP024_2.py.snap | 345 ++++++++++++++++++ src/ruff/checks.rs | 10 +- src/source_code_style.rs | 10 +- 43 files changed, 1196 insertions(+), 274 deletions(-) create mode 100644 resources/test/fixtures/pyupgrade/UP024_0.py create mode 100644 resources/test/fixtures/pyupgrade/UP024_1.py create mode 100644 resources/test/fixtures/pyupgrade/UP024_2.py create mode 100644 src/pyupgrade/plugins/os_error_alias.rs create mode 100644 src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_0.py.snap create mode 100644 src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_1.py.snap create mode 100644 src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_2.py.snap diff --git a/.gitignore b/.gitignore index 3da25e783c..4fedb556aa 100644 --- a/.gitignore +++ b/.gitignore @@ -181,3 +181,4 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ +.vimspector.json diff --git a/README.md b/README.md index 6830981e9e..f89ca54308 100644 --- a/README.md +++ b/README.md @@ -682,6 +682,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI. | UP021 | ReplaceUniversalNewlines | `universal_newlines` is deprecated, use `text` | 🛠 | | UP022 | ReplaceStdoutStderr | Sending stdout and stderr to pipe is deprecated, use `capture_output` | 🛠 | | UP023 | RewriteCElementTree | `cElementTree` is deprecated, use `ElementTree` | 🛠 | +| UP024 | OSErrorAlias | Replace aliased errors with `OSError` | 🛠 | | UP025 | RewriteUnicodeLiteral | Remove unicode literals from strings | 🛠 | ### pep8-naming (N) diff --git a/resources/test/fixtures/pyupgrade/UP024_0.py b/resources/test/fixtures/pyupgrade/UP024_0.py new file mode 100644 index 0000000000..5d8c07b8bd --- /dev/null +++ b/resources/test/fixtures/pyupgrade/UP024_0.py @@ -0,0 +1,98 @@ +import mmap, select, socket +from mmap import error +# These should be fixed +try: + pass +except EnvironmentError: + pass + +try: + pass +except IOError: + pass + +try: + pass +except WindowsError: + pass + +try: + pass +except mmap.error: + pass + +try: + pass +except select.error: + pass + +try: + pass +except socket.error: + pass + +try: + pass +except error: + pass + +# Should NOT be in parentheses when replaced + +try: + pass +except (IOError,): + pass +try: + pass +except (mmap.error,): + pass +try: + pass +except (EnvironmentError, IOError, OSError, select.error): + pass + +# Should be kept in parentheses (because multiple) + +try: + pass +except (IOError, KeyError, OSError): + pass + +# First should change, second should not +from .mmap import error +try: + pass +except (IOError, error): + pass +# These should not change + +from foo import error + +try: + pass +except (OSError, error): + pass + +try: + pass +except: + pass + +try: + pass +except AssertionError: + pass +try: + pass +except (mmap).error: + pass + +try: + pass +except OSError: + pass + +try: + pass +except (OSError, KeyError): + pass diff --git a/resources/test/fixtures/pyupgrade/UP024_1.py b/resources/test/fixtures/pyupgrade/UP024_1.py new file mode 100644 index 0000000000..20e00934b0 --- /dev/null +++ b/resources/test/fixtures/pyupgrade/UP024_1.py @@ -0,0 +1,17 @@ +import mmap, socket, select + +try: + pass +except (OSError, mmap.error, IOError): + pass +except (OSError, socket.error, KeyError): + pass + +try: + pass +except ( + OSError, + select.error, + IOError, +): + pass diff --git a/resources/test/fixtures/pyupgrade/UP024_2.py b/resources/test/fixtures/pyupgrade/UP024_2.py new file mode 100644 index 0000000000..25e846e028 --- /dev/null +++ b/resources/test/fixtures/pyupgrade/UP024_2.py @@ -0,0 +1,50 @@ +# These should not change +raise ValueError +raise ValueError(1) + +from .mmap import error +raise error + +# Testing the modules +import socket, mmap, select +raise socket.error +raise mmap.error +raise select.error + +raise socket.error() +raise mmap.error(1) +raise select.error(1, 2) + +raise socket.error( + 1, + 2, + 3, +) + +from mmap import error +raise error + +from socket import error +raise error(1) + +from select import error +raise error(1, 2) + +# Testing the names +raise EnvironmentError +raise IOError +raise WindowsError + +raise EnvironmentError() +raise IOError(1) +raise WindowsError(1, 2) + +raise EnvironmentError( + 1, + 2, + 3, +) + +raise WindowsError +raise EnvironmentError(1) +raise IOError(1, 2) diff --git a/ruff.schema.json b/ruff.schema.json index 78bf843ee2..634fda9166 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -909,6 +909,7 @@ "UP021", "UP022", "UP023", + "UP024", "UP025", "W", "W2", diff --git a/src/ast/helpers.rs b/src/ast/helpers.rs index 1aa3aed821..1fce821bba 100644 --- a/src/ast/helpers.rs +++ b/src/ast/helpers.rs @@ -335,20 +335,17 @@ pub fn to_absolute(relative: Location, base: Location) -> Location { /// Return `true` if a `Stmt` has leading content. pub fn match_leading_content(stmt: &Stmt, locator: &SourceCodeLocator) -> bool { - let range = Range { - location: Location::new(stmt.location.row(), 0), - end_location: stmt.location, - }; + let range = Range::new(Location::new(stmt.location.row(), 0), stmt.location); let prefix = locator.slice_source_code_range(&range); prefix.chars().any(|char| !char.is_whitespace()) } /// Return `true` if a `Stmt` has trailing content. pub fn match_trailing_content(stmt: &Stmt, locator: &SourceCodeLocator) -> bool { - let range = Range { - location: stmt.end_location.unwrap(), - end_location: Location::new(stmt.end_location.unwrap().row() + 1, 0), - }; + let range = Range::new( + stmt.end_location.unwrap(), + Location::new(stmt.end_location.unwrap().row() + 1, 0), + ); let suffix = locator.slice_source_code_range(&range); for char in suffix.chars() { if char == '#' { @@ -384,10 +381,7 @@ pub fn identifier_range(stmt: &Stmt, locator: &SourceCodeLocator) -> Range { let contents = locator.slice_source_code_range(&Range::from_located(stmt)); for (start, tok, end) in lexer::make_tokenizer_located(&contents, stmt.location).flatten() { if matches!(tok, Tok::Name { .. }) { - return Range { - location: start, - end_location: end, - }; + return Range::new(start, end); } } error!("Failed to find identifier for {:?}", stmt); @@ -419,20 +413,15 @@ pub fn excepthandler_name_range( match (name, type_) { (Some(_), Some(type_)) => { let type_end_location = type_.end_location.unwrap(); - let contents = locator.slice_source_code_range(&Range { - location: type_end_location, - end_location: body[0].location, - }); + let contents = + locator.slice_source_code_range(&Range::new(type_end_location, body[0].location)); let range = lexer::make_tokenizer_located(&contents, type_end_location) .flatten() .tuple_windows() .find(|(tok, next_tok)| { matches!(tok.1, Tok::As) && matches!(next_tok.1, Tok::Name { .. }) }) - .map(|((..), (location, _, end_location))| Range { - location, - end_location, - }); + .map(|((..), (location, _, end_location))| Range::new(location, end_location)); range } _ => None, @@ -506,10 +495,10 @@ pub fn preceded_by_continuation(stmt: &Stmt, locator: &SourceCodeLocator) -> boo // make conservative choices. // TODO(charlie): Come up with a more robust strategy. if stmt.location.row() > 1 { - let range = Range { - location: Location::new(stmt.location.row() - 1, 0), - end_location: Location::new(stmt.location.row(), 0), - }; + let range = Range::new( + Location::new(stmt.location.row() - 1, 0), + Location::new(stmt.location.row(), 0), + ); let line = locator.slice_source_code_range(&range); if line.trim().ends_with('\\') { return true; @@ -733,10 +722,7 @@ y = 2 let locator = SourceCodeLocator::new(contents); assert_eq!( identifier_range(stmt, &locator), - Range { - location: Location::new(1, 4), - end_location: Location::new(1, 5), - } + Range::new(Location::new(1, 4), Location::new(1, 5),) ); let contents = r#" @@ -750,10 +736,7 @@ def \ let locator = SourceCodeLocator::new(contents); assert_eq!( identifier_range(stmt, &locator), - Range { - location: Location::new(2, 2), - end_location: Location::new(2, 3), - } + Range::new(Location::new(2, 2), Location::new(2, 3),) ); let contents = "class Class(): pass".trim(); @@ -762,10 +745,7 @@ def \ let locator = SourceCodeLocator::new(contents); assert_eq!( identifier_range(stmt, &locator), - Range { - location: Location::new(1, 6), - end_location: Location::new(1, 11), - } + Range::new(Location::new(1, 6), Location::new(1, 11),) ); let contents = "class Class: pass".trim(); @@ -774,10 +754,7 @@ def \ let locator = SourceCodeLocator::new(contents); assert_eq!( identifier_range(stmt, &locator), - Range { - location: Location::new(1, 6), - end_location: Location::new(1, 11), - } + Range::new(Location::new(1, 6), Location::new(1, 11),) ); let contents = r#" @@ -791,10 +768,7 @@ class Class(): let locator = SourceCodeLocator::new(contents); assert_eq!( identifier_range(stmt, &locator), - Range { - location: Location::new(2, 6), - end_location: Location::new(2, 11), - } + Range::new(Location::new(2, 6), Location::new(2, 11),) ); let contents = r#"x = y + 1"#.trim(); @@ -803,10 +777,7 @@ class Class(): let locator = SourceCodeLocator::new(contents); assert_eq!( identifier_range(stmt, &locator), - Range { - location: Location::new(1, 0), - end_location: Location::new(1, 9), - } + Range::new(Location::new(1, 0), Location::new(1, 9),) ); Ok(()) diff --git a/src/ast/types.rs b/src/ast/types.rs index e5e1bfc010..edca211017 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -22,12 +22,16 @@ pub struct Range { } impl Range { - pub fn from_located(located: &Located) -> Self { - Range { - location: located.location, - end_location: located.end_location.unwrap(), + pub fn new(location: Location, end_location: Location) -> Self { + Self { + location, + end_location, } } + + pub fn from_located(located: &Located) -> Self { + Range::new(located.location, located.end_location.unwrap()) + } } #[derive(Debug)] diff --git a/src/ast/whitespace.rs b/src/ast/whitespace.rs index 434200e497..b4e1fa60f5 100644 --- a/src/ast/whitespace.rs +++ b/src/ast/whitespace.rs @@ -9,10 +9,10 @@ use crate::checkers::ast::Checker; /// Extract the leading indentation from a line. pub fn indentation<'a, T>(checker: &'a Checker, located: &'a Located) -> Cow<'a, str> { let range = Range::from_located(located); - checker.locator.slice_source_code_range(&Range { - location: Location::new(range.location.row(), 0), - end_location: Location::new(range.location.row(), range.location.column()), - }) + checker.locator.slice_source_code_range(&Range::new( + Location::new(range.location.row(), 0), + Location::new(range.location.row(), range.location.column()), + )) } /// Extract the leading words from a line of text. diff --git a/src/autofix/fixer.rs b/src/autofix/fixer.rs index 9ecec87e32..0e985f26fc 100644 --- a/src/autofix/fixer.rs +++ b/src/autofix/fixer.rs @@ -68,10 +68,7 @@ fn apply_fixes<'a>( } // Add all contents from `last_pos` to `fix.location`. - let slice = locator.slice_source_code_range(&Range { - location: last_pos, - end_location: fix.location, - }); + let slice = locator.slice_source_code_range(&Range::new(last_pos, fix.location)); output.append(&slice); // Add the patch itself. diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index 985a1cf438..e956c8b305 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -215,10 +215,10 @@ impl<'a> Checker<'a> { return false; } let noqa_lineno = self.noqa_line_for.get(&lineno).unwrap_or(&lineno); - let line = self.locator.slice_source_code_range(&Range { - location: Location::new(*noqa_lineno, 0), - end_location: Location::new(noqa_lineno + 1, 0), - }); + let line = self.locator.slice_source_code_range(&Range::new( + Location::new(*noqa_lineno, 0), + Location::new(noqa_lineno + 1, 0), + )); match noqa::extract_noqa_directive(&line) { Directive::None => false, Directive::All(..) => true, @@ -1107,6 +1107,11 @@ where flake8_errmsg::plugins::string_in_exception(self, exc); } } + if self.settings.enabled.contains(&CheckCode::UP024) { + if let Some(item) = exc { + pyupgrade::plugins::os_error_alias(self, item); + } + } } StmtKind::AugAssign { target, .. } => { self.handle_node_load(target); @@ -1191,6 +1196,9 @@ where if self.settings.enabled.contains(&CheckCode::B013) { flake8_bugbear::plugins::redundant_tuple_in_exception_handler(self, handlers); } + if self.settings.enabled.contains(&CheckCode::UP024) { + pyupgrade::plugins::os_error_alias(self, handlers); + } } StmtKind::Assign { targets, value, .. } => { if self.settings.enabled.contains(&CheckCode::E731) { @@ -1716,6 +1724,9 @@ where if self.settings.enabled.contains(&CheckCode::UP022) { pyupgrade::plugins::replace_stdout_stderr(self, expr, keywords); } + if self.settings.enabled.contains(&CheckCode::UP024) { + pyupgrade::plugins::os_error_alias(self, expr); + } // flake8-print if self.settings.enabled.contains(&CheckCode::T201) @@ -3869,10 +3880,10 @@ impl<'a> Checker<'a> { let content = self .locator .slice_source_code_range(&Range::from_located(expr)); - let indentation = self.locator.slice_source_code_range(&Range { - location: Location::new(expr.location.row(), 0), - end_location: Location::new(expr.location.row(), expr.location.column()), - }); + let indentation = self.locator.slice_source_code_range(&Range::new( + Location::new(expr.location.row(), 0), + Location::new(expr.location.row(), expr.location.column()), + )); let body = pydocstyle::helpers::raw_contents(&content); let docstring = Docstring { kind: definition.kind, diff --git a/src/checkers/noqa.rs b/src/checkers/noqa.rs index f8d5853077..df512fffe4 100644 --- a/src/checkers/noqa.rs +++ b/src/checkers/noqa.rs @@ -102,10 +102,7 @@ pub fn check_noqa( if matches.is_empty() { let mut check = Check::new( CheckKind::UnusedNOQA(None), - Range { - location: Location::new(row + 1, start), - end_location: Location::new(row + 1, end), - }, + Range::new(Location::new(row + 1, start), Location::new(row + 1, end)), ); if matches!(autofix, flags::Autofix::Enabled) && settings.fixable.contains(check.kind.code()) @@ -169,10 +166,7 @@ pub fn check_noqa( .map(|code| (*code).to_string()) .collect(), })), - Range { - location: Location::new(row + 1, start), - end_location: Location::new(row + 1, end), - }, + Range::new(Location::new(row + 1, start), Location::new(row + 1, end)), ); if matches!(autofix, flags::Autofix::Enabled) && settings.fixable.contains(check.kind.code()) diff --git a/src/checks.rs b/src/checks.rs index 64bf99fe1a..aba6fc57b7 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -236,6 +236,7 @@ pub enum CheckCode { UP021, UP022, UP023, + UP024, UP025, // pydocstyle D100, @@ -907,6 +908,7 @@ pub enum CheckKind { ReplaceUniversalNewlines, ReplaceStdoutStderr, RewriteCElementTree, + OSErrorAlias(Option), RewriteUnicodeLiteral, // pydocstyle BlankLineAfterLastSection(String), @@ -1301,6 +1303,7 @@ impl CheckCode { CheckCode::UP021 => CheckKind::ReplaceUniversalNewlines, CheckCode::UP022 => CheckKind::ReplaceStdoutStderr, CheckCode::UP023 => CheckKind::RewriteCElementTree, + CheckCode::UP024 => CheckKind::OSErrorAlias(None), CheckCode::UP025 => CheckKind::RewriteUnicodeLiteral, // pydocstyle CheckCode::D100 => CheckKind::PublicModule, @@ -1733,6 +1736,7 @@ impl CheckCode { CheckCode::UP021 => CheckCategory::Pyupgrade, CheckCode::UP022 => CheckCategory::Pyupgrade, CheckCode::UP023 => CheckCategory::Pyupgrade, + CheckCode::UP024 => CheckCategory::Pyupgrade, CheckCode::UP025 => CheckCategory::Pyupgrade, CheckCode::W292 => CheckCategory::Pycodestyle, CheckCode::W605 => CheckCategory::Pycodestyle, @@ -1955,6 +1959,7 @@ impl CheckKind { CheckKind::ReplaceUniversalNewlines => &CheckCode::UP021, CheckKind::ReplaceStdoutStderr => &CheckCode::UP022, CheckKind::RewriteCElementTree => &CheckCode::UP023, + CheckKind::OSErrorAlias(..) => &CheckCode::UP024, CheckKind::RewriteUnicodeLiteral => &CheckCode::UP025, // pydocstyle CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413, @@ -2715,6 +2720,7 @@ impl CheckKind { CheckKind::RewriteCElementTree => { "`cElementTree` is deprecated, use `ElementTree`".to_string() } + CheckKind::OSErrorAlias(..) => "Replace aliased errors with `OSError`".to_string(), CheckKind::RewriteUnicodeLiteral => "Remove unicode literals from strings".to_string(), // pydocstyle CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(), @@ -3129,8 +3135,8 @@ impl CheckKind { matches!( self, CheckKind::AmbiguousUnicodeCharacterString(..) - | CheckKind::AmbiguousUnicodeCharacterDocstring(..) | CheckKind::AmbiguousUnicodeCharacterComment(..) + | CheckKind::AmbiguousUnicodeCharacterDocstring(..) | CheckKind::BlankLineAfterLastSection(..) | CheckKind::BlankLineAfterSection(..) | CheckKind::BlankLineAfterSummary @@ -3156,12 +3162,7 @@ impl CheckKind { | CheckKind::MisplacedComparisonConstant(..) | CheckKind::MissingReturnTypeSpecialMethod(..) | CheckKind::NativeLiterals(..) - | CheckKind::OpenAlias | CheckKind::NewLineAfterLastParagraph - | CheckKind::ReplaceUniversalNewlines - | CheckKind::ReplaceStdoutStderr - | CheckKind::RewriteCElementTree - | CheckKind::RewriteUnicodeLiteral | CheckKind::NewLineAfterSectionName(..) | CheckKind::NoBlankLineAfterFunction(..) | CheckKind::NoBlankLineBeforeClass(..) @@ -3174,16 +3175,22 @@ impl CheckKind { | CheckKind::NoneComparison(..) | CheckKind::NotInTest | CheckKind::NotIsTest + | CheckKind::OSErrorAlias(..) | CheckKind::OneBlankLineAfterClass(..) | CheckKind::OneBlankLineBeforeClass(..) + | CheckKind::OpenAlias | CheckKind::PEP3120UnnecessaryCodingComment | CheckKind::PPrintFound - | CheckKind::PrintFound | CheckKind::PercentFormatExtraNamedArguments(..) + | CheckKind::PrintFound | CheckKind::RaiseNotImplemented | CheckKind::RedundantOpenModes(..) | CheckKind::RedundantTupleInExceptionHandler(..) | CheckKind::RemoveSixCompat + | CheckKind::ReplaceStdoutStderr + | CheckKind::ReplaceUniversalNewlines + | CheckKind::RewriteCElementTree + | CheckKind::RewriteUnicodeLiteral | CheckKind::SectionNameEndsInColon(..) | CheckKind::SectionNotOverIndented(..) | CheckKind::SectionUnderlineAfterName(..) @@ -3313,6 +3320,10 @@ impl CheckKind { CheckKind::OneBlankLineAfterClass(..) => { Some("Insert 1 blank line after class docstring".to_string()) } + CheckKind::OSErrorAlias(name) => Some(match name { + None => "Replace with builtin `OSError`".to_string(), + Some(name) => format!("Replace `{name}` with builtin `OSError`"), + }), CheckKind::NoBlankLinesBetweenHeaderAndContent(..) => { Some("Remove blank line(s)".to_string()) } diff --git a/src/checks_gen.rs b/src/checks_gen.rs index 87150b7bd7..8f0e26ede2 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -540,6 +540,7 @@ pub enum CheckCodePrefix { UP021, UP022, UP023, + UP024, UP025, W, W2, @@ -773,6 +774,7 @@ impl CheckCodePrefix { CheckCode::UP021, CheckCode::UP022, CheckCode::UP023, + CheckCode::UP024, CheckCode::UP025, CheckCode::D100, CheckCode::D101, @@ -2452,6 +2454,7 @@ impl CheckCodePrefix { CheckCode::UP021, CheckCode::UP022, CheckCode::UP023, + CheckCode::UP024, CheckCode::UP025, ] } @@ -2485,6 +2488,7 @@ impl CheckCodePrefix { CheckCode::UP021, CheckCode::UP022, CheckCode::UP023, + CheckCode::UP024, CheckCode::UP025, ] } @@ -2702,6 +2706,7 @@ impl CheckCodePrefix { CheckCode::UP021, CheckCode::UP022, CheckCode::UP023, + CheckCode::UP024, CheckCode::UP025, ], CheckCodePrefix::UP0 => vec![ @@ -2727,6 +2732,7 @@ impl CheckCodePrefix { CheckCode::UP021, CheckCode::UP022, CheckCode::UP023, + CheckCode::UP024, CheckCode::UP025, ], CheckCodePrefix::UP00 => vec![ @@ -2774,12 +2780,14 @@ impl CheckCodePrefix { CheckCode::UP021, CheckCode::UP022, CheckCode::UP023, + CheckCode::UP024, CheckCode::UP025, ], CheckCodePrefix::UP020 => vec![CheckCode::UP020], CheckCodePrefix::UP021 => vec![CheckCode::UP021], CheckCodePrefix::UP022 => vec![CheckCode::UP022], CheckCodePrefix::UP023 => vec![CheckCode::UP023], + CheckCodePrefix::UP024 => vec![CheckCode::UP024], CheckCodePrefix::UP025 => vec![CheckCode::UP025], CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605], CheckCodePrefix::W2 => vec![CheckCode::W292], @@ -3352,6 +3360,7 @@ impl CheckCodePrefix { CheckCodePrefix::UP021 => SuffixLength::Three, CheckCodePrefix::UP022 => SuffixLength::Three, CheckCodePrefix::UP023 => SuffixLength::Three, + CheckCodePrefix::UP024 => SuffixLength::Three, CheckCodePrefix::UP025 => SuffixLength::Three, CheckCodePrefix::W => SuffixLength::Zero, CheckCodePrefix::W2 => SuffixLength::One, diff --git a/src/directives.rs b/src/directives.rs index 1275dab486..0c4a0fcf99 100644 --- a/src/directives.rs +++ b/src/directives.rs @@ -110,10 +110,7 @@ pub fn extract_isort_directives(lxr: &[LexResult], locator: &SourceCodeLocator) } // TODO(charlie): Modify RustPython to include the comment text in the token. - let comment_text = locator.slice_source_code_range(&Range { - location: start, - end_location: end, - }); + let comment_text = locator.slice_source_code_range(&Range::new(start, end)); if comment_text == "# isort: split" { splits.push(start.row()); diff --git a/src/docstrings/constants.rs b/src/docstrings/constants.rs index 2ef0cc1c69..efdd0a1602 100644 --- a/src/docstrings/constants.rs +++ b/src/docstrings/constants.rs @@ -1,4 +1,4 @@ -// See: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals. +/// See: pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[ "u\"\"\"", "u'''", "r\"\"\"", "r'''", "U\"\"\"", "U'''", "R\"\"\"", "R'''", "\"\"\"", "'''", diff --git a/src/eradicate/checks.rs b/src/eradicate/checks.rs index a0af0a8d42..dff4cda8d9 100644 --- a/src/eradicate/checks.rs +++ b/src/eradicate/checks.rs @@ -28,20 +28,11 @@ pub fn commented_out_code( ) -> Option { let location = Location::new(start.row(), 0); let end_location = Location::new(end.row() + 1, 0); - let line = locator.slice_source_code_range(&Range { - location, - end_location, - }); + let line = locator.slice_source_code_range(&Range::new(location, end_location)); // Verify that the comment is on its own line, and that it contains code. if is_standalone_comment(&line) && comment_contains_code(&line) { - let mut check = Check::new( - CheckKind::CommentedOutCode, - Range { - location: start, - end_location: end, - }, - ); + let mut check = Check::new(CheckKind::CommentedOutCode, Range::new(start, end)); if matches!(autofix, flags::Autofix::Enabled) && settings.fixable.contains(&CheckCode::ERA001) { diff --git a/src/flake8_quotes/checks.rs b/src/flake8_quotes/checks.rs index 0a4417461b..b9dc552a02 100644 --- a/src/flake8_quotes/checks.rs +++ b/src/flake8_quotes/checks.rs @@ -47,10 +47,7 @@ pub fn quotes( is_docstring: bool, settings: &Settings, ) -> Option { - let text = locator.slice_source_code_range(&Range { - location: start, - end_location: end, - }); + let text = locator.slice_source_code_range(&Range::new(start, end)); // Remove any prefixes (e.g., remove `u` from `u"foo"`). let last_quote_char = text.chars().last().unwrap(); @@ -76,10 +73,7 @@ pub fn quotes( Some(Check::new( CheckKind::BadQuotesDocstring(settings.docstring_quotes.clone()), - Range { - location: start, - end_location: end, - }, + Range::new(start, end), )) } else if is_multiline { // If our string is or contains a known good string, ignore it. @@ -94,10 +88,7 @@ pub fn quotes( Some(Check::new( CheckKind::BadQuotesMultilineString(settings.multiline_quotes.clone()), - Range { - location: start, - end_location: end, - }, + Range::new(start, end), )) } else { let string_contents = &raw_text[1..raw_text.len() - 1]; @@ -112,10 +103,7 @@ pub fn quotes( { return Some(Check::new( CheckKind::AvoidQuoteEscape, - Range { - location: start, - end_location: end, - }, + Range::new(start, end), )); } return None; @@ -125,10 +113,7 @@ pub fn quotes( if !string_contents.contains(good_single(&settings.inline_quotes)) { return Some(Check::new( CheckKind::BadQuotesInlineString(settings.inline_quotes.clone()), - Range { - location: start, - end_location: end, - }, + Range::new(start, end), )); } diff --git a/src/flake8_simplify/plugins/key_in_dict.rs b/src/flake8_simplify/plugins/key_in_dict.rs index 4df6e16187..bedd1954a3 100644 --- a/src/flake8_simplify/plugins/key_in_dict.rs +++ b/src/flake8_simplify/plugins/key_in_dict.rs @@ -46,10 +46,7 @@ pub fn key_in_dict_for(checker: &mut Checker, target: &Expr, iter: &Expr) { checker, target, iter, - Range { - location: target.location, - end_location: iter.end_location.unwrap(), - }, + Range::new(target.location, iter.end_location.unwrap()), ); } diff --git a/src/isort/comments.rs b/src/isort/comments.rs index a6202bba30..e20337e0f6 100644 --- a/src/isort/comments.rs +++ b/src/isort/comments.rs @@ -22,10 +22,7 @@ pub fn collect_comments<'a>(range: &Range, locator: &'a SourceCodeLocator) -> Ve .filter_map(|(start, tok, end)| { if matches!(tok, Tok::Comment) { Some(Comment { - value: locator.slice_source_code_range(&Range { - location: start, - end_location: end, - }), + value: locator.slice_source_code_range(&Range::new(start, end)), location: start, end_location: end, }) diff --git a/src/isort/plugins.rs b/src/isort/plugins.rs index 9deacf4afc..353f3a12ad 100644 --- a/src/isort/plugins.rs +++ b/src/isort/plugins.rs @@ -19,18 +19,12 @@ use crate::{Check, Settings, SourceCodeLocator}; fn extract_range(body: &[&Stmt]) -> Range { let location = body.first().unwrap().location; let end_location = body.last().unwrap().end_location.unwrap(); - Range { - location, - end_location, - } + Range::new(location, end_location) } fn extract_indentation_range(body: &[&Stmt]) -> Range { let location = body.first().unwrap().location; - Range { - location: Location::new(location.row(), 0), - end_location: location, - } + Range::new(Location::new(location.row(), 0), location) } /// I001 @@ -57,10 +51,10 @@ pub fn check_imports( // Extract comments. Take care to grab any inline comments from the last line. let comments = comments::collect_comments( - &Range { - location: range.location, - end_location: Location::new(range.end_location.row() + 1, 0), - }, + &Range::new( + range.location, + Location::new(range.end_location.row() + 1, 0), + ), locator, ); @@ -90,10 +84,10 @@ pub fn check_imports( ); // Expand the span the entire range, including leading and trailing space. - let range = Range { - location: Location::new(range.location.row(), 0), - end_location: Location::new(range.end_location.row() + 1 + num_trailing_lines, 0), - }; + let range = Range::new( + Location::new(range.location.row(), 0), + Location::new(range.end_location.row() + 1 + num_trailing_lines, 0), + ); let actual = dedent(&locator.slice_source_code_range(&range)); if actual == dedent(&expected) { None diff --git a/src/linter.rs b/src/linter.rs index 275b3505fe..2bb725fc09 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -120,10 +120,7 @@ pub(crate) fn check_path( if settings.enabled.contains(&CheckCode::E999) { checks.push(Check::new( CheckKind::SyntaxError(parse_error.error.to_string()), - Range { - location: parse_error.location, - end_location: parse_error.location, - }, + Range::new(parse_error.location, parse_error.location), )); } } diff --git a/src/message.rs b/src/message.rs index c9af0b96d6..ea11a947e1 100644 --- a/src/message.rs +++ b/src/message.rs @@ -55,22 +55,18 @@ pub struct Source { impl Source { pub fn from_check(check: &Check, locator: &SourceCodeLocator) -> Self { - let source = locator.slice_source_code_range(&Range { - location: Location::new(check.location.row(), 0), - // Checks can already extend one-past-the-end per Ropey's semantics. If they do, though, - // then they'll end at the start of a line. We need to avoid extending by yet another - // line past-the-end. - end_location: if check.end_location.column() == 0 { - check.end_location - } else { - Location::new(check.end_location.row() + 1, 0) - }, - }); + let location = Location::new(check.location.row(), 0); + // Checks can already extend one-past-the-end per Ropey's semantics. If they do, + // though, then they'll end at the start of a line. We need to avoid + // extending by yet another line past-the-end. + let end_location = if check.end_location.column() == 0 { + check.end_location + } else { + Location::new(check.end_location.row() + 1, 0) + }; + let source = locator.slice_source_code_range(&Range::new(location, end_location)); let num_chars_in_range = locator - .slice_source_code_range(&Range { - location: check.location, - end_location: check.end_location, - }) + .slice_source_code_range(&Range::new(check.location, check.end_location)) .chars() .count(); Source { diff --git a/src/noqa.rs b/src/noqa.rs index 7099759c96..a6065dc209 100644 --- a/src/noqa.rs +++ b/src/noqa.rs @@ -248,10 +248,7 @@ mod tests { let checks = vec![Check::new( CheckKind::UnusedVariable("x".to_string()), - Range { - location: Location::new(1, 0), - end_location: Location::new(1, 0), - }, + Range::new(Location::new(1, 0), Location::new(1, 0)), )]; let contents = "x = 1"; let noqa_line_for = IntMap::default(); @@ -269,17 +266,11 @@ mod tests { let checks = vec![ Check::new( CheckKind::AmbiguousVariableName("x".to_string()), - Range { - location: Location::new(1, 0), - end_location: Location::new(1, 0), - }, + Range::new(Location::new(1, 0), Location::new(1, 0)), ), Check::new( CheckKind::UnusedVariable("x".to_string()), - Range { - location: Location::new(1, 0), - end_location: Location::new(1, 0), - }, + Range::new(Location::new(1, 0), Location::new(1, 0)), ), ]; let contents = "x = 1 # noqa: E741\n"; @@ -298,17 +289,11 @@ mod tests { let checks = vec![ Check::new( CheckKind::AmbiguousVariableName("x".to_string()), - Range { - location: Location::new(1, 0), - end_location: Location::new(1, 0), - }, + Range::new(Location::new(1, 0), Location::new(1, 0)), ), Check::new( CheckKind::UnusedVariable("x".to_string()), - Range { - location: Location::new(1, 0), - end_location: Location::new(1, 0), - }, + Range::new(Location::new(1, 0), Location::new(1, 0)), ), ]; let contents = "x = 1 # noqa"; diff --git a/src/pycodestyle/checks.rs b/src/pycodestyle/checks.rs index 118a8bf219..fca3b4f8a2 100644 --- a/src/pycodestyle/checks.rs +++ b/src/pycodestyle/checks.rs @@ -34,10 +34,10 @@ pub fn line_too_long(lineno: usize, line: &str, max_line_length: usize) -> Optio Some(Check::new( CheckKind::LineTooLong(line_length, max_line_length), - Range { - location: Location::new(lineno + 1, max_line_length), - end_location: Location::new(lineno + 1, line_length), - }, + Range::new( + Location::new(lineno + 1, max_line_length), + Location::new(lineno + 1, line_length), + ), )) } @@ -162,10 +162,7 @@ pub fn no_newline_at_end_of_file(contents: &str, autofix: bool) -> Option let location = Location::new(contents.lines().count(), line.len()); let mut check = Check::new( CheckKind::NoNewLineAtEndOfFile, - Range { - location, - end_location: location, - }, + Range::new(location, location), ); if autofix { check.amend(Fix::insertion("\n".to_string(), location)); @@ -203,10 +200,7 @@ pub fn invalid_escape_sequence( ) -> Vec { let mut checks = vec![]; - let text = locator.slice_source_code_range(&Range { - location: start, - end_location: end, - }); + let text = locator.slice_source_code_range(&Range::new(start, end)); // Determine whether the string is single- or triple-quoted. let quote = extract_quote(&text); @@ -249,10 +243,7 @@ pub fn invalid_escape_sequence( let end_location = Location::new(location.row(), location.column() + 2); let mut check = Check::new( CheckKind::InvalidEscapeSequence(next_char), - Range { - location, - end_location, - }, + Range::new(location, end_location), ); if autofix { check.amend(Fix::insertion(r"\".to_string(), location)); diff --git a/src/pycodestyle/plugins.rs b/src/pycodestyle/plugins.rs index f751ff5761..1a9c0d317c 100644 --- a/src/pycodestyle/plugins.rs +++ b/src/pycodestyle/plugins.rs @@ -329,10 +329,10 @@ pub fn do_not_assign_lambda(checker: &mut Checker, target: &Expr, value: &Expr, { match function(id, args, body, checker.style) { Ok(content) => { - let first_line = checker.locator.slice_source_code_range(&Range { - location: Location::new(stmt.location.row(), 0), - end_location: Location::new(stmt.location.row() + 1, 0), - }); + let first_line = checker.locator.slice_source_code_range(&Range::new( + Location::new(stmt.location.row(), 0), + Location::new(stmt.location.row() + 1, 0), + )); let indentation = &leading_space(&first_line); let mut indented = String::new(); for (idx, line) in content.lines().enumerate() { diff --git a/src/pydocstyle/plugins.rs b/src/pydocstyle/plugins.rs index 3fe58ca45b..dc5ab51038 100644 --- a/src/pydocstyle/plugins.rs +++ b/src/pydocstyle/plugins.rs @@ -34,10 +34,7 @@ pub fn not_missing( if checker.settings.enabled.contains(&CheckCode::D100) { checker.add_check(Check::new( CheckKind::PublicModule, - Range { - location: Location::new(1, 0), - end_location: Location::new(1, 0), - }, + Range::new(Location::new(1, 0), Location::new(1, 0)), )); } false @@ -46,10 +43,7 @@ pub fn not_missing( if checker.settings.enabled.contains(&CheckCode::D104) { checker.add_check(Check::new( CheckKind::PublicPackage, - Range { - location: Location::new(1, 0), - end_location: Location::new(1, 0), - }, + Range::new(Location::new(1, 0), Location::new(1, 0)), )); } false @@ -412,10 +406,10 @@ pub fn indent(checker: &mut Checker, docstring: &Docstring) { { let mut check = Check::new( CheckKind::NoUnderIndentation, - Range { - location: Location::new(docstring.expr.location.row() + i, 0), - end_location: Location::new(docstring.expr.location.row() + i, 0), - }, + Range::new( + Location::new(docstring.expr.location.row() + i, 0), + Location::new(docstring.expr.location.row() + i, 0), + ), ); if checker.patch(check.kind.code()) { check.amend(Fix::replacement( @@ -462,10 +456,10 @@ pub fn indent(checker: &mut Checker, docstring: &Docstring) { // enables autofix. let mut check = Check::new( CheckKind::NoOverIndentation, - Range { - location: Location::new(docstring.expr.location.row() + i, 0), - end_location: Location::new(docstring.expr.location.row() + i, 0), - }, + Range::new( + Location::new(docstring.expr.location.row() + i, 0), + Location::new(docstring.expr.location.row() + i, 0), + ), ); if checker.patch(check.kind.code()) { check.amend(Fix::replacement( @@ -486,10 +480,10 @@ pub fn indent(checker: &mut Checker, docstring: &Docstring) { if line_indent.len() > docstring.indentation.len() { let mut check = Check::new( CheckKind::NoOverIndentation, - Range { - location: Location::new(docstring.expr.location.row() + i, 0), - end_location: Location::new(docstring.expr.location.row() + i, 0), - }, + Range::new( + Location::new(docstring.expr.location.row() + i, 0), + Location::new(docstring.expr.location.row() + i, 0), + ), ); if checker.patch(check.kind.code()) { check.amend(Fix::replacement( diff --git a/src/pygrep_hooks/plugins/blanket_noqa.rs b/src/pygrep_hooks/plugins/blanket_noqa.rs index cbfcc12665..8d0386b320 100644 --- a/src/pygrep_hooks/plugins/blanket_noqa.rs +++ b/src/pygrep_hooks/plugins/blanket_noqa.rs @@ -13,10 +13,10 @@ pub fn blanket_noqa(lineno: usize, line: &str) -> Option { BLANKET_NOQA_REGEX.find(line).map(|m| { Check::new( CheckKind::BlanketNOQA, - Range { - location: Location::new(lineno + 1, m.start()), - end_location: Location::new(lineno + 1, m.end()), - }, + Range::new( + Location::new(lineno + 1, m.start()), + Location::new(lineno + 1, m.end()), + ), ) }) } diff --git a/src/pygrep_hooks/plugins/blanket_type_ignore.rs b/src/pygrep_hooks/plugins/blanket_type_ignore.rs index 7a44c7c4cd..a95fa5826e 100644 --- a/src/pygrep_hooks/plugins/blanket_type_ignore.rs +++ b/src/pygrep_hooks/plugins/blanket_type_ignore.rs @@ -13,10 +13,10 @@ pub fn blanket_type_ignore(lineno: usize, line: &str) -> Option { BLANKET_TYPE_IGNORE_REGEX.find(line).map(|m| { Check::new( CheckKind::BlanketTypeIgnore, - Range { - location: Location::new(lineno + 1, m.start()), - end_location: Location::new(lineno + 1, m.end()), - }, + Range::new( + Location::new(lineno + 1, m.start()), + Location::new(lineno + 1, m.end()), + ), ) }) } diff --git a/src/pyupgrade/checks.rs b/src/pyupgrade/checks.rs index 5463acd195..b04ede9b8e 100644 --- a/src/pyupgrade/checks.rs +++ b/src/pyupgrade/checks.rs @@ -170,10 +170,7 @@ pub fn unnecessary_coding_comment(lineno: usize, line: &str, autofix: bool) -> O if CODING_COMMENT_REGEX.is_match(line) { let mut check = Check::new( CheckKind::PEP3120UnnecessaryCodingComment, - Range { - location: Location::new(lineno + 1, 0), - end_location: Location::new(lineno + 2, 0), - }, + Range::new(Location::new(lineno + 1, 0), Location::new(lineno + 2, 0)), ); if autofix { check.amend(Fix::deletion( @@ -216,10 +213,7 @@ pub fn unnecessary_lru_cache_params( continue; } - let range = Range { - location: func.end_location.unwrap(), - end_location: expr.end_location.unwrap(), - }; + let range = Range::new(func.end_location.unwrap(), expr.end_location.unwrap()); // Ex) `functools.lru_cache()` if keywords.is_empty() { return Some(Check::new(CheckKind::UnnecessaryLRUCacheParams, range)); diff --git a/src/pyupgrade/mod.rs b/src/pyupgrade/mod.rs index 7b5d7ac58a..b74c255331 100644 --- a/src/pyupgrade/mod.rs +++ b/src/pyupgrade/mod.rs @@ -42,6 +42,9 @@ mod tests { #[test_case(CheckCode::UP021, Path::new("UP021.py"); "UP021")] #[test_case(CheckCode::UP022, Path::new("UP022.py"); "UP022")] #[test_case(CheckCode::UP023, Path::new("UP023.py"); "UP023")] + #[test_case(CheckCode::UP024, Path::new("UP024_0.py"); "UP024_0")] + #[test_case(CheckCode::UP024, Path::new("UP024_1.py"); "UP024_1")] + #[test_case(CheckCode::UP024, Path::new("UP024_2.py"); "UP024_2")] #[test_case(CheckCode::UP025, Path::new("UP025.py"); "UP025")] fn checks(check_code: CheckCode, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy()); diff --git a/src/pyupgrade/plugins/mod.rs b/src/pyupgrade/plugins/mod.rs index 68396962d5..66c205eea7 100644 --- a/src/pyupgrade/plugins/mod.rs +++ b/src/pyupgrade/plugins/mod.rs @@ -4,6 +4,7 @@ pub use datetime_utc_alias::datetime_utc_alias; pub use deprecated_unittest_alias::deprecated_unittest_alias; pub use native_literals::native_literals; pub use open_alias::open_alias; +pub use os_error_alias::os_error_alias; pub use redundant_open_modes::redundant_open_modes; pub use remove_six_compat::remove_six_compat; pub use replace_stdout_stderr::replace_stdout_stderr; @@ -27,6 +28,7 @@ mod datetime_utc_alias; mod deprecated_unittest_alias; mod native_literals; mod open_alias; +mod os_error_alias; mod redundant_open_modes; mod remove_six_compat; mod replace_stdout_stderr; diff --git a/src/pyupgrade/plugins/os_error_alias.rs b/src/pyupgrade/plugins/os_error_alias.rs new file mode 100644 index 0000000000..955cbe73c7 --- /dev/null +++ b/src/pyupgrade/plugins/os_error_alias.rs @@ -0,0 +1,236 @@ +#![allow(clippy::len_zero, clippy::needless_pass_by_value)] + +use itertools::Itertools; +use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Located}; + +use crate::ast::helpers::{compose_call_path, match_module_member}; +use crate::ast::types::Range; +use crate::autofix::Fix; +use crate::checkers::ast::Checker; +use crate::checks::{Check, CheckKind}; + +const ERROR_NAMES: &[&str] = &["EnvironmentError", "IOError", "WindowsError"]; +const ERROR_MODULES: &[&str] = &["mmap", "select", "socket"]; + +fn get_correct_name(original: &str) -> String { + if ERROR_NAMES.contains(&original) { + "OSError".to_string() + } else { + original.to_string() + } +} + +fn get_before_replace(elts: &[Expr]) -> Vec { + elts.iter() + .map(|elt| { + if let ExprKind::Name { id, .. } = &elt.node { + id.to_string() + } else { + String::new() + } + }) + .collect() +} + +fn check_module(checker: &Checker, expr: &Expr) -> (Vec, Vec) { + let mut replacements: Vec = vec![]; + let mut before_replace: Vec = vec![]; + for module in ERROR_MODULES.iter() { + if match_module_member( + expr, + module, + "error", + &checker.from_imports, + &checker.import_aliases, + ) { + replacements.push("OSError".to_string()); + before_replace.push(format!("{module}.error")); + break; + } + } + (replacements, before_replace) +} + +fn handle_name_or_attribute( + checker: &Checker, + item: &Expr, + replacements: &mut Vec, + before_replace: &mut Vec, +) { + match &item.node { + ExprKind::Name { id, .. } => { + let (temp_replacements, temp_before_replace) = check_module(checker, item); + replacements.extend(temp_replacements); + before_replace.extend(temp_before_replace); + if replacements.is_empty() { + let new_name = get_correct_name(id); + replacements.push(new_name); + before_replace.push(id.to_string()); + } + } + ExprKind::Attribute { .. } => { + let (temp_replacements, temp_before_replace) = check_module(checker, item); + replacements.extend(temp_replacements); + before_replace.extend(temp_before_replace); + } + _ => (), + } +} + +/// Handles one block of an except (use a loop if there are multile blocks) +fn handle_except_block(checker: &mut Checker, handler: &Located) { + let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler.node; + let Some(error_handlers) = type_.as_ref() else { + return; + }; + // The first part creates list of all the exceptions being caught, and + // what they should be changed to + let mut replacements: Vec = vec![]; + let mut before_replace: Vec = vec![]; + match &error_handlers.node { + ExprKind::Name { .. } | ExprKind::Attribute { .. } => { + handle_name_or_attribute( + checker, + error_handlers, + &mut replacements, + &mut before_replace, + ); + } + ExprKind::Tuple { elts, .. } => { + before_replace = get_before_replace(elts); + for elt in elts { + match &elt.node { + ExprKind::Name { id, .. } => { + let new_name = get_correct_name(id); + replacements.push(new_name); + } + ExprKind::Attribute { .. } => { + let (new_replacements, new_before_replace) = check_module(checker, elt); + replacements.extend(new_replacements); + before_replace.extend(new_before_replace); + } + _ => (), + } + } + } + _ => return, + } + replacements = replacements + .iter() + .unique() + .map(std::string::ToString::to_string) + .collect(); + before_replace = before_replace + .iter() + .filter(|x| !x.is_empty()) + .map(std::string::ToString::to_string) + .collect(); + + // This part checks if there are differences between what there is and + // what there should be. Where differences, the changes are applied + handle_making_changes(checker, error_handlers, &before_replace, &replacements); +} + +fn handle_making_changes( + checker: &mut Checker, + target: &Expr, + before_replace: &[String], + replacements: &[String], +) { + if before_replace != replacements && replacements.len() > 0 { + let range = Range::new(target.location, target.end_location.unwrap()); + let contents = checker.locator.slice_source_code_range(&range); + // Pyyupgrade does not want imports changed if a module only is + // surrounded by parentheses. For example: `except mmap.error:` + // would be changed, but: `(mmap).error:` would not. One issue with + // this implementation is that any valid changes will also be + // ignored. Let me know if you want me to go with a more + // complicated solution that avoids this. + if contents.contains(").") { + return; + } + let mut final_str: String; + if replacements.len() == 1 { + final_str = replacements.get(0).unwrap().to_string(); + } else { + final_str = replacements.join(", "); + final_str.insert(0, '('); + final_str.push(')'); + } + let mut check = Check::new(CheckKind::OSErrorAlias(compose_call_path(target)), range); + if checker.patch(check.kind.code()) { + check.amend(Fix::replacement( + final_str, + range.location, + range.end_location, + )); + } + checker.add_check(check); + } +} + +// This is a hacky way to handle the different variable types we get since +// raise and try are very different. Would love input on a cleaner way +pub trait OSErrorAliasChecker { + fn check_error(&self, checker: &mut Checker) + where + Self: Sized; +} + +impl OSErrorAliasChecker for &Vec { + fn check_error(&self, checker: &mut Checker) { + // Each separate except block is a separate error and fix + for handler in self.iter() { + handle_except_block(checker, handler); + } + } +} + +impl OSErrorAliasChecker for &Box { + fn check_error(&self, checker: &mut Checker) { + let mut replacements: Vec = vec![]; + let mut before_replace: Vec = vec![]; + match &self.node { + ExprKind::Name { .. } | ExprKind::Attribute { .. } => { + handle_name_or_attribute(checker, self, &mut replacements, &mut before_replace); + } + _ => return, + } + handle_making_changes(checker, self, &before_replace, &replacements); + } +} + +impl OSErrorAliasChecker for &Expr { + fn check_error(&self, checker: &mut Checker) { + let mut replacements: Vec = vec![]; + let mut before_replace: Vec = vec![]; + let change_target: &Expr; + match &self.node { + ExprKind::Name { .. } | ExprKind::Attribute { .. } => { + change_target = self; + handle_name_or_attribute(checker, self, &mut replacements, &mut before_replace); + } + ExprKind::Call { func, .. } => { + change_target = func; + match &func.node { + ExprKind::Name { .. } | ExprKind::Attribute { .. } => { + handle_name_or_attribute( + checker, + func, + &mut replacements, + &mut before_replace, + ); + } + _ => return, + } + } + _ => return, + } + handle_making_changes(checker, change_target, &before_replace, &replacements); + } +} + +/// UP024 +pub fn os_error_alias(checker: &mut Checker, handlers: U) { + handlers.check_error(checker); +} diff --git a/src/pyupgrade/plugins/redundant_open_modes.rs b/src/pyupgrade/plugins/redundant_open_modes.rs index f570ef638a..57408122f6 100644 --- a/src/pyupgrade/plugins/redundant_open_modes.rs +++ b/src/pyupgrade/plugins/redundant_open_modes.rs @@ -107,10 +107,8 @@ fn create_remove_param_fix( expr: &Expr, mode_param: &Expr, ) -> Result { - let content = locator.slice_source_code_range(&Range { - location: expr.location, - end_location: expr.end_location.unwrap(), - }); + let content = + locator.slice_source_code_range(&Range::new(expr.location, expr.end_location.unwrap())); // Find the last comma before mode_param and create a deletion fix // starting from the comma and ending after mode_param. let mut fix_start: Option = None; diff --git a/src/pyupgrade/plugins/remove_six_compat.rs b/src/pyupgrade/plugins/remove_six_compat.rs index 65254a6a3f..1ea4b45111 100644 --- a/src/pyupgrade/plugins/remove_six_compat.rs +++ b/src/pyupgrade/plugins/remove_six_compat.rs @@ -65,10 +65,10 @@ fn replace_by_str_literal( let content = format!( "{}{}", if binary { "b" } else { "" }, - locator.slice_source_code_range(&Range { - location: arg.location, - end_location: arg.end_location.unwrap(), - }) + locator.slice_source_code_range(&Range::new( + arg.location, + arg.end_location.unwrap(), + )) ); check.amend(Fix::replacement( content, diff --git a/src/pyupgrade/plugins/replace_stdout_stderr.rs b/src/pyupgrade/plugins/replace_stdout_stderr.rs index 7dd51b3470..182a335f79 100644 --- a/src/pyupgrade/plugins/replace_stdout_stderr.rs +++ b/src/pyupgrade/plugins/replace_stdout_stderr.rs @@ -87,10 +87,12 @@ pub fn replace_stdout_stderr(checker: &mut Checker, expr: &Expr, kwargs: &[Keywo stderr }; let mut contents = String::from("capture_output=True"); - if let Some(middle) = extract_middle(&checker.locator.slice_source_code_range(&Range { - location: first.end_location.unwrap(), - end_location: last.location, - })) { + if let Some(middle) = + extract_middle(&checker.locator.slice_source_code_range(&Range::new( + first.end_location.unwrap(), + last.location, + ))) + { if middle.multi_line { contents.push(','); contents.push('\n'); diff --git a/src/pyupgrade/plugins/replace_universal_newlines.rs b/src/pyupgrade/plugins/replace_universal_newlines.rs index 67f2b5a9a0..0ac67e77c2 100644 --- a/src/pyupgrade/plugins/replace_universal_newlines.rs +++ b/src/pyupgrade/plugins/replace_universal_newlines.rs @@ -16,13 +16,13 @@ pub fn replace_universal_newlines(checker: &mut Checker, expr: &Expr, kwargs: &[ &checker.import_aliases, ) { let Some(kwarg) = find_keyword(kwargs, "universal_newlines") else { return; }; - let range = Range { - location: kwarg.location, - end_location: Location::new( + let range = Range::new( + kwarg.location, + Location::new( kwarg.location.row(), kwarg.location.column() + "universal_newlines".len(), ), - }; + ); let mut check = Check::new(CheckKind::ReplaceUniversalNewlines, range); if checker.patch(check.kind.code()) { check.amend(Fix::replacement( diff --git a/src/pyupgrade/plugins/unnecessary_encode_utf8.rs b/src/pyupgrade/plugins/unnecessary_encode_utf8.rs index ed0a62c211..06a8317d48 100644 --- a/src/pyupgrade/plugins/unnecessary_encode_utf8.rs +++ b/src/pyupgrade/plugins/unnecessary_encode_utf8.rs @@ -84,10 +84,10 @@ fn replace_with_bytes_literal( ) -> Check { let mut check = Check::new(CheckKind::UnnecessaryEncodeUTF8, Range::from_located(expr)); if patch { - let content = locator.slice_source_code_range(&Range { - location: constant.location, - end_location: constant.end_location.unwrap(), - }); + let content = locator.slice_source_code_range(&Range::new( + constant.location, + constant.end_location.unwrap(), + )); let content = format!( "b{}", content.trim_start_matches('u').trim_start_matches('U') diff --git a/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_0.py.snap b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_0.py.snap new file mode 100644 index 0000000000..6da0bb67df --- /dev/null +++ b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_0.py.snap @@ -0,0 +1,209 @@ +--- +source: src/pyupgrade/mod.rs +expression: checks +--- +- kind: + OSErrorAlias: EnvironmentError + location: + row: 6 + column: 7 + end_location: + row: 6 + column: 23 + fix: + content: OSError + location: + row: 6 + column: 7 + end_location: + row: 6 + column: 23 + parent: ~ +- kind: + OSErrorAlias: IOError + location: + row: 11 + column: 7 + end_location: + row: 11 + column: 14 + fix: + content: OSError + location: + row: 11 + column: 7 + end_location: + row: 11 + column: 14 + parent: ~ +- kind: + OSErrorAlias: WindowsError + location: + row: 16 + column: 7 + end_location: + row: 16 + column: 19 + fix: + content: OSError + location: + row: 16 + column: 7 + end_location: + row: 16 + column: 19 + parent: ~ +- kind: + OSErrorAlias: mmap.error + location: + row: 21 + column: 7 + end_location: + row: 21 + column: 17 + fix: + content: OSError + location: + row: 21 + column: 7 + end_location: + row: 21 + column: 17 + parent: ~ +- kind: + OSErrorAlias: select.error + location: + row: 26 + column: 7 + end_location: + row: 26 + column: 19 + fix: + content: OSError + location: + row: 26 + column: 7 + end_location: + row: 26 + column: 19 + parent: ~ +- kind: + OSErrorAlias: socket.error + location: + row: 31 + column: 7 + end_location: + row: 31 + column: 19 + fix: + content: OSError + location: + row: 31 + column: 7 + end_location: + row: 31 + column: 19 + parent: ~ +- kind: + OSErrorAlias: error + location: + row: 36 + column: 7 + end_location: + row: 36 + column: 12 + fix: + content: OSError + location: + row: 36 + column: 7 + end_location: + row: 36 + column: 12 + parent: ~ +- kind: + OSErrorAlias: ~ + location: + row: 43 + column: 7 + end_location: + row: 43 + column: 17 + fix: + content: OSError + location: + row: 43 + column: 7 + end_location: + row: 43 + column: 17 + parent: ~ +- kind: + OSErrorAlias: ~ + location: + row: 47 + column: 7 + end_location: + row: 47 + column: 20 + fix: + content: OSError + location: + row: 47 + column: 7 + end_location: + row: 47 + column: 20 + parent: ~ +- kind: + OSErrorAlias: ~ + location: + row: 51 + column: 7 + end_location: + row: 51 + column: 57 + fix: + content: OSError + location: + row: 51 + column: 7 + end_location: + row: 51 + column: 57 + parent: ~ +- kind: + OSErrorAlias: ~ + location: + row: 58 + column: 7 + end_location: + row: 58 + column: 35 + fix: + content: "(OSError, KeyError)" + location: + row: 58 + column: 7 + end_location: + row: 58 + column: 35 + parent: ~ +- kind: + OSErrorAlias: ~ + location: + row: 65 + column: 7 + end_location: + row: 65 + column: 23 + fix: + content: "(OSError, error)" + location: + row: 65 + column: 7 + end_location: + row: 65 + column: 23 + parent: ~ + diff --git a/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_1.py.snap b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_1.py.snap new file mode 100644 index 0000000000..1a0e49148d --- /dev/null +++ b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_1.py.snap @@ -0,0 +1,56 @@ +--- +source: src/pyupgrade/mod.rs +expression: checks +--- +- kind: + OSErrorAlias: ~ + location: + row: 5 + column: 7 + end_location: + row: 5 + column: 37 + fix: + content: OSError + location: + row: 5 + column: 7 + end_location: + row: 5 + column: 37 + parent: ~ +- kind: + OSErrorAlias: ~ + location: + row: 7 + column: 7 + end_location: + row: 7 + column: 40 + fix: + content: "(OSError, KeyError)" + location: + row: 7 + column: 7 + end_location: + row: 7 + column: 40 + parent: ~ +- kind: + OSErrorAlias: ~ + location: + row: 12 + column: 7 + end_location: + row: 16 + column: 1 + fix: + content: OSError + location: + row: 12 + column: 7 + end_location: + row: 16 + column: 1 + parent: ~ + diff --git a/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_2.py.snap b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_2.py.snap new file mode 100644 index 0000000000..da619c0098 --- /dev/null +++ b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP024_UP024_2.py.snap @@ -0,0 +1,345 @@ +--- +source: src/pyupgrade/mod.rs +expression: checks +--- +- kind: + OSErrorAlias: socket.error + location: + row: 10 + column: 6 + end_location: + row: 10 + column: 18 + fix: + content: OSError + location: + row: 10 + column: 6 + end_location: + row: 10 + column: 18 + parent: ~ +- kind: + OSErrorAlias: mmap.error + location: + row: 11 + column: 6 + end_location: + row: 11 + column: 16 + fix: + content: OSError + location: + row: 11 + column: 6 + end_location: + row: 11 + column: 16 + parent: ~ +- kind: + OSErrorAlias: select.error + location: + row: 12 + column: 6 + end_location: + row: 12 + column: 18 + fix: + content: OSError + location: + row: 12 + column: 6 + end_location: + row: 12 + column: 18 + parent: ~ +- kind: + OSErrorAlias: socket.error + location: + row: 14 + column: 6 + end_location: + row: 14 + column: 18 + fix: + content: OSError + location: + row: 14 + column: 6 + end_location: + row: 14 + column: 18 + parent: ~ +- kind: + OSErrorAlias: mmap.error + location: + row: 15 + column: 6 + end_location: + row: 15 + column: 16 + fix: + content: OSError + location: + row: 15 + column: 6 + end_location: + row: 15 + column: 16 + parent: ~ +- kind: + OSErrorAlias: select.error + location: + row: 16 + column: 6 + end_location: + row: 16 + column: 18 + fix: + content: OSError + location: + row: 16 + column: 6 + end_location: + row: 16 + column: 18 + parent: ~ +- kind: + OSErrorAlias: socket.error + location: + row: 18 + column: 6 + end_location: + row: 18 + column: 18 + fix: + content: OSError + location: + row: 18 + column: 6 + end_location: + row: 18 + column: 18 + parent: ~ +- kind: + OSErrorAlias: error + location: + row: 25 + column: 6 + end_location: + row: 25 + column: 11 + fix: + content: OSError + location: + row: 25 + column: 6 + end_location: + row: 25 + column: 11 + parent: ~ +- kind: + OSErrorAlias: error + location: + row: 28 + column: 6 + end_location: + row: 28 + column: 11 + fix: + content: OSError + location: + row: 28 + column: 6 + end_location: + row: 28 + column: 11 + parent: ~ +- kind: + OSErrorAlias: error + location: + row: 31 + column: 6 + end_location: + row: 31 + column: 11 + fix: + content: OSError + location: + row: 31 + column: 6 + end_location: + row: 31 + column: 11 + parent: ~ +- kind: + OSErrorAlias: EnvironmentError + location: + row: 34 + column: 6 + end_location: + row: 34 + column: 22 + fix: + content: OSError + location: + row: 34 + column: 6 + end_location: + row: 34 + column: 22 + parent: ~ +- kind: + OSErrorAlias: IOError + location: + row: 35 + column: 6 + end_location: + row: 35 + column: 13 + fix: + content: OSError + location: + row: 35 + column: 6 + end_location: + row: 35 + column: 13 + parent: ~ +- kind: + OSErrorAlias: WindowsError + location: + row: 36 + column: 6 + end_location: + row: 36 + column: 18 + fix: + content: OSError + location: + row: 36 + column: 6 + end_location: + row: 36 + column: 18 + parent: ~ +- kind: + OSErrorAlias: EnvironmentError + location: + row: 38 + column: 6 + end_location: + row: 38 + column: 22 + fix: + content: OSError + location: + row: 38 + column: 6 + end_location: + row: 38 + column: 22 + parent: ~ +- kind: + OSErrorAlias: IOError + location: + row: 39 + column: 6 + end_location: + row: 39 + column: 13 + fix: + content: OSError + location: + row: 39 + column: 6 + end_location: + row: 39 + column: 13 + parent: ~ +- kind: + OSErrorAlias: WindowsError + location: + row: 40 + column: 6 + end_location: + row: 40 + column: 18 + fix: + content: OSError + location: + row: 40 + column: 6 + end_location: + row: 40 + column: 18 + parent: ~ +- kind: + OSErrorAlias: EnvironmentError + location: + row: 42 + column: 6 + end_location: + row: 42 + column: 22 + fix: + content: OSError + location: + row: 42 + column: 6 + end_location: + row: 42 + column: 22 + parent: ~ +- kind: + OSErrorAlias: WindowsError + location: + row: 48 + column: 6 + end_location: + row: 48 + column: 18 + fix: + content: OSError + location: + row: 48 + column: 6 + end_location: + row: 48 + column: 18 + parent: ~ +- kind: + OSErrorAlias: EnvironmentError + location: + row: 49 + column: 6 + end_location: + row: 49 + column: 22 + fix: + content: OSError + location: + row: 49 + column: 6 + end_location: + row: 49 + column: 22 + parent: ~ +- kind: + OSErrorAlias: IOError + location: + row: 50 + column: 6 + end_location: + row: 50 + column: 13 + fix: + content: OSError + location: + row: 50 + column: 6 + end_location: + row: 50 + column: 13 + parent: ~ + diff --git a/src/ruff/checks.rs b/src/ruff/checks.rs index e1326bf47f..a1ce73f16f 100644 --- a/src/ruff/checks.rs +++ b/src/ruff/checks.rs @@ -1614,10 +1614,7 @@ pub fn ambiguous_unicode_character( ) -> Vec { let mut checks = vec![]; - let text = locator.slice_source_code_range(&Range { - location: start, - end_location: end, - }); + let text = locator.slice_source_code_range(&Range::new(start, end)); let mut col_offset = 0; let mut row_offset = 0; @@ -1648,10 +1645,7 @@ pub fn ambiguous_unicode_character( representant, ), }, - Range { - location, - end_location, - }, + Range::new(location, end_location), ); if settings.enabled.contains(check.kind.code()) { if matches!(autofix, flags::Autofix::Enabled) diff --git a/src/source_code_style.rs b/src/source_code_style.rs index da29704992..fe85a9ad98 100644 --- a/src/source_code_style.rs +++ b/src/source_code_style.rs @@ -125,10 +125,7 @@ fn detect_indentation(contents: &str, locator: &SourceCodeLocator) -> Option Option Option { for (start, tok, end) in lexer::make_tokenizer(contents).flatten() { if let Tok::String { .. } = tok { - let content = locator.slice_source_code_range(&Range { - location: start, - end_location: end, - }); + let content = locator.slice_source_code_range(&Range::new(start, end)); if let Some(pattern) = leading_quote(&content) { if pattern.contains('\'') { return Some(Quote::Single);