mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 12:29:48 +00:00
Implement "native literals" check from pyupgrade (#1350)
This commit is contained in:
parent
e290050821
commit
9da3e2cca1
9 changed files with 218 additions and 2 deletions
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
|
25
resources/test/fixtures/pyupgrade/UP018.py
vendored
Normal file
25
resources/test/fixtures/pyupgrade/UP018.py
vendored
Normal 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""")
|
|
@ -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) {
|
||||
|
|
|
@ -226,6 +226,7 @@ pub enum CheckCode {
|
|||
UP015,
|
||||
UP016,
|
||||
UP017,
|
||||
UP018,
|
||||
// pydocstyle
|
||||
D100,
|
||||
D101,
|
||||
|
@ -829,6 +830,7 @@ pub enum CheckKind {
|
|||
RedundantOpenModes,
|
||||
RemoveSixCompat,
|
||||
DatetimeTimezoneUTC,
|
||||
NativeLiterals,
|
||||
// pydocstyle
|
||||
BlankLineAfterLastSection(String),
|
||||
BlankLineAfterSection(String),
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -2956,6 +2962,7 @@ impl CheckKind {
|
|||
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
|
||||
| CheckKind::ConvertTypedDictFunctionalToClass(..)
|
||||
| CheckKind::DashedUnderlineAfterSection(..)
|
||||
| CheckKind::DatetimeTimezoneUTC
|
||||
| CheckKind::DeprecatedUnittestAlias(..)
|
||||
| CheckKind::DoNotAssertFalse
|
||||
| CheckKind::DoNotAssignLambda
|
||||
|
@ -2969,6 +2976,7 @@ impl CheckKind {
|
|||
| CheckKind::KeyInDict(..)
|
||||
| CheckKind::MisplacedComparisonConstant(..)
|
||||
| CheckKind::MissingReturnTypeSpecialMethod(..)
|
||||
| CheckKind::NativeLiterals
|
||||
| CheckKind::NewLineAfterLastParagraph
|
||||
| CheckKind::NewLineAfterSectionName(..)
|
||||
| CheckKind::NoBlankLineAfterFunction(..)
|
||||
|
@ -2991,7 +2999,6 @@ impl CheckKind {
|
|||
| CheckKind::RedundantOpenModes
|
||||
| CheckKind::RedundantTupleInExceptionHandler(..)
|
||||
| CheckKind::RemoveSixCompat
|
||||
| CheckKind::DatetimeTimezoneUTC
|
||||
| CheckKind::SectionNameEndsInColon(..)
|
||||
| CheckKind::SectionNotOverIndented(..)
|
||||
| CheckKind::SectionUnderlineAfterName(..)
|
||||
|
|
|
@ -513,6 +513,7 @@ pub enum CheckCodePrefix {
|
|||
UP015,
|
||||
UP016,
|
||||
UP017,
|
||||
UP018,
|
||||
W,
|
||||
W2,
|
||||
W29,
|
||||
|
@ -2088,6 +2089,7 @@ impl CheckCodePrefix {
|
|||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U0 => {
|
||||
|
@ -2114,6 +2116,7 @@ impl CheckCodePrefix {
|
|||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U00 => {
|
||||
|
@ -2222,6 +2225,7 @@ impl CheckCodePrefix {
|
|||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U010 => {
|
||||
|
@ -2313,6 +2317,7 @@ impl CheckCodePrefix {
|
|||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
],
|
||||
CheckCodePrefix::UP0 => vec![
|
||||
CheckCode::UP001,
|
||||
|
@ -2331,6 +2336,7 @@ impl CheckCodePrefix {
|
|||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
],
|
||||
CheckCodePrefix::UP00 => vec![
|
||||
CheckCode::UP001,
|
||||
|
@ -2359,6 +2365,7 @@ impl CheckCodePrefix {
|
|||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
],
|
||||
CheckCodePrefix::UP010 => vec![CheckCode::UP010],
|
||||
CheckCodePrefix::UP011 => vec![CheckCode::UP011],
|
||||
|
@ -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],
|
||||
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
@ -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;
|
||||
|
|
73
src/pyupgrade/plugins/native_literals.rs
Normal file
73
src/pyupgrade/plugins/native_literals.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue