diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aadef7bcfb..899e88d73d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,7 +80,7 @@ Then, run `cargo test`. Your test will fail, but you'll be prompted to follow-up rest of your changes. Finally, to update the documentation, run `cargo dev generate-rules-table` from the repo root. To -update the generated prefix map, run `cargo dev generate-check-code-prefix`. Both of these commands +update the generated prefix map, run `cargo +nightly dev generate-check-code-prefix`. Both of these commands should be run whenever a new check is added to the codebase. ### Example: Adding a new configuration option diff --git a/README.md b/README.md index 6ae502535e..f66e818eb9 100644 --- a/README.md +++ b/README.md @@ -659,6 +659,7 @@ 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 | 🛠 | ### pep8-naming (N) @@ -1262,7 +1263,7 @@ natively, including: - [`pep8-naming`](https://pypi.org/project/pep8-naming/) - [`pydocstyle`](https://pypi.org/project/pydocstyle/) - [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10) -- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (19/33) +- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (20/33) - [`yesqa`](https://github.com/asottile/yesqa) Note that, in some cases, Ruff uses different error code prefixes than would be found in the @@ -1319,7 +1320,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/), [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10), and a subset of the rules -implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (19/33). +implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (20/33). If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue. diff --git a/resources/test/fixtures/pyupgrade/UP020.py b/resources/test/fixtures/pyupgrade/UP020.py new file mode 100644 index 0000000000..5c1f4b67ee --- /dev/null +++ b/resources/test/fixtures/pyupgrade/UP020.py @@ -0,0 +1,9 @@ +from io import open + +with open("f.txt") as f: + print(f.read()) + +import io + +with io.open("f.txt", mode="r", buffering=-1, **kwargs) as f: + print(f.read()) diff --git a/ruff.schema.json b/ruff.schema.json index 9fe1a910ae..e81c1ee098 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -865,6 +865,8 @@ "UP017", "UP018", "UP019", + "UP02", + "UP020", "W", "W2", "W29", diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index a29ae40b6f..f9526680b5 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -1926,6 +1926,10 @@ where pyupgrade::plugins::redundant_open_modes(self, expr); } + if self.settings.enabled.contains(&CheckCode::UP020) { + pyupgrade::plugins::open_alias(self, expr, func); + } + // flake8-boolean-trap if self.settings.enabled.contains(&CheckCode::FBT003) { flake8_boolean_trap::plugins::check_boolean_positional_value_in_function_call( diff --git a/src/checks.rs b/src/checks.rs index 628ecc8d23..2dd42d7f18 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -227,6 +227,7 @@ pub enum CheckCode { UP017, UP018, UP019, + UP020, // pydocstyle D100, D101, @@ -839,6 +840,7 @@ pub enum CheckKind { RemoveSixCompat, DatetimeTimezoneUTC, NativeLiterals, + OpenAlias, // pydocstyle BlankLineAfterLastSection(String), BlankLineAfterSection(String), @@ -1215,6 +1217,7 @@ impl CheckCode { CheckCode::UP017 => CheckKind::DatetimeTimezoneUTC, CheckCode::UP018 => CheckKind::NativeLiterals, CheckCode::UP019 => CheckKind::TypingTextStrAlias, + CheckCode::UP020 => CheckKind::OpenAlias, // pydocstyle CheckCode::D100 => CheckKind::PublicModule, CheckCode::D101 => CheckKind::PublicClass, @@ -1635,6 +1638,7 @@ impl CheckCode { CheckCode::UP017 => CheckCategory::Pyupgrade, CheckCode::UP018 => CheckCategory::Pyupgrade, CheckCode::UP019 => CheckCategory::Pyupgrade, + CheckCode::UP020 => CheckCategory::Pyupgrade, CheckCode::W292 => CheckCategory::Pycodestyle, CheckCode::W605 => CheckCategory::Pycodestyle, CheckCode::YTT101 => CheckCategory::Flake82020, @@ -1848,6 +1852,7 @@ impl CheckKind { CheckKind::DatetimeTimezoneUTC => &CheckCode::UP017, CheckKind::NativeLiterals => &CheckCode::UP018, CheckKind::TypingTextStrAlias => &CheckCode::UP019, + CheckKind::OpenAlias => &CheckCode::UP020, // pydocstyle CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413, CheckKind::BlankLineAfterSection(..) => &CheckCode::D410, @@ -2573,6 +2578,7 @@ 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::ConvertTypedDictFunctionalToClass(name) => { format!("Convert `{name}` from `TypedDict` functional to class syntax") } @@ -3015,6 +3021,7 @@ impl CheckKind { | CheckKind::MisplacedComparisonConstant(..) | CheckKind::MissingReturnTypeSpecialMethod(..) | CheckKind::NativeLiterals + | CheckKind::OpenAlias | CheckKind::NewLineAfterLastParagraph | CheckKind::NewLineAfterSectionName(..) | CheckKind::NoBlankLineAfterFunction(..) diff --git a/src/checks_gen.rs b/src/checks_gen.rs index 476daa18a1..5cb182b0ad 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -527,6 +527,8 @@ pub enum CheckCodePrefix { UP017, UP018, UP019, + UP02, + UP020, W, W2, W29, @@ -2104,6 +2106,7 @@ impl CheckCodePrefix { CheckCode::UP017, CheckCode::UP018, CheckCode::UP019, + CheckCode::UP020, ] } CheckCodePrefix::U0 => { @@ -2132,6 +2135,7 @@ impl CheckCodePrefix { CheckCode::UP017, CheckCode::UP018, CheckCode::UP019, + CheckCode::UP020, ] } CheckCodePrefix::U00 => { @@ -2344,6 +2348,7 @@ impl CheckCodePrefix { CheckCode::UP017, CheckCode::UP018, CheckCode::UP019, + CheckCode::UP020, ], CheckCodePrefix::UP0 => vec![ CheckCode::UP001, @@ -2364,6 +2369,7 @@ impl CheckCodePrefix { CheckCode::UP017, CheckCode::UP018, CheckCode::UP019, + CheckCode::UP020, ], CheckCodePrefix::UP00 => vec![ CheckCode::UP001, @@ -2405,6 +2411,8 @@ impl CheckCodePrefix { CheckCodePrefix::UP017 => vec![CheckCode::UP017], CheckCodePrefix::UP018 => vec![CheckCode::UP018], CheckCodePrefix::UP019 => vec![CheckCode::UP019], + CheckCodePrefix::UP02 => vec![CheckCode::UP020], + CheckCodePrefix::UP020 => vec![CheckCode::UP020], CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605], CheckCodePrefix::W2 => vec![CheckCode::W292], CheckCodePrefix::W29 => vec![CheckCode::W292], @@ -2963,6 +2971,8 @@ impl CheckCodePrefix { CheckCodePrefix::UP017 => SuffixLength::Three, CheckCodePrefix::UP018 => SuffixLength::Three, CheckCodePrefix::UP019 => SuffixLength::Three, + CheckCodePrefix::UP02 => SuffixLength::Two, + CheckCodePrefix::UP020 => SuffixLength::Three, CheckCodePrefix::W => SuffixLength::Zero, CheckCodePrefix::W2 => SuffixLength::One, CheckCodePrefix::W29 => SuffixLength::Two, diff --git a/src/pyupgrade/plugins/mod.rs b/src/pyupgrade/plugins/mod.rs index 132933b552..065d8f67f9 100644 --- a/src/pyupgrade/plugins/mod.rs +++ b/src/pyupgrade/plugins/mod.rs @@ -3,6 +3,7 @@ pub use convert_typed_dict_functional_to_class::convert_typed_dict_functional_to 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 redundant_open_modes::redundant_open_modes; pub use remove_six_compat::remove_six_compat; pub use super_call_with_parameters::super_call_with_parameters; @@ -21,6 +22,7 @@ mod convert_typed_dict_functional_to_class; mod datetime_utc_alias; mod deprecated_unittest_alias; mod native_literals; +mod open_alias; mod redundant_open_modes; mod remove_six_compat; mod super_call_with_parameters; diff --git a/src/pyupgrade/plugins/open_alias.rs b/src/pyupgrade/plugins/open_alias.rs new file mode 100644 index 0000000000..7403109ceb --- /dev/null +++ b/src/pyupgrade/plugins/open_alias.rs @@ -0,0 +1,24 @@ +use rustpython_ast::Expr; + +use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path}; +use crate::ast::types::Range; +use crate::autofix::Fix; +use crate::checkers::ast::Checker; +use crate::checks::{Check, CheckCode, CheckKind}; + +/// UP020 +pub fn open_alias(checker: &mut Checker, expr: &Expr, func: &Expr) { + let call_path = dealias_call_path(collect_call_paths(expr), &checker.import_aliases); + + if match_call_path(&call_path, "io", "open", &checker.from_imports) { + let mut check = Check::new(CheckKind::OpenAlias, Range::from_located(expr)); + if checker.patch(&CheckCode::UP020) { + check.amend(Fix::replacement( + "open".to_string(), + func.location, + func.end_location.unwrap(), + )); + } + checker.add_check(check); + } +}