Automatically fix a variety of comprehension rules (#553)

This commit is contained in:
Charlie Marsh 2022-11-02 20:39:35 -04:00 committed by GitHub
parent f50ff61056
commit 9948be0145
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 639 additions and 44 deletions

View File

@ -392,17 +392,17 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| Code | Name | Message | Fix | | Code | Name | Message | Fix |
| ---- | ---- | ------- | --- | | ---- | ---- | ------- | --- |
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | | | C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | | | C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | | | C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | | | C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | | | C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | |
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | | | C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | 🛠 |
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | | | C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | |
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | | | C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | | | C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | | | C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | |
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | | | C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | | | C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | | | C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | | | C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
@ -521,7 +521,7 @@ including:
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) - [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34) - [`pyupgrade`](https://pypi.org/project/pyupgrade/) (9/34)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7) - [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8: Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
@ -545,7 +545,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa), Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34). and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (9/34).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue. If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.

View File

@ -989,7 +989,12 @@ where
// flake8-comprehensions // flake8-comprehensions
if self.settings.enabled.contains(&CheckCode::C400) { if self.settings.enabled.contains(&CheckCode::C400) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_list( if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_list(
expr, func, args, keywords, expr,
func,
args,
keywords,
self.locator,
self.patch(),
) { ) {
self.checks.push(check); self.checks.push(check);
}; };
@ -997,7 +1002,12 @@ where
if self.settings.enabled.contains(&CheckCode::C401) { if self.settings.enabled.contains(&CheckCode::C401) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_set( if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_set(
expr, func, args, keywords, expr,
func,
args,
keywords,
self.locator,
self.patch(),
) { ) {
self.checks.push(check); self.checks.push(check);
}; };
@ -1014,7 +1024,12 @@ where
if self.settings.enabled.contains(&CheckCode::C403) { if self.settings.enabled.contains(&CheckCode::C403) {
if let Some(check) = if let Some(check) =
flake8_comprehensions::checks::unnecessary_list_comprehension_set( flake8_comprehensions::checks::unnecessary_list_comprehension_set(
expr, func, args, keywords, expr,
func,
args,
keywords,
self.locator,
self.patch(),
) )
{ {
self.checks.push(check); self.checks.push(check);
@ -1033,7 +1048,12 @@ where
if self.settings.enabled.contains(&CheckCode::C405) { if self.settings.enabled.contains(&CheckCode::C405) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_set( if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_set(
expr, func, args, keywords, expr,
func,
args,
keywords,
self.locator,
self.patch(),
) { ) {
self.checks.push(check); self.checks.push(check);
}; };
@ -1049,7 +1069,12 @@ where
if self.settings.enabled.contains(&CheckCode::C408) { if self.settings.enabled.contains(&CheckCode::C408) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_collection_call( if let Some(check) = flake8_comprehensions::checks::unnecessary_collection_call(
expr, func, args, keywords, expr,
func,
args,
keywords,
self.locator,
self.patch(),
) { ) {
self.checks.push(check); self.checks.push(check);
}; };
@ -1076,9 +1101,13 @@ where
} }
if self.settings.enabled.contains(&CheckCode::C411) { if self.settings.enabled.contains(&CheckCode::C411) {
if let Some(check) = if let Some(check) = flake8_comprehensions::checks::unnecessary_list_call(
flake8_comprehensions::checks::unnecessary_list_call(expr, func, args) expr,
{ func,
args,
self.locator,
self.patch(),
) {
self.checks.push(check); self.checks.push(check);
}; };
} }

View File

@ -1469,6 +1469,12 @@ impl CheckKind {
| CheckKind::SuperCallWithParameters | CheckKind::SuperCallWithParameters
| CheckKind::TypeOfPrimitive(_) | CheckKind::TypeOfPrimitive(_)
| CheckKind::UnnecessaryAbspath | CheckKind::UnnecessaryAbspath
| CheckKind::UnnecessaryCollectionCall(_)
| CheckKind::UnnecessaryGeneratorList
| CheckKind::UnnecessaryGeneratorSet
| CheckKind::UnnecessaryListCall
| CheckKind::UnnecessaryListComprehensionSet
| CheckKind::UnnecessaryLiteralSet(_)
| CheckKind::UnusedImport(_, false) | CheckKind::UnusedImport(_, false)
| CheckKind::UnusedLoopControlVariable(_) | CheckKind::UnusedLoopControlVariable(_)
| CheckKind::UnusedNOQA(_) | CheckKind::UnusedNOQA(_)

View File

@ -1,3 +1,4 @@
use log::error;
use num_bigint::BigInt; use num_bigint::BigInt;
use rustpython_ast::{ use rustpython_ast::{
Comprehension, Constant, Expr, ExprKind, Keyword, KeywordData, Located, Unaryop, Comprehension, Constant, Expr, ExprKind, Keyword, KeywordData, Located, Unaryop,
@ -5,6 +6,8 @@ use rustpython_ast::{
use crate::ast::types::Range; use crate::ast::types::Range;
use crate::checks::{Check, CheckKind}; use crate::checks::{Check, CheckKind};
use crate::flake8_comprehensions::fixes;
use crate::source_code_locator::SourceCodeLocator;
fn function_name(func: &Expr) -> Option<&str> { fn function_name(func: &Expr) -> Option<&str> {
if let ExprKind::Name { id, .. } = &func.node { if let ExprKind::Name { id, .. } = &func.node {
@ -49,13 +52,22 @@ pub fn unnecessary_generator_list(
func: &Expr, func: &Expr,
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
) -> Option<Check> { ) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("list", func, args, keywords)?; let argument = exactly_one_argument_with_matching_function("list", func, args, keywords)?;
if let ExprKind::GeneratorExp { .. } = argument { if let ExprKind::GeneratorExp { .. } = argument {
return Some(Check::new( let mut check = Check::new(
CheckKind::UnnecessaryGeneratorList, CheckKind::UnnecessaryGeneratorList,
Range::from_located(expr), Range::from_located(expr),
)); );
if fix {
match fixes::fix_unnecessary_generator_list(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
} }
None None
} }
@ -66,13 +78,22 @@ pub fn unnecessary_generator_set(
func: &Expr, func: &Expr,
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
) -> Option<Check> { ) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?; let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
if let ExprKind::GeneratorExp { .. } = argument { if let ExprKind::GeneratorExp { .. } = argument {
return Some(Check::new( let mut check = Check::new(
CheckKind::UnnecessaryGeneratorSet, CheckKind::UnnecessaryGeneratorSet,
Range::from_located(expr), Range::from_located(expr),
)); );
if fix {
match fixes::fix_unnecessary_generator_set(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
} }
None None
} }
@ -105,13 +126,22 @@ pub fn unnecessary_list_comprehension_set(
func: &Expr, func: &Expr,
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
) -> Option<Check> { ) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?; let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
if let ExprKind::ListComp { .. } = &argument { if let ExprKind::ListComp { .. } = &argument {
return Some(Check::new( let mut check = Check::new(
CheckKind::UnnecessaryListComprehensionSet, CheckKind::UnnecessaryListComprehensionSet,
Range::from_located(expr), Range::from_located(expr),
)); );
if fix {
match fixes::fix_unnecessary_list_comprehension_set(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
} }
None None
} }
@ -144,6 +174,8 @@ pub fn unnecessary_literal_set(
func: &Expr, func: &Expr,
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
) -> Option<Check> { ) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?; let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
let kind = match argument { let kind = match argument {
@ -151,10 +183,17 @@ pub fn unnecessary_literal_set(
ExprKind::Tuple { .. } => "tuple", ExprKind::Tuple { .. } => "tuple",
_ => return None, _ => return None,
}; };
Some(Check::new( let mut check = Check::new(
CheckKind::UnnecessaryLiteralSet(kind.to_string()), CheckKind::UnnecessaryLiteralSet(kind.to_string()),
Range::from_located(expr), Range::from_located(expr),
)) );
if fix {
match fixes::fix_unnecessary_literal_set(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
Some(check)
} }
/// C406 (`dict([(1, 2)])`) /// C406 (`dict([(1, 2)])`)
@ -190,22 +229,36 @@ pub fn unnecessary_collection_call(
func: &Expr, func: &Expr,
args: &[Expr], args: &[Expr],
keywords: &[Located<KeywordData>], keywords: &[Located<KeywordData>],
locator: &SourceCodeLocator,
fix: bool,
) -> Option<Check> { ) -> Option<Check> {
if !args.is_empty() { if !args.is_empty() {
return None; return None;
} }
let id = function_name(func)?; let id = function_name(func)?;
match id { match id {
"dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => (), "dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => {
// `dict()` or `dict(a=1)` (as opposed to `dict(**a)`)
}
"list" | "tuple" => { "list" | "tuple" => {
// list() or tuple() // `list()` or `tuple()`
} }
_ => return None, _ => return None,
}; };
Some(Check::new( let mut check = Check::new(
CheckKind::UnnecessaryCollectionCall(id.to_string()), CheckKind::UnnecessaryCollectionCall(id.to_string()),
Range::from_located(expr), Range::from_located(expr),
)) );
if fix {
// TODO(charlie): Support fixing `dict(a=1)`.
if keywords.is_empty() {
match fixes::fix_unnecessary_collection_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
}
Some(check)
} }
/// C409 /// C409
@ -245,13 +298,23 @@ pub fn unnecessary_literal_within_list_call(
} }
/// C411 /// C411
pub fn unnecessary_list_call(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> { pub fn unnecessary_list_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
locator: &SourceCodeLocator,
fix: bool,
) -> Option<Check> {
let argument = first_argument_with_matching_function("list", func, args)?; let argument = first_argument_with_matching_function("list", func, args)?;
if let ExprKind::ListComp { .. } = argument { if let ExprKind::ListComp { .. } = argument {
return Some(Check::new( let mut check = Check::new(CheckKind::UnnecessaryListCall, Range::from_located(expr));
CheckKind::UnnecessaryListCall, if fix {
Range::from_located(expr), match fixes::fix_unnecessary_list_call(locator, expr) {
)); Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
} }
None None
} }

View File

@ -0,0 +1,397 @@
use anyhow::Result;
use libcst_native::{
Arg, Codegen, Dict, Expression, LeftCurlyBrace, LeftSquareBracket, List, ListComp,
RightCurlyBrace, RightSquareBracket, Set, SetComp, SmallStatement, Statement, Tuple,
};
use rustpython_ast::Expr;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::source_code_locator::SourceCodeLocator;
/// (C400) Convert `list(x for x in y)` to `[x for x in y]`.
pub fn fix_unnecessary_generator_list(locator: &SourceCodeLocator, expr: &Expr) -> Result<Fix> {
// Module(SimpleStatementLine(Expr(Call(GeneratorExp)))) ->
// Module(SimpleStatementLine(Expr(ListComp)))
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(expr)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::Expr."
));
};
let call = if let Expression::Call(call) = &mut body.value {
call
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Call."));
};
let arg = if let Some(Arg { value, .. }) = call.args.first_mut() {
value
} else {
return Err(anyhow::anyhow!("Expected node to be: Arg."));
};
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg {
generator_exp
} else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp."
));
};
body.value = Expression::ListComp(Box::new(ListComp {
elt: generator_exp.elt.clone(),
for_in: generator_exp.for_in.clone(),
lbracket: LeftSquareBracket {
whitespace_after: call.whitespace_before_args.clone(),
},
rbracket: RightSquareBracket {
whitespace_before: call.whitespace_after_func.clone(),
},
lpar: generator_exp.lpar.clone(),
rpar: generator_exp.rpar.clone(),
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C401) Convert `set(x for x in y)` to `{x for x in y}`.
pub fn fix_unnecessary_generator_set(locator: &SourceCodeLocator, expr: &Expr) -> Result<Fix> {
// Module(SimpleStatementLine(Expr(Call(GeneratorExp)))) ->
// Module(SimpleStatementLine(Expr(SetComp)))
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(expr)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::Expr."
));
};
let call = if let Expression::Call(call) = &mut body.value {
call
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Call."));
};
let arg = if let Some(Arg { value, .. }) = call.args.first_mut() {
value
} else {
return Err(anyhow::anyhow!("Expected node to be: Arg."));
};
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg {
generator_exp
} else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp."
));
};
body.value = Expression::SetComp(Box::new(SetComp {
elt: generator_exp.elt.clone(),
for_in: generator_exp.for_in.clone(),
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: call.whitespace_after_func.clone(),
},
lpar: generator_exp.lpar.clone(),
rpar: generator_exp.rpar.clone(),
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C403) Convert `set([x for x in y])` to `{x for x in y}`.
pub fn fix_unnecessary_list_comprehension_set(
locator: &SourceCodeLocator,
expr: &Expr,
) -> Result<Fix> {
// Module(SimpleStatementLine(Expr(Call(ListComp)))) ->
// Module(SimpleStatementLine(Expr(SetComp)))
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(expr)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::Expr."
));
};
let call = if let Expression::Call(call) = &mut body.value {
call
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Call."));
};
let arg = if let Some(Arg { value, .. }) = call.args.first_mut() {
value
} else {
return Err(anyhow::anyhow!("Expected node to be: Arg."));
};
let list_comp = if let Expression::ListComp(list_comp) = arg {
list_comp
} else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::ListComp."
));
};
body.value = Expression::SetComp(Box::new(SetComp {
elt: list_comp.elt.clone(),
for_in: list_comp.for_in.clone(),
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: call.whitespace_after_func.clone(),
},
lpar: list_comp.lpar.clone(),
rpar: list_comp.rpar.clone(),
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C405) Convert `set((1, 2))` to `{1, 2}`.
pub fn fix_unnecessary_literal_set(locator: &SourceCodeLocator, expr: &Expr) -> Result<Fix> {
// Module(SimpleStatementLine(Expr(Call(List|Tuple)))) ->
// Module(SimpleStatementLine(Expr(Set)))
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(expr)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::Expr."
));
};
let call = if let Expression::Call(call) = &mut body.value {
call
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Call."));
};
let arg = if let Some(Arg { value, .. }) = call.args.first_mut() {
value
} else {
return Err(anyhow::anyhow!("Expected node to be: Arg."));
};
let elements = match arg {
Expression::Tuple(inner) => inner.elements.clone(),
Expression::List(inner) => inner.elements.clone(),
_ => {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::Tuple | Expression::List."
))
}
};
if elements.is_empty() {
call.args = vec![];
} else {
body.value = Expression::Set(Box::new(Set {
elements,
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: call.whitespace_after_func.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
}));
}
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C408)
pub fn fix_unnecessary_collection_call(locator: &SourceCodeLocator, expr: &Expr) -> Result<Fix> {
// Module(SimpleStatementLine(Expr(Call("list" | "tuple" | "dict")))) ->
// Module(SimpleStatementLine(Expr(List|Tuple|Dict)))
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(expr)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::Expr."
));
};
let call = if let Expression::Call(call) = &body.value {
call
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Call."));
};
let name = if let Expression::Name(name) = &call.func.as_ref() {
name
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Name."));
};
match name.value {
"tuple" => {
body.value = Expression::Tuple(Box::new(Tuple {
elements: vec![],
lpar: vec![Default::default()],
rpar: vec![Default::default()],
}));
}
"list" => {
body.value = Expression::List(Box::new(List {
elements: vec![],
lbracket: Default::default(),
rbracket: Default::default(),
lpar: vec![],
rpar: vec![],
}));
}
"dict" => {
body.value = Expression::Dict(Box::new(Dict {
elements: vec![],
lbrace: Default::default(),
rbrace: Default::default(),
lpar: vec![],
rpar: vec![],
}));
}
_ => {
return Err(anyhow::anyhow!("Expected function name to be one of: \
'tuple', 'list', 'dict'."
.to_string()));
}
};
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C411) Convert `list([i for i in x])` to `[i for i in x]`.
pub fn fix_unnecessary_list_call(locator: &SourceCodeLocator, expr: &Expr) -> Result<Fix> {
// Module(SimpleStatementLine(Expr(Call(List|Tuple)))) ->
// Module(SimpleStatementLine(Expr(List|Tuple)))
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(expr)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::Expr."
));
};
let call = if let Expression::Call(call) = &mut body.value {
call
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Call."));
};
let arg = if let Some(Arg { value, .. }) = call.args.first_mut() {
value
} else {
return Err(anyhow::anyhow!("Expected node to be: Arg."));
};
body.value = arg.clone();
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}

