Skip to content

Commit

Permalink
Implement "native literals" check from pyupgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
squiddy committed Dec 23, 2022
1 parent e290050 commit 0f243fc
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -629,6 +629,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 |
| UP016 | RemoveSixCompat | Unnecessary `six` compatibility usage | 🛠 |
| UP017 | DatetimeTimezoneUTC | Use `datetime.UTC` alias | 🛠 |
| UP018 | NativeLiterals | Unnecessary call to `str` and `bytes` | 🛠 |

### pep8-naming (N)

Expand Down Expand Up @@ -1232,7 +1233,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/) (17/33)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (18/33)
- [`yesqa`](https://github.com/asottile/yesqa)

Note that, in some cases, Ruff uses different error code prefixes than would be found in the
Expand Down
25 changes: 25 additions & 0 deletions resources/test/fixtures/pyupgrade/UP018.py
@@ -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""")
3 changes: 3 additions & 0 deletions src/checkers/ast.rs
Expand Up @@ -1644,6 +1644,9 @@ where
if self.settings.enabled.contains(&CheckCode::UP016) {
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
if self.settings.enabled.contains(&CheckCode::UP008) {
Expand Down
9 changes: 8 additions & 1 deletion src/checks.rs
Expand Up @@ -226,6 +226,7 @@ pub enum CheckCode {
UP015,
UP016,
UP017,
UP018,
// pydocstyle
D100,
D101,
Expand Down Expand Up @@ -829,6 +830,7 @@ pub enum CheckKind {
RedundantOpenModes,
RemoveSixCompat,
DatetimeTimezoneUTC,
NativeLiterals,
// pydocstyle
BlankLineAfterLastSection(String),
BlankLineAfterSection(String),
Expand Down Expand Up @@ -1203,6 +1205,7 @@ impl CheckCode {
CheckCode::UP015 => CheckKind::RedundantOpenModes,
CheckCode::UP016 => CheckKind::RemoveSixCompat,
CheckCode::UP017 => CheckKind::DatetimeTimezoneUTC,
CheckCode::UP018 => CheckKind::NativeLiterals,
// pydocstyle
CheckCode::D100 => CheckKind::PublicModule,
CheckCode::D101 => CheckKind::PublicClass,
Expand Down Expand Up @@ -1621,6 +1624,7 @@ impl CheckCode {
CheckCode::UP015 => CheckCategory::Pyupgrade,
CheckCode::UP016 => CheckCategory::Pyupgrade,
CheckCode::UP017 => CheckCategory::Pyupgrade,
CheckCode::UP018 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020,
Expand Down Expand Up @@ -1832,6 +1836,7 @@ impl CheckKind {
CheckKind::RedundantOpenModes => &CheckCode::UP015,
CheckKind::RemoveSixCompat => &CheckCode::UP016,
CheckKind::DatetimeTimezoneUTC => &CheckCode::UP017,
CheckKind::NativeLiterals => &CheckCode::UP018,
// pydocstyle
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
Expand Down Expand Up @@ -2555,6 +2560,7 @@ impl CheckKind {
CheckKind::RedundantOpenModes => "Unnecessary open mode parameters".to_string(),
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::ConvertTypedDictFunctionalToClass(name) => {
format!("Convert `{name}` from `TypedDict` functional to class syntax")
}
Expand Down Expand Up @@ -2956,6 +2962,7 @@ impl CheckKind {
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)
| CheckKind::DashedUnderlineAfterSection(..)
| CheckKind::DatetimeTimezoneUTC
| CheckKind::DeprecatedUnittestAlias(..)
| CheckKind::DoNotAssertFalse
| CheckKind::DoNotAssignLambda
Expand All @@ -2969,6 +2976,7 @@ impl CheckKind {
| CheckKind::KeyInDict(..)
| CheckKind::MisplacedComparisonConstant(..)
| CheckKind::MissingReturnTypeSpecialMethod(..)
| CheckKind::NativeLiterals
| CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(..)
| CheckKind::NoBlankLineAfterFunction(..)
Expand All @@ -2991,7 +2999,6 @@ impl CheckKind {
| CheckKind::RedundantOpenModes
| CheckKind::RedundantTupleInExceptionHandler(..)
| CheckKind::RemoveSixCompat
| CheckKind::DatetimeTimezoneUTC
| CheckKind::SectionNameEndsInColon(..)
| CheckKind::SectionNotOverIndented(..)
| CheckKind::SectionUnderlineAfterName(..)
Expand Down
9 changes: 9 additions & 0 deletions src/checks_gen.rs
Expand Up @@ -513,6 +513,7 @@ pub enum CheckCodePrefix {
UP015,
UP016,
UP017,
UP018,
W,
W2,
W29,
Expand Down Expand Up @@ -2088,6 +2089,7 @@ impl CheckCodePrefix {
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
]
}
CheckCodePrefix::U0 => {
Expand All @@ -2114,6 +2116,7 @@ impl CheckCodePrefix {
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
]
}
CheckCodePrefix::U00 => {
Expand Down Expand Up @@ -2222,6 +2225,7 @@ impl CheckCodePrefix {
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
]
}
CheckCodePrefix::U010 => {
Expand Down Expand Up @@ -2313,6 +2317,7 @@ impl CheckCodePrefix {
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
],
CheckCodePrefix::UP0 => vec![
CheckCode::UP001,
Expand All @@ -2331,6 +2336,7 @@ impl CheckCodePrefix {
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
],
CheckCodePrefix::UP00 => vec![
CheckCode::UP001,
Expand Down Expand Up @@ -2359,6 +2365,7 @@ impl CheckCodePrefix {
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
],
CheckCodePrefix::UP010 => vec![CheckCode::UP010],
CheckCodePrefix::UP011 => vec![CheckCode::UP011],
Expand All @@ -2368,6 +2375,7 @@ impl CheckCodePrefix {
CheckCodePrefix::UP015 => vec![CheckCode::UP015],
CheckCodePrefix::UP016 => vec![CheckCode::UP016],
CheckCodePrefix::UP017 => vec![CheckCode::UP017],
CheckCodePrefix::UP018 => vec![CheckCode::UP018],
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
CheckCodePrefix::W2 => vec![CheckCode::W292],
CheckCodePrefix::W29 => vec![CheckCode::W292],
Expand Down Expand Up @@ -2923,6 +2931,7 @@ impl CheckCodePrefix {
CheckCodePrefix::UP015 => SuffixLength::Three,
CheckCodePrefix::UP016 => SuffixLength::Three,
CheckCodePrefix::UP017 => SuffixLength::Three,
CheckCodePrefix::UP018 => SuffixLength::Three,
CheckCodePrefix::W => SuffixLength::Zero,
CheckCodePrefix::W2 => SuffixLength::One,
CheckCodePrefix::W29 => SuffixLength::Two,
Expand Down
1 change: 1 addition & 0 deletions src/pyupgrade/mod.rs
Expand Up @@ -37,6 +37,7 @@ mod tests {
#[test_case(CheckCode::UP014, Path::new("UP014.py"); "UP014")]
#[test_case(CheckCode::UP015, Path::new("UP015.py"); "UP015")]
#[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<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(
Expand Down
2 changes: 2 additions & 0 deletions src/pyupgrade/plugins/mod.rs
Expand Up @@ -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 datetime_utc_alias::datetime_utc_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 remove_six_compat::remove_six_compat;
pub use super_call_with_parameters::super_call_with_parameters;
Expand All @@ -18,6 +19,7 @@ mod convert_named_tuple_functional_to_class;
mod convert_typed_dict_functional_to_class;
mod datetime_utc_alias;
mod deprecated_unittest_alias;
mod native_literals;
mod redundant_open_modes;
mod remove_six_compat;
mod super_call_with_parameters;
Expand Down
73 changes: 73 additions & 0 deletions src/pyupgrade/plugins/native_literals.rs
@@ -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")
&& checker.is_builtin(id)
&& keywords.is_empty()
&& args.len() <= 1
{
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);
}
}
@@ -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

0 comments on commit 0f243fc

Please sign in to comment.