diff --git a/README.md b/README.md index 9537c101a1..458416d814 100644 --- a/README.md +++ b/README.md @@ -644,7 +644,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 | 🛠 | -| UP005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` instead | 🛠 | +| 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 | 🛠 | | UP008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | 🛠 | @@ -659,7 +659,8 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI. | UP017 | DatetimeTimezoneUTC | Use `datetime.UTC` alias | 🛠 | | UP018 | NativeLiterals | Unnecessary call to `str` and `bytes` | 🛠 | | UP019 | TypingTextStrAlias | `typing.Text` is deprecated, use `str` | 🛠 | -| UP020 | OpenAlias | Use builtin `open` instead | 🛠 | +| UP020 | OpenAlias | Use builtin `open` | 🛠 | +| UP021 | ReplaceUniversalNewlines | `universal_newlines` is deprecated, use `text` | 🛠 | ### pep8-naming (N) diff --git a/resources/test/fixtures/pyupgrade/UP021.py b/resources/test/fixtures/pyupgrade/UP021.py new file mode 100644 index 0000000000..c6e0b5e591 --- /dev/null +++ b/resources/test/fixtures/pyupgrade/UP021.py @@ -0,0 +1,12 @@ +import subprocess +import subprocess as somename +from subprocess import run +from subprocess import run as anothername + +subprocess.run(["foo"], universal_newlines=True, check=True) +somename.run(["foo"], universal_newlines=True) + +run(["foo"], universal_newlines=True, check=False) +anothername(["foo"], universal_newlines=True) + +subprocess.run(["foo"], check=True) diff --git a/ruff.schema.json b/ruff.schema.json index 3cb676b64e..aa4c910f9c 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -878,6 +878,7 @@ "UP019", "UP02", "UP020", + "UP021", "W", "W2", "W29", diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index 1a4e1ebecc..a3d39b43ac 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -1654,6 +1654,9 @@ where if self.settings.enabled.contains(&CheckCode::UP018) { pyupgrade::plugins::native_literals(self, expr, func, args, keywords); } + if self.settings.enabled.contains(&CheckCode::UP021) { + pyupgrade::plugins::replace_universal_newlines(self, expr, keywords); + } // flake8-super if self.settings.enabled.contains(&CheckCode::UP008) { diff --git a/src/checks.rs b/src/checks.rs index 2dd42d7f18..1f8e0c1a98 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -228,6 +228,7 @@ pub enum CheckCode { UP018, UP019, UP020, + UP021, // pydocstyle D100, D101, @@ -841,6 +842,7 @@ pub enum CheckKind { DatetimeTimezoneUTC, NativeLiterals, OpenAlias, + ReplaceUniversalNewlines, // pydocstyle BlankLineAfterLastSection(String), BlankLineAfterSection(String), @@ -1218,6 +1220,7 @@ impl CheckCode { CheckCode::UP018 => CheckKind::NativeLiterals, CheckCode::UP019 => CheckKind::TypingTextStrAlias, CheckCode::UP020 => CheckKind::OpenAlias, + CheckCode::UP021 => CheckKind::ReplaceUniversalNewlines, // pydocstyle CheckCode::D100 => CheckKind::PublicModule, CheckCode::D101 => CheckKind::PublicClass, @@ -1639,6 +1642,7 @@ impl CheckCode { CheckCode::UP018 => CheckCategory::Pyupgrade, CheckCode::UP019 => CheckCategory::Pyupgrade, CheckCode::UP020 => CheckCategory::Pyupgrade, + CheckCode::UP021 => CheckCategory::Pyupgrade, CheckCode::W292 => CheckCategory::Pycodestyle, CheckCode::W605 => CheckCategory::Pycodestyle, CheckCode::YTT101 => CheckCategory::Flake82020, @@ -1853,6 +1857,7 @@ impl CheckKind { CheckKind::NativeLiterals => &CheckCode::UP018, CheckKind::TypingTextStrAlias => &CheckCode::UP019, CheckKind::OpenAlias => &CheckCode::UP020, + CheckKind::ReplaceUniversalNewlines => &CheckCode::UP021, // pydocstyle CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413, CheckKind::BlankLineAfterSection(..) => &CheckCode::D410, @@ -2545,7 +2550,7 @@ impl CheckKind { CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(), CheckKind::TypingTextStrAlias => "`typing.Text` is deprecated, use `str`".to_string(), CheckKind::DeprecatedUnittestAlias(alias, target) => { - format!("`{alias}` is deprecated, use `{target}` instead") + format!("`{alias}` is deprecated, use `{target}`") } CheckKind::UselessObjectInheritance(name) => { format!("Class `{name}` inherits from object") @@ -2578,10 +2583,13 @@ impl CheckKind { 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::OpenAlias => "Use builtin `open` instead".to_string(), + CheckKind::OpenAlias => "Use builtin `open`".to_string(), CheckKind::ConvertTypedDictFunctionalToClass(name) => { format!("Convert `{name}` from `TypedDict` functional to class syntax") } + CheckKind::ReplaceUniversalNewlines => { + "`universal_newlines` is deprecated, use `text`".to_string() + } CheckKind::ConvertNamedTupleFunctionalToClass(name) => { format!("Convert `{name}` from `NamedTuple` functional to class syntax") } @@ -3023,6 +3031,7 @@ impl CheckKind { | CheckKind::NativeLiterals | CheckKind::OpenAlias | CheckKind::NewLineAfterLastParagraph + | CheckKind::ReplaceUniversalNewlines | CheckKind::NewLineAfterSectionName(..) | CheckKind::NoBlankLineAfterFunction(..) | CheckKind::NoBlankLineBeforeClass(..) diff --git a/src/checks_gen.rs b/src/checks_gen.rs index 5cb182b0ad..fb218f9976 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -529,6 +529,7 @@ pub enum CheckCodePrefix { UP019, UP02, UP020, + UP021, W, W2, W29, @@ -2107,6 +2108,7 @@ impl CheckCodePrefix { CheckCode::UP018, CheckCode::UP019, CheckCode::UP020, + CheckCode::UP021, ] } CheckCodePrefix::U0 => { @@ -2136,6 +2138,7 @@ impl CheckCodePrefix { CheckCode::UP018, CheckCode::UP019, CheckCode::UP020, + CheckCode::UP021, ] } CheckCodePrefix::U00 => { @@ -2349,6 +2352,7 @@ impl CheckCodePrefix { CheckCode::UP018, CheckCode::UP019, CheckCode::UP020, + CheckCode::UP021, ], CheckCodePrefix::UP0 => vec![ CheckCode::UP001, @@ -2370,6 +2374,7 @@ impl CheckCodePrefix { CheckCode::UP018, CheckCode::UP019, CheckCode::UP020, + CheckCode::UP021, ], CheckCodePrefix::UP00 => vec![ CheckCode::UP001, @@ -2411,8 +2416,9 @@ impl CheckCodePrefix { CheckCodePrefix::UP017 => vec![CheckCode::UP017], CheckCodePrefix::UP018 => vec![CheckCode::UP018], CheckCodePrefix::UP019 => vec![CheckCode::UP019], - CheckCodePrefix::UP02 => vec![CheckCode::UP020], + CheckCodePrefix::UP02 => vec![CheckCode::UP020, CheckCode::UP021], CheckCodePrefix::UP020 => vec![CheckCode::UP020], + CheckCodePrefix::UP021 => vec![CheckCode::UP021], CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605], CheckCodePrefix::W2 => vec![CheckCode::W292], CheckCodePrefix::W29 => vec![CheckCode::W292], @@ -2973,6 +2979,7 @@ impl CheckCodePrefix { CheckCodePrefix::UP019 => SuffixLength::Three, CheckCodePrefix::UP02 => SuffixLength::Two, CheckCodePrefix::UP020 => SuffixLength::Three, + CheckCodePrefix::UP021 => SuffixLength::Three, CheckCodePrefix::W => SuffixLength::Zero, CheckCodePrefix::W2 => SuffixLength::One, CheckCodePrefix::W29 => SuffixLength::Two, diff --git a/src/pyupgrade/mod.rs b/src/pyupgrade/mod.rs index 1c79efbeac..953dbcdc67 100644 --- a/src/pyupgrade/mod.rs +++ b/src/pyupgrade/mod.rs @@ -39,6 +39,7 @@ mod tests { #[test_case(CheckCode::UP016, Path::new("UP016.py"); "UP016")] #[test_case(CheckCode::UP018, Path::new("UP018.py"); "UP018")] #[test_case(CheckCode::UP019, Path::new("UP019.py"); "UP019")] + #[test_case(CheckCode::UP021, Path::new("UP021.py"); "UP021")] fn checks(check_code: CheckCode, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy()); let mut checks = test_path( diff --git a/src/pyupgrade/plugins/mod.rs b/src/pyupgrade/plugins/mod.rs index 065d8f67f9..aba272ca2e 100644 --- a/src/pyupgrade/plugins/mod.rs +++ b/src/pyupgrade/plugins/mod.rs @@ -6,6 +6,7 @@ pub use native_literals::native_literals; pub use open_alias::open_alias; pub use redundant_open_modes::redundant_open_modes; pub use remove_six_compat::remove_six_compat; +pub use replace_universal_newlines::replace_universal_newlines; pub use super_call_with_parameters::super_call_with_parameters; pub use type_of_primitive::type_of_primitive; pub use typing_text_str_alias::typing_text_str_alias; @@ -25,6 +26,7 @@ mod native_literals; mod open_alias; mod redundant_open_modes; mod remove_six_compat; +mod replace_universal_newlines; mod super_call_with_parameters; mod type_of_primitive; mod typing_text_str_alias; diff --git a/src/pyupgrade/plugins/replace_universal_newlines.rs b/src/pyupgrade/plugins/replace_universal_newlines.rs new file mode 100644 index 0000000000..67f2b5a9a0 --- /dev/null +++ b/src/pyupgrade/plugins/replace_universal_newlines.rs @@ -0,0 +1,36 @@ +use rustpython_ast::{Expr, Keyword, Location}; + +use crate::ast::helpers::{find_keyword, match_module_member}; +use crate::ast::types::Range; +use crate::autofix::Fix; +use crate::checkers::ast::Checker; +use crate::checks::{Check, CheckKind}; + +/// UP021 +pub fn replace_universal_newlines(checker: &mut Checker, expr: &Expr, kwargs: &[Keyword]) { + if match_module_member( + expr, + "subprocess", + "run", + &checker.from_imports, + &checker.import_aliases, + ) { + let Some(kwarg) = find_keyword(kwargs, "universal_newlines") else { return; }; + let range = Range { + location: kwarg.location, + end_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( + "text".to_string(), + range.location, + range.end_location, + )); + } + checker.add_check(check); + } +} diff --git a/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP021_UP021.py.snap b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP021_UP021.py.snap new file mode 100644 index 0000000000..076072665a --- /dev/null +++ b/src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP021_UP021.py.snap @@ -0,0 +1,65 @@ +--- +source: src/pyupgrade/mod.rs +expression: checks +--- +- kind: ReplaceUniversalNewlines + location: + row: 6 + column: 24 + end_location: + row: 6 + column: 42 + fix: + content: text + location: + row: 6 + column: 24 + end_location: + row: 6 + column: 42 +- kind: ReplaceUniversalNewlines + location: + row: 7 + column: 22 + end_location: + row: 7 + column: 40 + fix: + content: text + location: + row: 7 + column: 22 + end_location: + row: 7 + column: 40 +- kind: ReplaceUniversalNewlines + location: + row: 9 + column: 13 + end_location: + row: 9 + column: 31 + fix: + content: text + location: + row: 9 + column: 13 + end_location: + row: 9 + column: 31 +- kind: ReplaceUniversalNewlines + location: + row: 10 + column: 21 + end_location: + row: 10 + column: 39 + fix: + content: text + location: + row: 10 + column: 21 + end_location: + row: 10 + column: 39 +