View File

@ -1 +1,2 @@
pub mod checks; pub mod checks;
mod fixes;

View File

@ -9,5 +9,14 @@ expression: checks
end_location: end_location:
row: 1 row: 1
column: 29 column: 29
fix: ~ fix:
patch:
content: "[x for x in range(3)]"
location:
row: 1
column: 4
end_location:
row: 1
column: 29
applied: false

View File

@ -9,5 +9,14 @@ expression: checks
end_location: end_location:
row: 1 row: 1
column: 28 column: 28
fix: ~ fix:
patch:
content: "{x for x in range(3)}"
location:
row: 1
column: 4
end_location:
row: 1
column: 28
applied: false

View File

@ -9,5 +9,14 @@ expression: checks
end_location: end_location:
row: 1 row: 1
column: 30 column: 30
fix: ~ fix:
patch:
content: "{x for x in range(3)}"
location:
row: 1
column: 4
end_location:
row: 1
column: 30
applied: false

View File

@ -10,7 +10,16 @@ expression: checks
end_location: end_location:
row: 1 row: 1
column: 16 column: 16
fix: ~ fix:
patch:
content: "{1, 2}"
location:
row: 1
column: 5
end_location:
row: 1
column: 16
applied: false
- kind: - kind:
UnnecessaryLiteralSet: tuple UnnecessaryLiteralSet: tuple
location: location:
@ -19,7 +28,16 @@ expression: checks
end_location: end_location:
row: 2 row: 2
column: 16 column: 16
fix: ~ fix:
patch:
content: "{1, 2}"
location:
row: 2
column: 5
end_location:
row: 2
column: 16
applied: false
- kind: - kind:
UnnecessaryLiteralSet: list UnnecessaryLiteralSet: list
location: location:
@ -28,7 +46,16 @@ expression: checks
end_location: end_location:
row: 3 row: 3
column: 12 column: 12
fix: ~ fix:
patch:
content: set()
location:
row: 3
column: 5
end_location:
row: 3
column: 12
applied: false
- kind: - kind:
UnnecessaryLiteralSet: tuple UnnecessaryLiteralSet: tuple
location: location:
@ -37,5 +64,14 @@ expression: checks
end_location: end_location:
row: 4 row: 4
column: 12 column: 12
fix: ~ fix:
patch:
content: set()
location:
row: 4
column: 5
end_location:
row: 4
column: 12
applied: false

