Compare commits

..

12 Commits

Author SHA1 Message Date
Charlie Marsh
e00bcd19f5 Bump version to 0.0.97 2022-11-02 22:38:43 -04:00
Charlie Marsh
4550581be2 Relax lowercase condition in N806 (#562) 2022-11-02 22:38:29 -04:00
Charlie Marsh
b42d77a4c6 Avoid autofixes for errors in f-strings (#561) 2022-11-02 22:31:57 -04:00
Charlie Marsh
e473df1fe9 Bump version to 0.0.96 2022-11-02 22:10:56 -04:00
Charlie Marsh
d448281b33 Add plugin properties to settings cache key (#559) 2022-11-02 22:10:01 -04:00
Charlie Marsh
94597fefc1 Change flake8-quotes default to double quotes (#558) 2022-11-02 22:03:54 -04:00
Charlie Marsh
add0bdeeb7 DRY up utilities in flake8_comprehensions/fixes.rs (#556) 2022-11-02 22:00:53 -04:00
Charlie Marsh
6a180b95d1 Implement autofix for dict and tuple comprehensions (#555) 2022-11-02 21:36:20 -04:00
Charlie Marsh
416c338237 Respect trailing whitespace in comprehension fixes (#554) 2022-11-02 21:08:17 -04:00
Charlie Marsh
9948be0145 Automatically fix a variety of comprehension rules (#553) 2022-11-02 20:39:35 -04:00
Charlie Marsh
f50ff61056 Expose autofix mode in public API (#551) 2022-11-02 09:44:46 -04:00
Charlie Marsh
017fec2bc5 Set override in actions-rs/toolchain@v1 (#543) 2022-11-02 09:36:22 -04:00
45 changed files with 1132 additions and 176 deletions

View File

@@ -38,6 +38,7 @@ jobs:
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
components: rustfmt
- uses: actions/cache@v3
env:
@@ -62,6 +63,7 @@ jobs:
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
components: clippy
- uses: actions/cache@v3
env:
@@ -86,6 +88,7 @@ jobs:
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
- uses: actions/cache@v3
env:
cache-name: cache-cargo
@@ -109,6 +112,7 @@ jobs:
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
- uses: actions/setup-python@v4
with:
python-version: "3.10"

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.95
rev: v0.0.97
hooks:
- id: ruff

4
Cargo.lock generated
View File

@@ -920,7 +920,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.95-dev.0"
version = "0.0.97-dev.0"
dependencies = [
"anyhow",
"clap 4.0.15",
@@ -2211,7 +2211,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.95"
version = "0.0.97"
dependencies = [
"anyhow",
"assert_cmd",

View File

@@ -5,7 +5,7 @@ members = [
[package]
name = "ruff"
version = "0.0.95"
version = "0.0.97"
edition = "2021"
[lib]

View File

@@ -89,7 +89,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.95
rev: v0.0.97
hooks:
- id: ruff
```
@@ -392,17 +392,17 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | |
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` 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) | |
| 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()`) | |
| 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()`) | |
| 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()`) | 🛠 |
| 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()`) | 🛠 |
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
| 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)()` | |
@@ -521,7 +521,7 @@ including:
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
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)
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/) (10/34).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.95"
version = "0.0.97"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.95"
version = "0.0.97"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.95-dev.0"
version = "0.0.97-dev.0"
edition = "2021"
[lib]

View File

@@ -1 +1,4 @@
x = list(x for x in range(3))
x = list(
x for x in range(3)
)

View File

@@ -1 +1,4 @@
x = set(x for x in range(3))
x = set(
x for x in range(3)
)

View File

@@ -1,2 +1,5 @@
dict((x, x) for x in range(3))
dict(
(x, x) for x in range(3)
)
dict(((x, x) for x in range(3)), z=3)

View File

@@ -1 +1,4 @@
s = set([x for x in range(3)])
s = set(
[x for x in range(3)]
)

View File

@@ -1,3 +1,10 @@
t1 = tuple([1, 2])
t2 = tuple((1, 2))
t3 = tuple([])
t1 = tuple([])
t2 = tuple([1, 2])
t3 = tuple((1, 2))
t4 = tuple([
1,
2
])
t5 = tuple(
(1, 2)
)

View File

@@ -2,3 +2,4 @@ def f():
lower = 0
Camel = 0
CONSTANT = 0
_ = 0

1
rust-toolchain Normal file
View File

@@ -0,0 +1 @@
1.64.0

View File

@@ -1,2 +0,0 @@
[toolchain]
channel = "1.64.0"

View File

@@ -6,6 +6,8 @@ use rustpython_parser::ast::Location;
use crate::autofix::{Fix, Patch};
use crate::checks::Check;
// TODO(charlie): The model here is awkward because `Apply` is only relevant at
// higher levels in the execution flow.
#[derive(Hash)]
pub enum Mode {
Generate,

View File

@@ -129,7 +129,9 @@ impl<'a> Checker<'a> {
/// Return `true` if a patch should be generated under the given autofix
/// `Mode`.
pub fn patch(&self) -> bool {
self.autofix.patch()
// TODO(charlie): We can't fix errors in f-strings until RustPython adds
// location data.
self.autofix.patch() && self.in_f_string.is_none()
}
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
@@ -989,7 +991,13 @@ where
// flake8-comprehensions
if self.settings.enabled.contains(&CheckCode::C400) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_list(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -997,7 +1005,13 @@ where
if self.settings.enabled.contains(&CheckCode::C401) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_set(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -1005,7 +1019,13 @@ where
if self.settings.enabled.contains(&CheckCode::C402) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_dict(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -1014,7 +1034,13 @@ where
if self.settings.enabled.contains(&CheckCode::C403) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_list_comprehension_set(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1024,7 +1050,10 @@ where
if self.settings.enabled.contains(&CheckCode::C404) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_list_comprehension_dict(
expr, func, args, keywords,
func,
args,
keywords,
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1033,7 +1062,13 @@ where
if self.settings.enabled.contains(&CheckCode::C405) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_set(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -1041,7 +1076,10 @@ where
if self.settings.enabled.contains(&CheckCode::C406) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_dict(
expr, func, args, keywords,
func,
args,
keywords,
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -1049,7 +1087,13 @@ where
if self.settings.enabled.contains(&CheckCode::C408) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_collection_call(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -1058,7 +1102,12 @@ where
if self.settings.enabled.contains(&CheckCode::C409) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_literal_within_tuple_call(
expr, func, args,
expr,
func,
args,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1068,7 +1117,12 @@ where
if self.settings.enabled.contains(&CheckCode::C410) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_literal_within_list_call(
expr, func, args,
expr,
func,
args,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1076,9 +1130,14 @@ where
}
if self.settings.enabled.contains(&CheckCode::C411) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_list_call(expr, func, args)
{
if let Some(check) = flake8_comprehensions::checks::unnecessary_list_call(
expr,
func,
args,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
}
@@ -1086,7 +1145,9 @@ where
if self.settings.enabled.contains(&CheckCode::C413) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_call_around_sorted(
expr, func, args,
func,
args,
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1096,7 +1157,9 @@ where
if self.settings.enabled.contains(&CheckCode::C414) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_double_cast_or_process(
expr, func, args,
func,
args,
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1106,7 +1169,9 @@ where
if self.settings.enabled.contains(&CheckCode::C415) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_subscript_reversal(
expr, func, args,
func,
args,
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1114,9 +1179,11 @@ where
}
if self.settings.enabled.contains(&CheckCode::C417) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_map(expr, func, args)
{
if let Some(check) = flake8_comprehensions::checks::unnecessary_map(
func,
args,
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
}
@@ -1293,7 +1360,10 @@ where
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
if self.settings.enabled.contains(&CheckCode::C416) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_comprehension(
expr, elt, generators,
expr,
elt,
generators,
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};

View File

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

19
src/cst/matchers.rs Normal file
View File

@@ -0,0 +1,19 @@
use anyhow::Result;
use libcst_native::Module;
use rustpython_ast::Located;
use crate::ast::types::Range;
use crate::source_code_locator::SourceCodeLocator;
pub fn match_tree<'a, T>(
locator: &'a SourceCodeLocator,
located: &'a Located<T>,
) -> Result<Module<'a>> {
match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(located)),
None,
) {
Ok(module) => Ok(module),
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
}
}

View File

@@ -1 +1,2 @@
pub mod helpers;
pub mod matchers;

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -42,7 +42,10 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: &Optio
..
} = &test.node
{
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
let mut check = Check::new(
CheckKind::DoNotAssertFalse,
checker.locate_check(Range::from_located(test)),
);
if checker.patch() {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{ExprKind, Stmt, Withitem};
use crate::ast::helpers::match_name_or_attr;
use crate::ast::types::Range;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -17,7 +17,7 @@ pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[With
{
checker.add_check(Check::new(
CheckKind::NoAssertRaisesException,
Range::from_located(stmt),
checker.locate_check(Range::from_located(stmt)),
));
}
}

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Expr, ExprKind, Unaryop};
use crate::ast::types::Range;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -11,7 +11,7 @@ pub fn unary_prefix_increment(checker: &mut Checker, expr: &Expr, op: &Unaryop,
if matches!(op, Unaryop::UAdd) {
checker.add_check(Check::new(
CheckKind::UnaryPrefixIncrement,
Range::from_located(expr),
checker.locate_check(Range::from_located(expr)),
))
}
}

View File

@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use rustpython_ast::{Expr, ExprKind, Stmt};
use crate::ast::types::Range;
use crate::ast::types::{CheckLocator, Range};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::autofix::Fix;
@@ -64,7 +64,7 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body:
let mut check = Check::new(
CheckKind::UnusedLoopControlVariable(name.to_string()),
Range::from_located(expr),
checker.locate_check(Range::from_located(expr)),
);
if checker.patch() {
// Prefix the variable name with an underscore.

View File

@@ -1,3 +1,4 @@
use log::error;
use num_bigint::BigInt;
use rustpython_ast::{
Comprehension, Constant, Expr, ExprKind, Keyword, KeywordData, Located, Unaryop,
@@ -5,6 +6,8 @@ use rustpython_ast::{
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::flake8_comprehensions::fixes;
use crate::source_code_locator::SourceCodeLocator;
fn function_name(func: &Expr) -> Option<&str> {
if let ExprKind::Name { id, .. } = &func.node {
@@ -49,13 +52,20 @@ pub fn unnecessary_generator_list(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("list", func, args, keywords)?;
if let ExprKind::GeneratorExp { .. } = argument {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorList,
Range::from_located(expr),
));
let mut check = Check::new(CheckKind::UnnecessaryGeneratorList, location);
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
}
@@ -66,13 +76,20 @@ pub fn unnecessary_generator_set(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
if let ExprKind::GeneratorExp { .. } = argument {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorSet,
Range::from_located(expr),
));
let mut check = Check::new(CheckKind::UnnecessaryGeneratorSet, location);
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
}
@@ -83,15 +100,22 @@ pub fn unnecessary_generator_dict(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
if let ExprKind::GeneratorExp { elt, .. } = argument {
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorDict,
Range::from_located(expr),
));
let mut check = Check::new(CheckKind::UnnecessaryGeneratorDict, location);
if fix {
match fixes::fix_unnecessary_generator_dict(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
}
_ => {}
}
@@ -105,23 +129,30 @@ pub fn unnecessary_list_comprehension_set(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
if let ExprKind::ListComp { .. } = &argument {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionSet,
Range::from_located(expr),
));
let mut check = Check::new(CheckKind::UnnecessaryListComprehensionSet, location);
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
}
/// C404 (`dict([...])`)
pub fn unnecessary_list_comprehension_dict(
expr: &Expr,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
if let ExprKind::ListComp { elt, .. } = &argument {
@@ -129,7 +160,7 @@ pub fn unnecessary_list_comprehension_dict(
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionDict,
Range::from_located(expr),
location,
));
}
_ => {}
@@ -144,6 +175,9 @@ pub fn unnecessary_literal_set(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
let kind = match argument {
@@ -151,18 +185,22 @@ pub fn unnecessary_literal_set(
ExprKind::Tuple { .. } => "tuple",
_ => return None,
};
Some(Check::new(
CheckKind::UnnecessaryLiteralSet(kind.to_string()),
Range::from_located(expr),
))
let mut check = Check::new(CheckKind::UnnecessaryLiteralSet(kind.to_string()), location);
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)])`)
pub fn unnecessary_literal_dict(
expr: &Expr,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
let (kind, elts) = match argument {
@@ -180,7 +218,7 @@ pub fn unnecessary_literal_dict(
Some(Check::new(
CheckKind::UnnecessaryLiteralDict(kind.to_string()),
Range::from_located(expr),
location,
))
}
@@ -190,22 +228,37 @@ pub fn unnecessary_collection_call(
func: &Expr,
args: &[Expr],
keywords: &[Located<KeywordData>],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
if !args.is_empty() {
return None;
}
let id = function_name(func)?;
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() or tuple()
// `list()` or `tuple()`
}
_ => return None,
};
Some(Check::new(
let mut check = Check::new(
CheckKind::UnnecessaryCollectionCall(id.to_string()),
Range::from_located(expr),
))
location,
);
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
@@ -213,6 +266,9 @@ pub fn unnecessary_literal_within_tuple_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = first_argument_with_matching_function("tuple", func, args)?;
let argument_kind = match argument {
@@ -220,10 +276,17 @@ pub fn unnecessary_literal_within_tuple_call(
ExprKind::List { .. } => "list",
_ => return None,
};
Some(Check::new(
let mut check = Check::new(
CheckKind::UnnecessaryLiteralWithinTupleCall(argument_kind.to_string()),
Range::from_located(expr),
))
location,
);
if fix {
match fixes::fix_unnecessary_literal_within_tuple_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
Some(check)
}
/// C410
@@ -231,6 +294,9 @@ pub fn unnecessary_literal_within_list_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = first_argument_with_matching_function("list", func, args)?;
let argument_kind = match argument {
@@ -238,26 +304,48 @@ pub fn unnecessary_literal_within_list_call(
ExprKind::List { .. } => "list",
_ => return None,
};
Some(Check::new(
let mut check = Check::new(
CheckKind::UnnecessaryLiteralWithinListCall(argument_kind.to_string()),
Range::from_located(expr),
))
location,
);
if fix {
match fixes::fix_unnecessary_literal_within_list_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
Some(check)
}
/// 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,
location: Range,
) -> Option<Check> {
let argument = first_argument_with_matching_function("list", func, args)?;
if let ExprKind::ListComp { .. } = argument {
return Some(Check::new(
CheckKind::UnnecessaryListCall,
Range::from_located(expr),
));
let mut check = Check::new(CheckKind::UnnecessaryListCall, location);
if fix {
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
}
/// C413
pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
pub fn unnecessary_call_around_sorted(
func: &Expr,
args: &[Expr],
location: Range,
) -> Option<Check> {
let outer = function_name(func)?;
if !(outer == "list" || outer == "reversed") {
return None;
@@ -266,7 +354,7 @@ pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -
if function_name(func)? == "sorted" {
return Some(Check::new(
CheckKind::UnnecessaryCallAroundSorted(outer.to_string()),
Range::from_located(expr),
location,
));
}
}
@@ -275,19 +363,19 @@ pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -
/// C414
pub fn unnecessary_double_cast_or_process(
expr: &Expr,
func: &Expr,
args: &[Expr],
location: Range,
) -> Option<Check> {
let outer = function_name(func)?;
if !["list", "tuple", "set", "reversed", "sorted"].contains(&outer) {
return None;
}
fn new_check(inner: &str, outer: &str, expr: &Expr) -> Check {
fn new_check(inner: &str, outer: &str, location: Range) -> Check {
Check::new(
CheckKind::UnnecessaryDoubleCastOrProcess(inner.to_string(), outer.to_string()),
Range::from_located(expr),
location,
)
}
@@ -297,24 +385,28 @@ pub fn unnecessary_double_cast_or_process(
if (outer == "set" || outer == "sorted")
&& (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "sorted")
{
return Some(new_check(inner, outer, expr));
return Some(new_check(inner, outer, location));
}
// Ex) list(tuple(...))
if (outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple") {
return Some(new_check(inner, outer, expr));
return Some(new_check(inner, outer, location));
}
// Ex) set(set(...))
if outer == "set" && inner == "set" {
return Some(new_check(inner, outer, expr));
return Some(new_check(inner, outer, location));
}
}
None
}
/// C415
pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
pub fn unnecessary_subscript_reversal(
func: &Expr,
args: &[Expr],
location: Range,
) -> Option<Check> {
let first_arg = args.first()?;
let id = function_name(func)?;
if !["set", "sorted", "reversed"].contains(&id) {
@@ -337,7 +429,7 @@ pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -
if *val == BigInt::from(1) {
return Some(Check::new(
CheckKind::UnnecessarySubscriptReversal(id.to_string()),
Range::from_located(expr),
location,
));
}
}
@@ -354,6 +446,7 @@ pub fn unnecessary_comprehension(
expr: &Expr,
elt: &Expr,
generators: &[Comprehension],
location: Range,
) -> Option<Check> {
if generators.len() != 1 {
return None;
@@ -374,30 +467,27 @@ pub fn unnecessary_comprehension(
};
Some(Check::new(
CheckKind::UnnecessaryComprehension(expr_kind.to_string()),
Range::from_located(expr),
location,
))
}
/// C417
pub fn unnecessary_map(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
fn new_check(kind: &str, expr: &Expr) -> Check {
Check::new(
CheckKind::UnnecessaryMap(kind.to_string()),
Range::from_located(expr),
)
pub fn unnecessary_map(func: &Expr, args: &[Expr], location: Range) -> Option<Check> {
fn new_check(kind: &str, location: Range) -> Check {
Check::new(CheckKind::UnnecessaryMap(kind.to_string()), location)
}
let id = function_name(func)?;
match id {
"map" => {
if args.len() == 2 && matches!(&args[0].node, ExprKind::Lambda { .. }) {
return Some(new_check("generator", expr));
return Some(new_check("generator", location));
}
}
"list" | "set" => {
if let ExprKind::Call { func, args, .. } = &args.first()?.node {
let argument = first_argument_with_matching_function("map", func, args)?;
if let ExprKind::Lambda { .. } = argument {
return Some(new_check(id, expr));
return Some(new_check(id, location));
}
}
}
@@ -408,7 +498,7 @@ pub fn unnecessary_map(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check>
if let ExprKind::Lambda { body, .. } = &argument {
if matches!(&body.node, ExprKind::Tuple { elts, .. } | ExprKind::List { elts, .. } if elts.len() == 2)
{
return Some(new_check(id, expr));
return Some(new_check(id, location));
}
}
}

View File

@@ -0,0 +1,473 @@
use anyhow::Result;
use libcst_native::{
Arg, Call, Codegen, Dict, DictComp, Element, Expr, Expression, LeftCurlyBrace, LeftParen,
LeftSquareBracket, List, ListComp, Module, ParenthesizableWhitespace, RightCurlyBrace,
RightParen, RightSquareBracket, Set, SetComp, SimpleWhitespace, SmallStatement, Statement,
Tuple,
};
use crate::autofix::Fix;
use crate::cst::matchers::match_tree;
use crate::source_code_locator::SourceCodeLocator;
fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>> {
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
if let Some(SmallStatement::Expr(expr)) = expr.body.first_mut() {
Ok(expr)
} else {
Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::Expr."
))
}
} else {
Err(anyhow::anyhow!("Expected node to be: Statement::Simple."))
}
}
fn match_call<'a, 'b>(expr: &'a mut Expr<'b>) -> Result<&'a mut Call<'b>> {
if let Expression::Call(call) = &mut expr.value {
Ok(call)
} else {
Err(anyhow::anyhow!("Expected node to be: Expression::Call."))
}
}
fn match_arg<'a, 'b>(call: &'a Call<'b>) -> Result<&'a Arg<'b>> {
if let Some(arg) = call.args.first() {
Ok(arg)
} else {
Err(anyhow::anyhow!("Expected node to be: Arg."))
}
}
/// (C400) Convert `list(x for x in y)` to `[x for x in y]`.
pub fn fix_unnecessary_generator_list(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(GeneratorExp)))) -> Expr(ListComp)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
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: arg.whitespace_after_arg.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: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(GeneratorExp)))) -> Expr(SetComp)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
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: arg.whitespace_after_arg.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(),
))
}
/// (C402) Convert `dict((x, x) for x in range(3))` to `{x: x for x in
/// range(3)}`.
pub fn fix_unnecessary_generator_dict(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
// Extract the (k, v) from `(k, v) for ...`.
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
generator_exp
} else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp."
));
};
let tuple = if let Expression::Tuple(tuple) = &generator_exp.elt.as_ref() {
tuple
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple."));
};
let key = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(0) {
value
} else {
return Err(anyhow::anyhow!(
"Expected tuple to contain a key as the first element."
));
};
let value = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) {
value
} else {
return Err(anyhow::anyhow!(
"Expected tuple to contain a key as the second element."
));
};
body.value = Expression::DictComp(Box::new(DictComp {
key: Box::new(key.clone()),
value: Box::new(value.clone()),
for_in: generator_exp.for_in.clone(),
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
whitespace_before_colon: Default::default(),
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" ")),
}));
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: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(ListComp)))) ->
// Expr(SetComp)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
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: arg.whitespace_after_arg.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: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(List|Tuple)))) -> Expr(Set)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let mut call = match_call(body)?;
let arg = match_arg(call)?;
let elements = match &arg.value {
Expression::Tuple(inner) => &inner.elements,
Expression::List(inner) => &inner.elements,
_ => {
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: elements.clone(),
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.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: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call("list" | "tuple" | "dict")))) -> Expr(List|Tuple|Dict)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
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: Default::default(),
lpar: vec![Default::default()],
rpar: vec![Default::default()],
}));
}
"list" => {
body.value = Expression::List(Box::new(List {
elements: Default::default(),
lbracket: Default::default(),
rbracket: Default::default(),
lpar: Default::default(),
rpar: Default::default(),
}));
}
"dict" => {
body.value = Expression::Dict(Box::new(Dict {
elements: Default::default(),
lbrace: Default::default(),
rbrace: Default::default(),
lpar: Default::default(),
rpar: Default::default(),
}));
}
_ => {
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(),
))
}
/// (C409) Convert `tuple([1, 2])` to `tuple(1, 2)`
pub fn fix_unnecessary_literal_within_tuple_call(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let (elements, whitespace_after, whitespace_before) = match &arg.value {
Expression::Tuple(inner) => (
&inner.elements,
&inner
.lpar
.first()
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
.whitespace_after,
&inner
.rpar
.first()
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
.whitespace_before,
),
Expression::List(inner) => (
&inner.elements,
&inner.lbracket.whitespace_after,
&inner.rbracket.whitespace_before,
),
_ => {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::Tuple | Expression::List."
))
}
};
body.value = Expression::Tuple(Box::new(Tuple {
elements: elements.clone(),
lpar: vec![LeftParen {
whitespace_after: whitespace_after.clone(),
}],
rpar: vec![RightParen {
whitespace_before: whitespace_before.clone(),
}],
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C410) Convert `list([1, 2])` to `[1, 2]`
pub fn fix_unnecessary_literal_within_list_call(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let (elements, whitespace_after, whitespace_before) = match &arg.value {
Expression::Tuple(inner) => (
&inner.elements,
&inner
.lpar
.first()
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
.whitespace_after,
&inner
.rpar
.first()
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
.whitespace_before,
),
Expression::List(inner) => (
&inner.elements,
&inner.lbracket.whitespace_after,
&inner.rbracket.whitespace_before,
),
_ => {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::Tuple | Expression::List."
))
}
};
body.value = Expression::List(Box::new(List {
elements: elements.clone(),
lbracket: LeftSquareBracket {
whitespace_after: whitespace_after.clone(),
},
rbracket: RightSquareBracket {
whitespace_before: whitespace_before.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(),
))
}
/// (C411) Convert `list([i for i in x])` to `[i for i in x]`.
pub fn fix_unnecessary_list_call(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(List|Tuple)))) -> Expr(List|Tuple)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
body.value = arg.value.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;
mod fixes;

View File

@@ -9,25 +9,20 @@ pub fn print_call(
func: &Expr,
check_print: bool,
check_pprint: bool,
location: Range,
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if check_print && id == "print" {
return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr)));
} else if check_pprint && id == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
));
return Some(Check::new(CheckKind::PPrintFound, location));
}
}
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Name { id, .. } = &value.node {
if check_pprint && id == "pprint" && attr == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
));
return Some(Check::new(CheckKind::PPrintFound, location));
}
}
}

View File

@@ -1,6 +1,7 @@
use log::error;
use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::helpers;
use crate::check_ast::Checker;
use crate::checks::CheckCode;
@@ -12,6 +13,7 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
func,
checker.settings.enabled.contains(&CheckCode::T201),
checker.settings.enabled.contains(&CheckCode::T203),
checker.locate_check(Range::from_located(expr)),
) {
if checker.patch() {
let context = checker.binding_context();

View File

@@ -2,7 +2,7 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum Quote {
Single,
@@ -18,7 +18,7 @@ pub struct Options {
pub avoid_escape: Option<bool>,
}
#[derive(Debug)]
#[derive(Debug, Hash)]
pub struct Settings {
pub inline_quotes: Quote,
pub multiline_quotes: Quote,
@@ -29,7 +29,7 @@ pub struct Settings {
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
inline_quotes: options.inline_quotes.unwrap_or(Quote::Single),
inline_quotes: options.inline_quotes.unwrap_or(Quote::Double),
multiline_quotes: options.multiline_quotes.unwrap_or(Quote::Double),
docstring_quotes: options.docstring_quotes.unwrap_or(Quote::Double),
avoid_escape: options.avoid_escape.unwrap_or(true),
@@ -40,7 +40,7 @@ impl Settings {
impl Default for Settings {
fn default() -> Self {
Self {
inline_quotes: Quote::Single,
inline_quotes: Quote::Double,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Double,
avoid_escape: true,

View File

@@ -45,8 +45,8 @@ pub mod settings;
pub mod source_code_locator;
pub mod visibility;
/// Run ruff over Python source code directly.
pub fn check(path: &Path, contents: &str) -> Result<Vec<Check>> {
/// Run Ruff over Python source code directly.
pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
// Find the project root and pyproject.toml.
let project_root = pyproject::find_project_root(&[path.to_path_buf()]);
match &project_root {
@@ -75,7 +75,7 @@ pub fn check(path: &Path, contents: &str) -> Result<Vec<Check>> {
tokens,
&noqa_line_for,
&settings,
&Mode::None,
&if autofix { Mode::Generate } else { Mode::None },
)?;
Ok(checks)

View File

@@ -118,7 +118,7 @@ pub fn non_lowercase_variable_in_function(scope: &Scope, expr: &Expr, name: &str
if !matches!(scope.kind, ScopeKind::Function(FunctionScope { .. })) {
return None;
}
if !is_lower(name) {
if name.to_lowercase() != name {
return Some(Check::new(
CheckKind::NonLowercaseVariableInFunction(name.to_string()),
Range::from_located(expr),

View File

@@ -29,7 +29,7 @@ pub struct Options {
pub staticmethod_decorators: Option<Vec<String>>,
}
#[derive(Debug)]
#[derive(Debug, Hash)]
pub struct Settings {
pub ignore_names: Vec<String>,
pub classmethod_decorators: Vec<String>,

View File

@@ -2,9 +2,9 @@ use anyhow::Result;
use libcst_native::{Codegen, ImportNames, NameOrAttribute, SmallStatement, Statement};
use rustpython_ast::Stmt;
use crate::ast::types::Range;
use crate::autofix::{helpers, Fix};
use crate::cst::helpers::compose_module_path;
use crate::cst::matchers::match_tree;
use crate::source_code_locator::SourceCodeLocator;
/// Generate a Fix to remove any unused imports from an `import` statement.
@@ -15,13 +15,7 @@ pub fn remove_unused_imports(
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let mut tree = match_tree(locator, stmt)?;
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
@@ -78,13 +72,7 @@ pub fn remove_unused_import_froms(
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let mut tree = match_tree(locator, stmt)?;
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body

View File

@@ -84,14 +84,19 @@ impl Settings {
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
self.line_length.hash(state);
// Add base properties in alphabetical order.
self.dummy_variable_rgx.as_str().hash(state);
for value in self.enabled.iter() {
value.hash(state);
}
self.line_length.hash(state);
for value in self.per_file_ignores.iter() {
value.hash(state);
}
self.target_version.hash(state);
// Add plugin properties in alphabetical order.
self.flake8_quotes.hash(state);
self.pep8_naming.hash(state);
}
}

View File

@@ -11,7 +11,7 @@ use crate::checks::CheckCode;
use crate::checks_gen::CheckCodePrefix;
use crate::fs;
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum PythonVersion {
Py33,
Py34,

View File

@@ -9,5 +9,31 @@ expression: checks
end_location:
row: 1
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
- kind: UnnecessaryGeneratorList
location:
row: 2
column: 4
end_location:
row: 4
column: 1
fix:
patch:
content: "[\n x for x in range(3)\n]"
location:
row: 2
column: 4
end_location:
row: 4
column: 1
applied: false

View File

@@ -9,5 +9,31 @@ expression: checks
end_location:
row: 1
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
- kind: UnnecessaryGeneratorSet
location:
row: 2
column: 4
end_location:
row: 4
column: 1
fix:
patch:
content: "{\n x for x in range(3)\n}"
location:
row: 2
column: 4
end_location:
row: 4
column: 1
applied: false

View File

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

View File

@@ -9,5 +9,31 @@ expression: checks
end_location:
row: 1
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
- kind: UnnecessaryListComprehensionSet
location:
row: 2
column: 4
end_location:
row: 4
column: 1
fix:
patch:
content: "{\n x for x in range(3)\n}"
location:
row: 2
column: 4
end_location:
row: 4
column: 1
applied: false

View File

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 1
column: 16
fix: ~
fix:
patch:
content: "{1, 2}"
location:
row: 1
column: 5
end_location:
row: 1
column: 16
applied: false
- kind:
UnnecessaryLiteralSet: tuple
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 2
column: 16
fix: ~
fix:
patch:
content: "{1, 2}"
location:
row: 2
column: 5
end_location:
row: 2
column: 16
applied: false
- kind:
UnnecessaryLiteralSet: list
location:
@@ -28,7 +46,16 @@ expression: checks
end_location:
row: 3
column: 12
fix: ~
fix:
patch:
content: set()
location:
row: 3
column: 5
end_location:
row: 3
column: 12
applied: false
- kind:
UnnecessaryLiteralSet: tuple
location:
@@ -37,5 +64,14 @@ expression: checks
end_location:
row: 4
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:
row: 1
column: 11
fix: ~
fix:
patch:
content: ()
location:
row: 1
column: 4
end_location:
row: 1
column: 11
applied: false
- kind:
UnnecessaryCollectionCall: list
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 2
column: 10
fix: ~
fix:
patch:
content: "[]"
location:
row: 2
column: 4
end_location:
row: 2
column: 10
applied: false
- kind:
UnnecessaryCollectionCall: dict
location:
@@ -28,7 +46,16 @@ expression: checks
end_location:
row: 3
column: 11
fix: ~
fix:
patch:
content: "{}"
location:
row: 3
column: 5
end_location:
row: 3
column: 11
applied: false
- kind:
UnnecessaryCollectionCall: dict
location:

View File

@@ -9,24 +9,87 @@ expression: checks
column: 5
end_location:
row: 1
column: 18
fix: ~
- kind:
UnnecessaryLiteralWithinTupleCall: tuple
location:
row: 2
column: 5
end_location:
row: 2
column: 18
fix: ~
column: 14
fix:
patch:
content: ()
location:
row: 1
column: 5
end_location:
row: 1
column: 14
applied: false
- kind:
UnnecessaryLiteralWithinTupleCall: list
location:
row: 2
column: 5
end_location:
row: 2
column: 18
fix:
patch:
content: "(1, 2)"
location:
row: 2
column: 5
end_location:
row: 2
column: 18
applied: false
- kind:
UnnecessaryLiteralWithinTupleCall: tuple
location:
row: 3
column: 5
end_location:
row: 3
column: 14
fix: ~
column: 18
fix:
patch:
content: "(1, 2)"
location:
row: 3
column: 5
end_location:
row: 3
column: 18
applied: false
- kind:
UnnecessaryLiteralWithinTupleCall: list
location:
row: 4
column: 5
end_location:
row: 7
column: 2
fix:
patch:
content: "(\n 1,\n 2\n)"
location:
row: 4
column: 5
end_location:
row: 7
column: 2
applied: false
- kind:
UnnecessaryLiteralWithinTupleCall: tuple
location:
row: 8
column: 5
end_location:
row: 10
column: 1
fix:
patch:
content: "(1, 2)"
location:
row: 8
column: 5
end_location:
row: 10
column: 1
applied: false

View File

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 1
column: 17
fix: ~
fix:
patch:
content: "[1, 2]"
location:
row: 1
column: 5
end_location:
row: 1
column: 17
applied: false
- kind:
UnnecessaryLiteralWithinListCall: tuple
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 2
column: 17
fix: ~
fix:
patch:
content: "[1, 2]"
location:
row: 2
column: 5
end_location:
row: 2
column: 17
applied: false
- kind:
UnnecessaryLiteralWithinListCall: list
location:
@@ -28,7 +46,16 @@ expression: checks
end_location:
row: 3
column: 13
fix: ~
fix:
patch:
content: "[]"
location:
row: 3
column: 5
end_location:
row: 3
column: 13
applied: false
- kind:
UnnecessaryLiteralWithinListCall: tuple
location:
@@ -37,5 +64,14 @@ expression: checks
end_location:
row: 4
column: 13
fix: ~
fix:
patch:
content: "[]"
location:
row: 4
column: 5
end_location:
row: 4
column: 13
applied: false

View File

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