Implement "native literals" check from pyupgrade (#1350)

This commit is contained in:
Reiner Gerecke 2022-12-23 17:40:32 +01:00 committed by GitHub
parent e290050821
commit 9da3e2cca1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 218 additions and 2 deletions

View file

@ -629,6 +629,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 | | UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 |
| UP016 | RemoveSixCompat | Unnecessary `six` compatibility usage | 🛠 | | UP016 | RemoveSixCompat | Unnecessary `six` compatibility usage | 🛠 |
| UP017 | DatetimeTimezoneUTC | Use `datetime.UTC` alias | 🛠 | | UP017 | DatetimeTimezoneUTC | Use `datetime.UTC` alias | 🛠 |
| UP018 | NativeLiterals | Unnecessary call to `str` and `bytes` | 🛠 |
### pep8-naming (N) ### pep8-naming (N)
@ -1232,7 +1233,7 @@ natively, including:
- [`pep8-naming`](https://pypi.org/project/pep8-naming/) - [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`pydocstyle`](https://pypi.org/project/pydocstyle/) - [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10) - [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (17/33) - [`pyupgrade`](https://pypi.org/project/pyupgrade/) (18/33)
- [`yesqa`](https://github.com/asottile/yesqa) - [`yesqa`](https://github.com/asottile/yesqa)
Note that, in some cases, Ruff uses different error code prefixes than would be found in the Note that, in some cases, Ruff uses different error code prefixes than would be found in the

View file

@ -0,0 +1,25 @@
# These remain unchanged
str(1)
str(*a)
str("foo", *a)
str(**k)
str("foo", **k)
str("foo", encoding="UTF-8")
str("foo"
"bar")
bytes("foo", encoding="UTF-8")
bytes(*a)
bytes("foo", *a)
bytes("foo", **a)
bytes(b"foo"
b"bar")
# These become string or byte literals
str()
str("foo")
str("""
foo""")
bytes()
bytes(b"foo")
bytes(b"""
foo""")

View file

@ -1644,6 +1644,9 @@ where
if self.settings.enabled.contains(&CheckCode::UP016) { if self.settings.enabled.contains(&CheckCode::UP016) {
pyupgrade::plugins::remove_six_compat(self, expr); pyupgrade::plugins::remove_six_compat(self, expr);
} }
if self.settings.enabled.contains(&CheckCode::UP018) {
pyupgrade::plugins::native_literals(self, expr, func, args, keywords);
}
// flake8-super // flake8-super
if self.settings.enabled.contains(&CheckCode::UP008) { if self.settings.enabled.contains(&CheckCode::UP008) {

View file

@ -226,6 +226,7 @@ pub enum CheckCode {
UP015, UP015,
UP016, UP016,
UP017, UP017,
UP018,
// pydocstyle // pydocstyle
D100, D100,
D101, D101,
@ -829,6 +830,7 @@ pub enum CheckKind {
RedundantOpenModes, RedundantOpenModes,
RemoveSixCompat, RemoveSixCompat,
DatetimeTimezoneUTC, DatetimeTimezoneUTC,
NativeLiterals,
// pydocstyle // pydocstyle
BlankLineAfterLastSection(String), BlankLineAfterLastSection(String),
BlankLineAfterSection(String), BlankLineAfterSection(String),
@ -1203,6 +1205,7 @@ impl CheckCode {
CheckCode::UP015 => CheckKind::RedundantOpenModes, CheckCode::UP015 => CheckKind::RedundantOpenModes,
CheckCode::UP016 => CheckKind::RemoveSixCompat, CheckCode::UP016 => CheckKind::RemoveSixCompat,
CheckCode::UP017 => CheckKind::DatetimeTimezoneUTC, CheckCode::UP017 => CheckKind::DatetimeTimezoneUTC,
CheckCode::UP018 => CheckKind::NativeLiterals,
// pydocstyle // pydocstyle
CheckCode::D100 => CheckKind::PublicModule, CheckCode::D100 => CheckKind::PublicModule,
CheckCode::D101 => CheckKind::PublicClass, CheckCode::D101 => CheckKind::PublicClass,
@ -1621,6 +1624,7 @@ impl CheckCode {
CheckCode::UP015 => CheckCategory::Pyupgrade, CheckCode::UP015 => CheckCategory::Pyupgrade,
CheckCode::UP016 => CheckCategory::Pyupgrade, CheckCode::UP016 => CheckCategory::Pyupgrade,
CheckCode::UP017 => CheckCategory::Pyupgrade, CheckCode::UP017 => CheckCategory::Pyupgrade,
CheckCode::UP018 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle, CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle, CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020, CheckCode::YTT101 => CheckCategory::Flake82020,
@ -1832,6 +1836,7 @@ impl CheckKind {
CheckKind::RedundantOpenModes => &CheckCode::UP015, CheckKind::RedundantOpenModes => &CheckCode::UP015,
CheckKind::RemoveSixCompat => &CheckCode::UP016, CheckKind::RemoveSixCompat => &CheckCode::UP016,
CheckKind::DatetimeTimezoneUTC => &CheckCode::UP017, CheckKind::DatetimeTimezoneUTC => &CheckCode::UP017,
CheckKind::NativeLiterals => &CheckCode::UP018,
// pydocstyle // pydocstyle
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413, CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410, CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
@ -2555,6 +2560,7 @@ impl CheckKind {
CheckKind::RedundantOpenModes => "Unnecessary open mode parameters".to_string(), CheckKind::RedundantOpenModes => "Unnecessary open mode parameters".to_string(),
CheckKind::RemoveSixCompat => "Unnecessary `six` compatibility usage".to_string(), CheckKind::RemoveSixCompat => "Unnecessary `six` compatibility usage".to_string(),
CheckKind::DatetimeTimezoneUTC => "Use `datetime.UTC` alias".to_string(), CheckKind::DatetimeTimezoneUTC => "Use `datetime.UTC` alias".to_string(),
CheckKind::NativeLiterals => "Unnecessary call to `str` and `bytes`".to_string(),
CheckKind::ConvertTypedDictFunctionalToClass(name) => { CheckKind::ConvertTypedDictFunctionalToClass(name) => {
format!("Convert `{name}` from `TypedDict` functional to class syntax") format!("Convert `{name}` from `TypedDict` functional to class syntax")
} }
@ -2956,6 +2962,7 @@ impl CheckKind {
| CheckKind::ConvertNamedTupleFunctionalToClass(..) | CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..) | CheckKind::ConvertTypedDictFunctionalToClass(..)
| CheckKind::DashedUnderlineAfterSection(..) | CheckKind::DashedUnderlineAfterSection(..)
| CheckKind::DatetimeTimezoneUTC
| CheckKind::DeprecatedUnittestAlias(..) | CheckKind::DeprecatedUnittestAlias(..)
| CheckKind::DoNotAssertFalse | CheckKind::DoNotAssertFalse
| CheckKind::DoNotAssignLambda | CheckKind::DoNotAssignLambda
@ -2969,6 +2976,7 @@ impl CheckKind {
| CheckKind::KeyInDict(..) | CheckKind::KeyInDict(..)
| CheckKind::MisplacedComparisonConstant(..) | CheckKind::MisplacedComparisonConstant(..)
| CheckKind::MissingReturnTypeSpecialMethod(..) | CheckKind::MissingReturnTypeSpecialMethod(..)
| CheckKind::NativeLiterals
| CheckKind::NewLineAfterLastParagraph | CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(..) | CheckKind::NewLineAfterSectionName(..)
| CheckKind::NoBlankLineAfterFunction(..) | CheckKind::NoBlankLineAfterFunction(..)
@ -2991,7 +2999,6 @@ impl CheckKind {
| CheckKind::RedundantOpenModes | CheckKind::RedundantOpenModes
| CheckKind::RedundantTupleInExceptionHandler(..) | CheckKind::RedundantTupleInExceptionHandler(..)
| CheckKind::RemoveSixCompat | CheckKind::RemoveSixCompat
| CheckKind::DatetimeTimezoneUTC
| CheckKind::SectionNameEndsInColon(..) | CheckKind::SectionNameEndsInColon(..)
| CheckKind::SectionNotOverIndented(..) | CheckKind::SectionNotOverIndented(..)
| CheckKind::SectionUnderlineAfterName(..) | CheckKind::SectionUnderlineAfterName(..)

View file

@ -513,6 +513,7 @@ pub enum CheckCodePrefix {
UP015, UP015,
UP016, UP016,
UP017, UP017,
UP018,
W, W,
W2, W2,
W29, W29,
@ -2088,6 +2089,7 @@ impl CheckCodePrefix {
CheckCode::UP015, CheckCode::UP015,
CheckCode::UP016, CheckCode::UP016,
CheckCode::UP017, CheckCode::UP017,
CheckCode::UP018,
] ]
} }
CheckCodePrefix::U0 => { CheckCodePrefix::U0 => {
@ -2114,6 +2116,7 @@ impl CheckCodePrefix {
CheckCode::UP015, CheckCode::UP015,
CheckCode::UP016, CheckCode::UP016,
CheckCode::UP017, CheckCode::UP017,
CheckCode::UP018,
] ]
} }
CheckCodePrefix::U00 => { CheckCodePrefix::U00 => {
@ -2222,6 +2225,7 @@ impl CheckCodePrefix {
CheckCode::UP015, CheckCode::UP015,
CheckCode::UP016, CheckCode::UP016,
CheckCode::UP017, CheckCode::UP017,
CheckCode::UP018,
] ]
} }
CheckCodePrefix::U010 => { CheckCodePrefix::U010 => {
@ -2313,6 +2317,7 @@ impl CheckCodePrefix {
CheckCode::UP015, CheckCode::UP015,
CheckCode::UP016, CheckCode::UP016,
CheckCode::UP017, CheckCode::UP017,
CheckCode::UP018,
], ],
CheckCodePrefix::UP0 => vec![ CheckCodePrefix::UP0 => vec![
CheckCode::UP001, CheckCode::UP001,
@ -2331,6 +2336,7 @@ impl CheckCodePrefix {
CheckCode::UP015, CheckCode::UP015,
CheckCode::UP016, CheckCode::UP016,
CheckCode::UP017, CheckCode::UP017,
CheckCode::UP018,
], ],
CheckCodePrefix::UP00 => vec![ CheckCodePrefix::UP00 => vec![
CheckCode::UP001, CheckCode::UP001,
@ -2359,6 +2365,7 @@ impl CheckCodePrefix {
CheckCode::UP015, CheckCode::UP015,
CheckCode::UP016, CheckCode::UP016,
CheckCode::UP017, CheckCode::UP017,
CheckCode::UP018,
], ],
CheckCodePrefix::UP010 => vec![CheckCode::UP010], CheckCodePrefix::UP010 => vec![CheckCode::UP010],
CheckCodePrefix::UP011 => vec![CheckCode::UP011], CheckCodePrefix::UP011 => vec![CheckCode::UP011],
@ -2368,6 +2375,7 @@ impl CheckCodePrefix {
CheckCodePrefix::UP015 => vec![CheckCode::UP015], CheckCodePrefix::UP015 => vec![CheckCode::UP015],
CheckCodePrefix::UP016 => vec![CheckCode::UP016], CheckCodePrefix::UP016 => vec![CheckCode::UP016],
CheckCodePrefix::UP017 => vec![CheckCode::UP017], CheckCodePrefix::UP017 => vec![CheckCode::UP017],
CheckCodePrefix::UP018 => vec![CheckCode::UP018],
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605], CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
CheckCodePrefix::W2 => vec![CheckCode::W292], CheckCodePrefix::W2 => vec![CheckCode::W292],
CheckCodePrefix::W29 => vec![CheckCode::W292], CheckCodePrefix::W29 => vec![CheckCode::W292],
@ -2923,6 +2931,7 @@ impl CheckCodePrefix {
CheckCodePrefix::UP015 => SuffixLength::Three, CheckCodePrefix::UP015 => SuffixLength::Three,
CheckCodePrefix::UP016 => SuffixLength::Three, CheckCodePrefix::UP016 => SuffixLength::Three,
CheckCodePrefix::UP017 => SuffixLength::Three, CheckCodePrefix::UP017 => SuffixLength::Three,
CheckCodePrefix::UP018 => SuffixLength::Three,
CheckCodePrefix::W => SuffixLength::Zero, CheckCodePrefix::W => SuffixLength::Zero,
CheckCodePrefix::W2 => SuffixLength::One, CheckCodePrefix::W2 => SuffixLength::One,
CheckCodePrefix::W29 => SuffixLength::Two, CheckCodePrefix::W29 => SuffixLength::Two,

View file

@ -37,6 +37,7 @@ mod tests {
#[test_case(CheckCode::UP014, Path::new("UP014.py"); "UP014")] #[test_case(CheckCode::UP014, Path::new("UP014.py"); "UP014")]
#[test_case(CheckCode::UP015, Path::new("UP015.py"); "UP015")] #[test_case(CheckCode::UP015, Path::new("UP015.py"); "UP015")]
#[test_case(CheckCode::UP016, Path::new("UP016.py"); "UP016")] #[test_case(CheckCode::UP016, Path::new("UP016.py"); "UP016")]
#[test_case(CheckCode::UP018, Path::new("UP018.py"); "UP018")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> { fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy()); let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path( let mut checks = test_path(

View file

@ -2,6 +2,7 @@ pub use convert_named_tuple_functional_to_class::convert_named_tuple_functional_
pub use convert_typed_dict_functional_to_class::convert_typed_dict_functional_to_class; pub use convert_typed_dict_functional_to_class::convert_typed_dict_functional_to_class;
pub use datetime_utc_alias::datetime_utc_alias; pub use datetime_utc_alias::datetime_utc_alias;
pub use deprecated_unittest_alias::deprecated_unittest_alias; pub use deprecated_unittest_alias::deprecated_unittest_alias;
pub use native_literals::native_literals;
pub use redundant_open_modes::redundant_open_modes; pub use redundant_open_modes::redundant_open_modes;
pub use remove_six_compat::remove_six_compat; pub use remove_six_compat::remove_six_compat;
pub use super_call_with_parameters::super_call_with_parameters; pub use super_call_with_parameters::super_call_with_parameters;
@ -18,6 +19,7 @@ mod convert_named_tuple_functional_to_class;
mod convert_typed_dict_functional_to_class; mod convert_typed_dict_functional_to_class;
mod datetime_utc_alias; mod datetime_utc_alias;
mod deprecated_unittest_alias; mod deprecated_unittest_alias;
mod native_literals;
mod redundant_open_modes; mod redundant_open_modes;
mod remove_six_compat; mod remove_six_compat;
mod super_call_with_parameters; mod super_call_with_parameters;

View file

@ -0,0 +1,73 @@
use rustpython_ast::{Constant, Expr, ExprKind, Keyword};
use rustpython_parser::lexer;
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};
/// UP018
pub fn native_literals(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
) {
let ExprKind::Name { id, .. } = &func.node else { return; };
if (id == "str" || id == "bytes")
&& keywords.is_empty()
&& args.len() <= 1
&& checker.is_builtin(id)
{
let Some(arg) = args.get(0) else {
let mut check = Check::new(CheckKind::NativeLiterals, Range::from_located(expr));
if checker.patch(&CheckCode::UP018) {
check.amend(Fix::replacement(
format!("{}\"\"", if id == "bytes" { "b" } else { "" }),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
return;
};
if !matches!(
&arg.node,
ExprKind::Constant {
value: Constant::Str(_) | Constant::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
// here and skip.
let arg_code = checker
.locator
.slice_source_code_range(&Range::from_located(arg));
if lexer::make_tokenizer(&arg_code)
.flatten()
.filter(|(_, tok, _)| matches!(tok, Tok::String { .. } | Tok::Bytes { .. }))
.count()
> 1
{
return;
}
let mut check = Check::new(CheckKind::NativeLiterals, Range::from_located(expr));
if checker.patch(&CheckCode::UP018) {
check.amend(Fix::replacement(
arg_code.to_string(),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}
}

View file

@ -0,0 +1,95 @@
---
source: src/pyupgrade/mod.rs
expression: checks
---
- kind: NativeLiterals
location:
row: 18
column: 0
end_location:
row: 18
column: 5
fix:
content: "\"\""
location:
row: 18
column: 0
end_location:
row: 18
column: 5
- kind: NativeLiterals
location:
row: 19
column: 0
end_location:
row: 19
column: 10
fix:
content: "\"foo\""
location:
row: 19
column: 0
end_location:
row: 19
column: 10
- kind: NativeLiterals
location:
row: 20
column: 0
end_location:
row: 21
column: 7
fix:
content: "\"\"\"\nfoo\"\"\""
location:
row: 20
column: 0
end_location:
row: 21
column: 7
- kind: NativeLiterals
location:
row: 22
column: 0
end_location:
row: 22
column: 7
fix:
content: "b\"\""
location:
row: 22
column: 0
end_location:
row: 22
column: 7
- kind: NativeLiterals
location:
row: 23
column: 0
end_location:
row: 23
column: 13
fix:
content: "b\"foo\""
location:
row: 23
column: 0
end_location:
row: 23
column: 13
- kind: NativeLiterals
location:
row: 24
column: 0
end_location:
row: 25
column: 7
fix:
content: "b\"\"\"\nfoo\"\"\""
location:
row: 24
column: 0
end_location:
row: 25
column: 7