View File

@ -10,7 +10,16 @@ expression: checks
end_location: end_location:
row: 1 row: 1
column: 11 column: 11
fix: ~ fix:
patch:
content: ()
location:
row: 1
column: 4
end_location:
row: 1
column: 11
applied: false
- kind: - kind:
UnnecessaryCollectionCall: list UnnecessaryCollectionCall: list
location: location:
@ -19,7 +28,16 @@ expression: checks
end_location: end_location:
row: 2 row: 2
column: 10 column: 10
fix: ~ fix:
patch:
content: "[]"
location:
row: 2
column: 4
end_location:
row: 2
column: 10
applied: false
- kind: - kind:
UnnecessaryCollectionCall: dict UnnecessaryCollectionCall: dict
location: location:
@ -28,7 +46,16 @@ expression: checks
end_location: end_location:
row: 3 row: 3
column: 11 column: 11
fix: ~ fix:
patch:
content: "{}"
location:
row: 3
column: 5
end_location:
row: 3
column: 11
applied: false
- kind: - kind:
UnnecessaryCollectionCall: dict UnnecessaryCollectionCall: dict
location: location:

View File

@ -9,5 +9,14 @@ expression: checks
end_location: end_location:
row: 2 row: 2
column: 20 column: 20
fix: ~ fix:
patch:
content: "[i for i in x]"
location:
row: 2
column: 0
end_location:
row: 2
column: 20
applied: false