Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
124782771f | ||
|
|
98cab5cdba | ||
|
|
40f38c94a5 | ||
|
|
7839204bf7 | ||
|
|
e63ea704f0 |
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.129
|
||||
rev: v0.0.130
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -940,7 +940,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.129-dev.0"
|
||||
version = "0.0.130-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
@@ -2248,7 +2248,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.129"
|
||||
version = "0.0.130"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2298,7 +2298,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.129"
|
||||
version = "0.0.130"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.129"
|
||||
version = "0.0.130"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -466,6 +466,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| U011 | UnnecessaryLRUCacheParams | Unnecessary parameters to `functools.lru_cache` | 🛠 |
|
||||
| U012 | UnnecessaryEncodeUTF8 | Unnecessary call to `encode` as UTF-8 | 🛠 |
|
||||
| U013 | ConvertTypedDictFunctionalToClass | Convert `TypedDict` functional syntax to class syntax | 🛠 |
|
||||
| U014 | ConvertNamedTupleFunctionalToClass | Convert `NamedTuple` functional syntax to class syntax | 🛠 |
|
||||
|
||||
### pep8-naming
|
||||
|
||||
@@ -549,7 +550,7 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
|
||||
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body | 🛠 |
|
||||
| B008 | FunctionCallArgumentDefault | Do not perform function call in argument defaults | |
|
||||
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
|
||||
| B010 | SetAttrWithConstant | Do not call `setattr` with a constant attribute value. It is not any safer than normal property access. | |
|
||||
| B010 | SetAttrWithConstant | Do not call `setattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
|
||||
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||
| B012 | JumpStatementInFinally | `return/continue/break` inside finally blocks cause exceptions to be silenced | |
|
||||
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError` instead of `except (ValueError,)`. | |
|
||||
@@ -820,7 +821,7 @@ including:
|
||||
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)
|
||||
- [`mccabe`](https://pypi.org/project/mccabe/)
|
||||
- [`isort`](https://pypi.org/project/isort/)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/33)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (15/33)
|
||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||
|
||||
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
@@ -852,7 +853,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [`mccabe`](https://pypi.org/project/mccabe/)
|
||||
|
||||
Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa),
|
||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/33).
|
||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (15/33).
|
||||
|
||||
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
|
||||
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.129"
|
||||
version = "0.0.130"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.129"
|
||||
version = "0.0.130"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.129-dev.0"
|
||||
version = "0.0.130-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
1
resources/test/fixtures/B009_B010.py
vendored
1
resources/test/fixtures/B009_B010.py
vendored
@@ -34,3 +34,4 @@ setattr(foo, "bar", None)
|
||||
setattr(foo, "_123abc", None)
|
||||
setattr(foo, "abc123", None)
|
||||
setattr(foo, r"abc123", None)
|
||||
setattr(foo.bar, r"baz", None)
|
||||
|
||||
22
resources/test/fixtures/U014.py
vendored
Normal file
22
resources/test/fixtures/U014.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
from typing import NamedTuple
|
||||
import typing
|
||||
|
||||
# with complex annotations
|
||||
NT1 = NamedTuple("NT1", [("a", int), ("b", tuple[str, ...])])
|
||||
|
||||
# with default values as list
|
||||
NT2 = NamedTuple(
|
||||
"NT2",
|
||||
[("a", int), ("b", str), ("c", list[bool])],
|
||||
defaults=["foo", [True]],
|
||||
)
|
||||
|
||||
# with namespace
|
||||
NT3 = typing.NamedTuple("NT3", [("a", int), ("b", str)])
|
||||
|
||||
# with too many default values
|
||||
NT4 = NamedTuple(
|
||||
"NT4",
|
||||
[("a", int), ("b", str)],
|
||||
defaults=[1, "bar", "baz"],
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.129"
|
||||
version = "0.0.130"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -19,9 +19,7 @@ impl Range {
|
||||
pub fn from_located<T>(located: &Located<T>) -> Self {
|
||||
Range {
|
||||
location: located.location,
|
||||
end_location: located
|
||||
.end_location
|
||||
.expect("AST nodes should have end_location."),
|
||||
end_location: located.end_location.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1002,6 +1002,11 @@ where
|
||||
self, stmt, targets, value,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::U014) {
|
||||
pyupgrade::plugins::convert_named_tuple_functional_to_class(
|
||||
self, stmt, targets, value,
|
||||
);
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign { target, value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
|
||||
@@ -170,6 +170,7 @@ pub enum CheckCode {
|
||||
U011,
|
||||
U012,
|
||||
U013,
|
||||
U014,
|
||||
// pydocstyle
|
||||
D100,
|
||||
D101,
|
||||
@@ -500,7 +501,8 @@ pub enum CheckKind {
|
||||
UnnecessaryFutureImport(Vec<String>),
|
||||
UnnecessaryLRUCacheParams,
|
||||
UnnecessaryEncodeUTF8,
|
||||
ConvertTypedDictFunctionalToClass,
|
||||
ConvertTypedDictFunctionalToClass(String),
|
||||
ConvertNamedTupleFunctionalToClass(String),
|
||||
// pydocstyle
|
||||
BlankLineAfterLastSection(String),
|
||||
BlankLineAfterSection(String),
|
||||
@@ -773,7 +775,8 @@ impl CheckCode {
|
||||
CheckCode::U010 => CheckKind::UnnecessaryFutureImport(vec!["...".to_string()]),
|
||||
CheckCode::U011 => CheckKind::UnnecessaryLRUCacheParams,
|
||||
CheckCode::U012 => CheckKind::UnnecessaryEncodeUTF8,
|
||||
CheckCode::U013 => CheckKind::ConvertTypedDictFunctionalToClass,
|
||||
CheckCode::U013 => CheckKind::ConvertTypedDictFunctionalToClass("...".to_string()),
|
||||
CheckCode::U014 => CheckKind::ConvertNamedTupleFunctionalToClass("...".to_string()),
|
||||
// pydocstyle
|
||||
CheckCode::D100 => CheckKind::PublicModule,
|
||||
CheckCode::D101 => CheckKind::PublicClass,
|
||||
@@ -1005,6 +1008,7 @@ impl CheckCode {
|
||||
CheckCode::U011 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U012 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U013 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U014 => CheckCategory::Pyupgrade,
|
||||
CheckCode::D100 => CheckCategory::Pydocstyle,
|
||||
CheckCode::D101 => CheckCategory::Pydocstyle,
|
||||
CheckCode::D102 => CheckCategory::Pydocstyle,
|
||||
@@ -1227,7 +1231,8 @@ impl CheckKind {
|
||||
CheckKind::UnnecessaryFutureImport(_) => &CheckCode::U010,
|
||||
CheckKind::UnnecessaryLRUCacheParams => &CheckCode::U011,
|
||||
CheckKind::UnnecessaryEncodeUTF8 => &CheckCode::U012,
|
||||
CheckKind::ConvertTypedDictFunctionalToClass => &CheckCode::U013,
|
||||
CheckKind::ConvertTypedDictFunctionalToClass(_) => &CheckCode::U013,
|
||||
CheckKind::ConvertNamedTupleFunctionalToClass(_) => &CheckCode::U014,
|
||||
// pydocstyle
|
||||
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
|
||||
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
|
||||
@@ -1776,8 +1781,11 @@ impl CheckKind {
|
||||
"Unnecessary parameters to `functools.lru_cache`".to_string()
|
||||
}
|
||||
CheckKind::UnnecessaryEncodeUTF8 => "Unnecessary call to `encode` as UTF-8".to_string(),
|
||||
CheckKind::ConvertTypedDictFunctionalToClass => {
|
||||
"Convert `TypedDict` functional syntax to class syntax".to_string()
|
||||
CheckKind::ConvertTypedDictFunctionalToClass(name) => {
|
||||
format!("Convert `{name}` from `TypedDict` functional to class syntax")
|
||||
}
|
||||
CheckKind::ConvertNamedTupleFunctionalToClass(name) => {
|
||||
format!("Convert `{name}` from `NamedTuple` functional to class syntax")
|
||||
}
|
||||
// pydocstyle
|
||||
CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
|
||||
@@ -2045,7 +2053,8 @@ impl CheckKind {
|
||||
| CheckKind::BlankLineAfterSummary
|
||||
| CheckKind::BlankLineBeforeSection(..)
|
||||
| CheckKind::CapitalizeSectionName(..)
|
||||
| CheckKind::ConvertTypedDictFunctionalToClass
|
||||
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
|
||||
| CheckKind::ConvertTypedDictFunctionalToClass(..)
|
||||
| CheckKind::DashedUnderlineAfterSection(..)
|
||||
| CheckKind::DeprecatedUnittestAlias(..)
|
||||
| CheckKind::DoNotAssertFalse
|
||||
@@ -2076,6 +2085,7 @@ impl CheckKind {
|
||||
| CheckKind::SectionUnderlineAfterName(..)
|
||||
| CheckKind::SectionUnderlineMatchesSectionLength(..)
|
||||
| CheckKind::SectionUnderlineNotOverIndented(..)
|
||||
| CheckKind::SetAttrWithConstant
|
||||
| CheckKind::SuperCallWithParameters
|
||||
| CheckKind::TrueFalseComparison(..)
|
||||
| CheckKind::TypeOfPrimitive(..)
|
||||
|
||||
@@ -297,6 +297,7 @@ pub enum CheckCodePrefix {
|
||||
U011,
|
||||
U012,
|
||||
U013,
|
||||
U014,
|
||||
W,
|
||||
W2,
|
||||
W29,
|
||||
@@ -1111,6 +1112,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::U011,
|
||||
CheckCode::U012,
|
||||
CheckCode::U013,
|
||||
CheckCode::U014,
|
||||
],
|
||||
CheckCodePrefix::U0 => vec![
|
||||
CheckCode::U001,
|
||||
@@ -1125,6 +1127,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::U011,
|
||||
CheckCode::U012,
|
||||
CheckCode::U013,
|
||||
CheckCode::U014,
|
||||
],
|
||||
CheckCodePrefix::U00 => vec![
|
||||
CheckCode::U001,
|
||||
@@ -1149,11 +1152,13 @@ impl CheckCodePrefix {
|
||||
CheckCode::U011,
|
||||
CheckCode::U012,
|
||||
CheckCode::U013,
|
||||
CheckCode::U014,
|
||||
],
|
||||
CheckCodePrefix::U010 => vec![CheckCode::U010],
|
||||
CheckCodePrefix::U011 => vec![CheckCode::U011],
|
||||
CheckCodePrefix::U012 => vec![CheckCode::U012],
|
||||
CheckCodePrefix::U013 => vec![CheckCode::U013],
|
||||
CheckCodePrefix::U014 => vec![CheckCode::U014],
|
||||
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
|
||||
CheckCodePrefix::W2 => vec![CheckCode::W292],
|
||||
CheckCodePrefix::W29 => vec![CheckCode::W292],
|
||||
@@ -1496,6 +1501,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::U011 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::U012 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::U013 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::U014 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::W => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::W2 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::W29 => PrefixSpecificity::Tens,
|
||||
|
||||
@@ -1,26 +1,61 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind};
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
|
||||
fn assignment(obj: &Expr, name: &str, value: &Expr) -> Option<String> {
|
||||
let stmt = Stmt::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
StmtKind::Assign {
|
||||
targets: vec![Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
ExprKind::Attribute {
|
||||
value: Box::new(obj.clone()),
|
||||
attr: name.to_string(),
|
||||
ctx: ExprContext::Store,
|
||||
},
|
||||
)],
|
||||
value: Box::new(value.clone()),
|
||||
type_comment: None,
|
||||
},
|
||||
);
|
||||
let mut generator = SourceGenerator::new();
|
||||
match generator.unparse_stmt(&stmt) {
|
||||
Ok(()) => generator.generate().ok(),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// B010
|
||||
pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "setattr" {
|
||||
if let [_, arg, _] = args {
|
||||
if let [obj, name, value] = args {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
value: Constant::Str(name),
|
||||
..
|
||||
} = &arg.node
|
||||
} = &name.node
|
||||
{
|
||||
if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SetAttrWithConstant,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
if IDENTIFIER_REGEX.is_match(name) && !KWLIST.contains(&name.as_str()) {
|
||||
let mut check =
|
||||
Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
if let Some(content) = assignment(obj, name, value) {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,6 +519,7 @@ mod tests {
|
||||
#[test_case(CheckCode::U011, Path::new("U011_1.py"); "U011_1")]
|
||||
#[test_case(CheckCode::U012, Path::new("U012.py"); "U012")]
|
||||
#[test_case(CheckCode::U013, Path::new("U013.py"); "U013")]
|
||||
#[test_case(CheckCode::U014, Path::new("U014.py"); "U014")]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_0.py"); "W292_0")]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_1.py"); "W292_1")]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_2.py"); "W292_2")]
|
||||
|
||||
@@ -162,12 +162,13 @@ pub fn unnecessary_lru_cache_params(
|
||||
import_aliases,
|
||||
)
|
||||
{
|
||||
let range = Range {
|
||||
location: func.end_location.unwrap(),
|
||||
end_location: expr.end_location.unwrap(),
|
||||
};
|
||||
// Ex) `functools.lru_cache()`
|
||||
if keywords.is_empty() {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLRUCacheParams,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
return Some(Check::new(CheckKind::UnnecessaryLRUCacheParams, range));
|
||||
}
|
||||
// Ex) `functools.lru_cache(maxsize=None)`
|
||||
if target_version >= PythonVersion::Py39 && keywords.len() == 1 {
|
||||
@@ -181,10 +182,7 @@ pub fn unnecessary_lru_cache_params(
|
||||
}
|
||||
)
|
||||
{
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLRUCacheParams,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
return Some(Check::new(CheckKind::UnnecessaryLRUCacheParams, range));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,34 +191,3 @@ pub fn remove_unnecessary_future_import(
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// U011
|
||||
pub fn remove_unnecessary_lru_cache_params(
|
||||
locator: &SourceCodeLocator,
|
||||
decor_at: &Location,
|
||||
) -> Option<Fix> {
|
||||
let contents = locator.slice_source_code_at(decor_at);
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
let mut count: usize = 0;
|
||||
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
fix_start = Some(helpers::to_absolute(&start, decor_at));
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Rpar) {
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
fix_end = Some(helpers::to_absolute(&end, decor_at));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Some(Fix::deletion(start, end)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
208
src/pyupgrade/plugins/convert_named_tuple_functional_to_class.rs
Normal file
208
src/pyupgrade/plugins/convert_named_tuple_functional_to_class.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
use anyhow::{bail, Result};
|
||||
use log::error;
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Keyword, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::match_module_member;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
|
||||
/// Return the typename, args, keywords and mother class
|
||||
fn match_named_tuple_assign<'a>(
|
||||
checker: &Checker,
|
||||
targets: &'a [Expr],
|
||||
value: &'a Expr,
|
||||
) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a ExprKind)> {
|
||||
if let Some(target) = targets.get(0) {
|
||||
if let ExprKind::Name { id: typename, .. } = &target.node {
|
||||
if let ExprKind::Call {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
} = &value.node
|
||||
{
|
||||
if match_module_member(
|
||||
func,
|
||||
"typing",
|
||||
"NamedTuple",
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
) {
|
||||
return Some((typename, args, keywords, &func.node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Generate a `StmtKind::AnnAssign` representing the provided property
|
||||
/// definition.
|
||||
fn create_property_assignment_stmt(
|
||||
property: &str,
|
||||
annotation: &ExprKind,
|
||||
value: Option<&ExprKind>,
|
||||
) -> Stmt {
|
||||
Stmt::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
StmtKind::AnnAssign {
|
||||
target: Box::new(Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
ExprKind::Name {
|
||||
id: property.to_string(),
|
||||
ctx: ExprContext::Load,
|
||||
},
|
||||
)),
|
||||
annotation: Box::new(Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
annotation.clone(),
|
||||
)),
|
||||
value: value
|
||||
.map(|v| Box::new(Expr::new(Default::default(), Default::default(), v.clone()))),
|
||||
simple: 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Match the `defaults` keyword in a `NamedTuple(...)` call.
|
||||
fn match_defaults(keywords: &[Keyword]) -> Result<&[Expr]> {
|
||||
match keywords.iter().find(|keyword| {
|
||||
if let Some(arg) = &keyword.node.arg {
|
||||
arg.as_str() == "defaults"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
Some(defaults) => match &defaults.node.value.node {
|
||||
ExprKind::List { elts, .. } => Ok(elts),
|
||||
ExprKind::Tuple { elts, .. } => Ok(elts),
|
||||
_ => bail!("Expected defaults to be `ExprKind::List` | `ExprKind::Tuple`"),
|
||||
},
|
||||
None => Ok(&[]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a list of property assignments from the `NamedTuple` arguments.
|
||||
fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<Stmt>> {
|
||||
if let Some(fields) = args.get(1) {
|
||||
if let ExprKind::List { elts, .. } = &fields.node {
|
||||
let padded_defaults = if elts.len() >= defaults.len() {
|
||||
std::iter::repeat(None)
|
||||
.take(elts.len() - defaults.len())
|
||||
.chain(defaults.iter().map(Some))
|
||||
} else {
|
||||
bail!("Defaults must be `None` or an iterable of at least the number of fields")
|
||||
};
|
||||
elts.iter()
|
||||
.zip(padded_defaults)
|
||||
.map(|(field, default)| {
|
||||
if let ExprKind::Tuple { elts, .. } = &field.node {
|
||||
if let [field_name, annotation] = elts.as_slice() {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(property),
|
||||
..
|
||||
} = &field_name.node
|
||||
{
|
||||
if IDENTIFIER_REGEX.is_match(property)
|
||||
&& !KWLIST.contains(&property.as_str())
|
||||
{
|
||||
Ok(create_property_assignment_stmt(
|
||||
property,
|
||||
&annotation.node,
|
||||
default.map(|d| &d.node),
|
||||
))
|
||||
} else {
|
||||
bail!("Invalid property name: {}", property)
|
||||
}
|
||||
} else {
|
||||
bail!("Expected `field_name` to be `Constant::Str`")
|
||||
}
|
||||
} else {
|
||||
bail!("Expected `elts` to have exactly two elements")
|
||||
}
|
||||
} else {
|
||||
bail!("Expected `field` to be `ExprKind::Tuple`")
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
bail!("Expected argument to be `ExprKind::List`")
|
||||
}
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a `StmtKind:ClassDef` statement based on the provided body and
|
||||
/// keywords.
|
||||
fn create_class_def_stmt(typename: &str, body: Vec<Stmt>, base_class: &ExprKind) -> Stmt {
|
||||
Stmt::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
StmtKind::ClassDef {
|
||||
name: typename.to_string(),
|
||||
bases: vec![Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
base_class.clone(),
|
||||
)],
|
||||
keywords: vec![],
|
||||
body,
|
||||
decorator_list: vec![],
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn convert_to_class(
|
||||
stmt: &Stmt,
|
||||
typename: &str,
|
||||
body: Vec<Stmt>,
|
||||
base_class: &ExprKind,
|
||||
) -> Result<Fix> {
|
||||
let mut generator = SourceGenerator::new();
|
||||
generator.unparse_stmt(&create_class_def_stmt(typename, body, base_class))?;
|
||||
let content = generator.generate()?;
|
||||
Ok(Fix::replacement(
|
||||
content,
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// U014
|
||||
pub fn convert_named_tuple_functional_to_class(
|
||||
checker: &mut Checker,
|
||||
stmt: &Stmt,
|
||||
targets: &[Expr],
|
||||
value: &Expr,
|
||||
) {
|
||||
if let Some((typename, args, keywords, base_class)) =
|
||||
match_named_tuple_assign(checker, targets, value)
|
||||
{
|
||||
match match_defaults(keywords) {
|
||||
Ok(defaults) => {
|
||||
if let Ok(properties) = create_properties_from_args(args, defaults) {
|
||||
let mut check = Check::new(
|
||||
CheckKind::ConvertNamedTupleFunctionalToClass(typename.to_string()),
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
match convert_to_class(stmt, typename, properties, base_class) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(err) => error!("Failed to convert `NamedTuple`: {}", err),
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
Err(err) => error!("Failed to parse defaults: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +230,7 @@ pub fn convert_typed_dict_functional_to_class(
|
||||
Err(err) => error!("Failed to parse TypedDict: {}", err),
|
||||
Ok((body, total_keyword)) => {
|
||||
let mut check = Check::new(
|
||||
CheckKind::ConvertTypedDictFunctionalToClass,
|
||||
CheckKind::ConvertTypedDictFunctionalToClass(class_name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub use convert_named_tuple_functional_to_class::convert_named_tuple_functional_to_class;
|
||||
pub use convert_typed_dict_functional_to_class::convert_typed_dict_functional_to_class;
|
||||
pub use deprecated_unittest_alias::deprecated_unittest_alias;
|
||||
pub use super_call_with_parameters::super_call_with_parameters;
|
||||
@@ -10,6 +11,7 @@ pub use use_pep604_annotation::use_pep604_annotation;
|
||||
pub use useless_metaclass_type::useless_metaclass_type;
|
||||
pub use useless_object_inheritance::useless_object_inheritance;
|
||||
|
||||
mod convert_named_tuple_functional_to_class;
|
||||
mod convert_typed_dict_functional_to_class;
|
||||
mod deprecated_unittest_alias;
|
||||
mod super_call_with_parameters;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use rustpython_parser::ast::Expr;
|
||||
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::pyupgrade::{checks, fixes};
|
||||
use crate::pyupgrade::checks;
|
||||
|
||||
/// U011
|
||||
pub fn unnecessary_lru_cache_params(checker: &mut Checker, decorator_list: &[Expr]) {
|
||||
@@ -12,11 +13,7 @@ pub fn unnecessary_lru_cache_params(checker: &mut Checker, decorator_list: &[Exp
|
||||
&checker.import_aliases,
|
||||
) {
|
||||
if checker.patch(check.kind.code()) {
|
||||
if let Some(fix) =
|
||||
fixes::remove_unnecessary_lru_cache_params(checker.locator, &check.location)
|
||||
{
|
||||
check.amend(fix);
|
||||
}
|
||||
check.amend(Fix::deletion(check.location, check.end_location));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 33
|
||||
column: 25
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: foo.bar = None
|
||||
location:
|
||||
row: 33
|
||||
column: 0
|
||||
end_location:
|
||||
row: 33
|
||||
column: 25
|
||||
applied: false
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 34
|
||||
@@ -17,7 +26,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 34
|
||||
column: 29
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: foo._123abc = None
|
||||
location:
|
||||
row: 34
|
||||
column: 0
|
||||
end_location:
|
||||
row: 34
|
||||
column: 29
|
||||
applied: false
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 35
|
||||
@@ -25,7 +43,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 35
|
||||
column: 28
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: foo.abc123 = None
|
||||
location:
|
||||
row: 35
|
||||
column: 0
|
||||
end_location:
|
||||
row: 35
|
||||
column: 28
|
||||
applied: false
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 36
|
||||
@@ -33,5 +60,31 @@ expression: checks
|
||||
end_location:
|
||||
row: 36
|
||||
column: 29
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: foo.abc123 = None
|
||||
location:
|
||||
row: 36
|
||||
column: 0
|
||||
end_location:
|
||||
row: 36
|
||||
column: 29
|
||||
applied: false
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 37
|
||||
column: 0
|
||||
end_location:
|
||||
row: 37
|
||||
column: 30
|
||||
fix:
|
||||
patch:
|
||||
content: foo.bar.baz = None
|
||||
location:
|
||||
row: 37
|
||||
column: 0
|
||||
end_location:
|
||||
row: 37
|
||||
column: 30
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ expression: checks
|
||||
- kind: UnnecessaryLRUCacheParams
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
column: 10
|
||||
end_location:
|
||||
row: 5
|
||||
column: 12
|
||||
@@ -22,7 +22,7 @@ expression: checks
|
||||
- kind: UnnecessaryLRUCacheParams
|
||||
location:
|
||||
row: 11
|
||||
column: 1
|
||||
column: 20
|
||||
end_location:
|
||||
row: 11
|
||||
column: 22
|
||||
@@ -39,7 +39,7 @@ expression: checks
|
||||
- kind: UnnecessaryLRUCacheParams
|
||||
location:
|
||||
row: 16
|
||||
column: 1
|
||||
column: 10
|
||||
end_location:
|
||||
row: 16
|
||||
column: 24
|
||||
@@ -56,7 +56,7 @@ expression: checks
|
||||
- kind: UnnecessaryLRUCacheParams
|
||||
location:
|
||||
row: 21
|
||||
column: 1
|
||||
column: 20
|
||||
end_location:
|
||||
row: 21
|
||||
column: 34
|
||||
@@ -73,7 +73,7 @@ expression: checks
|
||||
- kind: UnnecessaryLRUCacheParams
|
||||
location:
|
||||
row: 27
|
||||
column: 1
|
||||
column: 10
|
||||
end_location:
|
||||
row: 28
|
||||
column: 1
|
||||
@@ -90,7 +90,7 @@ expression: checks
|
||||
- kind: UnnecessaryLRUCacheParams
|
||||
location:
|
||||
row: 33
|
||||
column: 1
|
||||
column: 10
|
||||
end_location:
|
||||
row: 35
|
||||
column: 1
|
||||
@@ -107,7 +107,7 @@ expression: checks
|
||||
- kind: UnnecessaryLRUCacheParams
|
||||
location:
|
||||
row: 40
|
||||
column: 1
|
||||
column: 20
|
||||
end_location:
|
||||
row: 42
|
||||
column: 19
|
||||
@@ -124,7 +124,7 @@ expression: checks
|
||||
- kind: UnnecessaryLRUCacheParams
|
||||
location:
|
||||
row: 47
|
||||
column: 1
|
||||
column: 20
|
||||
end_location:
|
||||
row: 51
|
||||
column: 1
|
||||
@@ -141,7 +141,7 @@ expression: checks
|
||||
- kind: UnnecessaryLRUCacheParams
|
||||
location:
|
||||
row: 56
|
||||
column: 1
|
||||
column: 20
|
||||
end_location:
|
||||
row: 62
|
||||
column: 1
|
||||
@@ -158,7 +158,7 @@ expression: checks
|
||||
- kind: UnnecessaryLRUCacheParams
|
||||
location:
|
||||
row: 67
|
||||
column: 1
|
||||
column: 20
|
||||
end_location:
|
||||
row: 72
|
||||
column: 1
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: ConvertTypedDictFunctionalToClass
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType1
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
@@ -19,7 +20,8 @@ expression: checks
|
||||
row: 5
|
||||
column: 52
|
||||
applied: false
|
||||
- kind: ConvertTypedDictFunctionalToClass
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType2
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
@@ -36,7 +38,8 @@ expression: checks
|
||||
row: 8
|
||||
column: 50
|
||||
applied: false
|
||||
- kind: ConvertTypedDictFunctionalToClass
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType3
|
||||
location:
|
||||
row: 11
|
||||
column: 0
|
||||
@@ -53,7 +56,8 @@ expression: checks
|
||||
row: 11
|
||||
column: 44
|
||||
applied: false
|
||||
- kind: ConvertTypedDictFunctionalToClass
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType4
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
@@ -70,7 +74,8 @@ expression: checks
|
||||
row: 14
|
||||
column: 30
|
||||
applied: false
|
||||
- kind: ConvertTypedDictFunctionalToClass
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType5
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
@@ -87,7 +92,8 @@ expression: checks
|
||||
row: 17
|
||||
column: 46
|
||||
applied: false
|
||||
- kind: ConvertTypedDictFunctionalToClass
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType6
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
@@ -104,7 +110,8 @@ expression: checks
|
||||
row: 18
|
||||
column: 41
|
||||
applied: false
|
||||
- kind: ConvertTypedDictFunctionalToClass
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType7
|
||||
location:
|
||||
row: 21
|
||||
column: 0
|
||||
@@ -121,7 +128,8 @@ expression: checks
|
||||
row: 21
|
||||
column: 56
|
||||
applied: false
|
||||
- kind: ConvertTypedDictFunctionalToClass
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType8
|
||||
location:
|
||||
row: 24
|
||||
column: 0
|
||||
@@ -138,7 +146,8 @@ expression: checks
|
||||
row: 24
|
||||
column: 65
|
||||
applied: false
|
||||
- kind: ConvertTypedDictFunctionalToClass
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType10
|
||||
location:
|
||||
row: 30
|
||||
column: 0
|
||||
@@ -155,7 +164,8 @@ expression: checks
|
||||
row: 30
|
||||
column: 59
|
||||
applied: false
|
||||
- kind: ConvertTypedDictFunctionalToClass
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType11
|
||||
location:
|
||||
row: 33
|
||||
column: 0
|
||||
|
||||
59
src/snapshots/ruff__linter__tests__U014_U014.py.snap
Normal file
59
src/snapshots/ruff__linter__tests__U014_U014.py.snap
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
ConvertNamedTupleFunctionalToClass: NT1
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 61
|
||||
fix:
|
||||
patch:
|
||||
content: "class NT1(NamedTuple):\n a: int\n b: tuple[str, ...]"
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 61
|
||||
applied: false
|
||||
- kind:
|
||||
ConvertNamedTupleFunctionalToClass: NT2
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 1
|
||||
fix:
|
||||
patch:
|
||||
content: "class NT2(NamedTuple):\n a: int\n b: str = 'foo'\n c: list[bool] = [True]"
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 1
|
||||
applied: false
|
||||
- kind:
|
||||
ConvertNamedTupleFunctionalToClass: NT3
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 15
|
||||
column: 56
|
||||
fix:
|
||||
patch:
|
||||
content: "class NT3(typing.NamedTuple):\n a: int\n b: str"
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 15
|
||||
column: 56
|
||||
applied: false
|
||||
|
||||
Reference in New Issue
Block a user