Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
406491a3a2 | ||
|
|
bfae262359 | ||
|
|
af894f290f | ||
|
|
c901742244 | ||
|
|
7e4faf4b69 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1871,7 +1871,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.52"
|
||||
version = "0.0.53"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.52"
|
||||
version = "0.0.53"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
14
README.md
14
README.md
@@ -201,18 +201,24 @@ stylistic lint rules that are obviated by autoformatting.
|
||||
|
||||
### Parity with Flake8
|
||||
|
||||
ruff's goal is to achieve feature-parity with Flake8 when used (1) without plugins, (2) alongside
|
||||
ruff's goal is to achieve feature parity with Flake8 when used (1) without plugins, (2) alongside
|
||||
Black, and (3) on Python 3 code.
|
||||
|
||||
**Under those conditions, ruff implements 44 out of 60 rules.** (ruff is missing: 14 rules related
|
||||
to string `.format` calls, 1 rule related to docstring parsing, and 1 rule related to redefined
|
||||
variables.)
|
||||
|
||||
ruff also implements some of the most popular Flake8 plugins natively, including:
|
||||
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
|
||||
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
|
||||
1. Flake8 has a plugin architecture and supports writing custom lint rules.
|
||||
2. ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
|
||||
1. ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
|
||||
pattern matching and parenthesized context managers.
|
||||
2. Flake8 has a plugin architecture and supports writing custom lint rules.
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -264,6 +270,8 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
|
||||
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin |
|
||||
| A003 | BuiltinAttributeShadowing | class attribute `...` is shadowing a python builtin |
|
||||
| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` |
|
||||
| T201 | PrintFound | `print` found |
|
||||
| T203 | PPrintFound | `pprint` found` |
|
||||
| R001 | UselessObjectInheritance | Class `...` inherits from object |
|
||||
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead |
|
||||
| M001 | UnusedNOQA | Unused `noqa` directive |
|
||||
|
||||
1
resources/test/fixtures/T201.py
vendored
Normal file
1
resources/test/fixtures/T201.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
print("Hello, world!") # T201
|
||||
10
resources/test/fixtures/T203.py
vendored
Normal file
10
resources/test/fixtures/T203.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
from pprint import pprint
|
||||
|
||||
pprint("Hello, world!") # T203
|
||||
|
||||
|
||||
import pprint
|
||||
|
||||
pprint.pprint("Hello, world!") # T203
|
||||
|
||||
pprint.pformat("Hello, world!")
|
||||
@@ -721,3 +721,31 @@ pub fn check_super_args(
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// flake8-print
|
||||
/// Check whether a function call is a `print` or `pprint` invocation
|
||||
pub fn check_print_call(expr: &Expr, func: &Expr) -> Option<Check> {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "print" {
|
||||
return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr)));
|
||||
} else if id == "pprint" {
|
||||
return Some(Check::new(
|
||||
CheckKind::PPrintFound,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "pprint" && attr == "pprint" {
|
||||
return Some(Check::new(
|
||||
CheckKind::PPrintFound,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Result<Fix> {
|
||||
pub fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Result<Fix> {
|
||||
if parent
|
||||
.map(|parent| is_lone_child(stmt, parent, deleted))
|
||||
.map_or(Ok(None), |v| v.map(Some))?
|
||||
|
||||
@@ -19,6 +19,7 @@ use crate::ast::types::{
|
||||
};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::{checks, operations, visitor};
|
||||
use crate::autofix::fixes::remove_stmt;
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
@@ -761,6 +762,43 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-print
|
||||
if self.settings.select.contains(&CheckCode::T201)
|
||||
|| self.settings.select.contains(&CheckCode::T203)
|
||||
{
|
||||
if let Some(mut check) = checks::check_print_call(expr, func) {
|
||||
if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
let context = self.binding_context();
|
||||
if matches!(
|
||||
self.parents[context.defined_by].node,
|
||||
StmtKind::Expr { .. }
|
||||
) {
|
||||
let deleted: Vec<&Stmt> = self
|
||||
.deletions
|
||||
.iter()
|
||||
.map(|index| self.parents[*index])
|
||||
.collect();
|
||||
|
||||
match remove_stmt(
|
||||
self.parents[context.defined_by],
|
||||
context.defined_in.map(|index| self.parents[index]),
|
||||
&deleted,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix.content.is_empty() || fix.content == "pass" {
|
||||
self.deletions.insert(context.defined_by);
|
||||
}
|
||||
check.amend(fix)
|
||||
}
|
||||
Err(e) => error!("Failed to fix unused imports: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.checks.push(check)
|
||||
}
|
||||
}
|
||||
|
||||
if let ExprKind::Name { id, ctx } = &func.node {
|
||||
if id == "locals" && matches!(ctx, ExprContext::Load) {
|
||||
let scope = &mut self.scopes[*(self
|
||||
|
||||
@@ -5,7 +5,7 @@ use anyhow::Result;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const DEFAULT_CHECK_CODES: [CheckCode; 46] = [
|
||||
pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
|
||||
// pycodestyle
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
@@ -50,15 +50,9 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 46] = [
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
// flake8-builtins
|
||||
CheckCode::A001,
|
||||
CheckCode::A002,
|
||||
CheckCode::A003,
|
||||
// flake8-super
|
||||
CheckCode::SPR001,
|
||||
];
|
||||
|
||||
pub const ALL_CHECK_CODES: [CheckCode; 49] = [
|
||||
pub const ALL_CHECK_CODES: [CheckCode; 51] = [
|
||||
// pycodestyle
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
@@ -109,6 +103,9 @@ pub const ALL_CHECK_CODES: [CheckCode; 49] = [
|
||||
CheckCode::A003,
|
||||
// flake8-super
|
||||
CheckCode::SPR001,
|
||||
// flake8-print
|
||||
CheckCode::T201,
|
||||
CheckCode::T203,
|
||||
// Meta
|
||||
CheckCode::M001,
|
||||
// Refactor
|
||||
@@ -168,6 +165,9 @@ pub enum CheckCode {
|
||||
A003,
|
||||
// flake8-super
|
||||
SPR001,
|
||||
// flake8-print
|
||||
T201,
|
||||
T203,
|
||||
// Refactor
|
||||
R001,
|
||||
R002,
|
||||
@@ -293,6 +293,9 @@ impl CheckCode {
|
||||
CheckCode::A003 => "A003",
|
||||
// flake8-super
|
||||
CheckCode::SPR001 => "SPR001",
|
||||
// flake8-print
|
||||
CheckCode::T201 => "T201",
|
||||
CheckCode::T203 => "T203",
|
||||
// Refactor
|
||||
CheckCode::R001 => "R001",
|
||||
CheckCode::R002 => "R002",
|
||||
@@ -363,6 +366,9 @@ impl CheckCode {
|
||||
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
|
||||
// flake8-super
|
||||
CheckCode::SPR001 => CheckKind::SuperCallWithParameters,
|
||||
// flake8-print
|
||||
CheckCode::T201 => CheckKind::PrintFound,
|
||||
CheckCode::T203 => CheckKind::PPrintFound,
|
||||
// Refactor
|
||||
CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()),
|
||||
CheckCode::R002 => CheckKind::NoAssertEquals,
|
||||
@@ -438,6 +444,9 @@ pub enum CheckKind {
|
||||
BuiltinAttributeShadowing(String),
|
||||
// flake8-super
|
||||
SuperCallWithParameters,
|
||||
// flake8-print
|
||||
PrintFound,
|
||||
PPrintFound,
|
||||
}
|
||||
|
||||
impl CheckKind {
|
||||
@@ -497,6 +506,9 @@ impl CheckKind {
|
||||
CheckKind::BuiltinAttributeShadowing(_) => "BuiltinAttributeShadowing",
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => "SuperCallWithParameters",
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => "PrintFound",
|
||||
CheckKind::PPrintFound => "PPrintFound",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,6 +566,9 @@ impl CheckKind {
|
||||
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => &CheckCode::SPR001,
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => &CheckCode::T201,
|
||||
CheckKind::PPrintFound => &CheckCode::T203,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,6 +717,9 @@ impl CheckKind {
|
||||
CheckKind::SuperCallWithParameters => {
|
||||
"Use `super()` instead of `super(__class__, self)`".to_string()
|
||||
}
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => "`print` found".to_string(),
|
||||
CheckKind::PPrintFound => "`pprint` found`".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,6 +732,8 @@ impl CheckKind {
|
||||
| CheckKind::UnusedNOQA(_)
|
||||
| CheckKind::SuperCallWithParameters
|
||||
| CheckKind::UnusedImport(_)
|
||||
| CheckKind::PrintFound
|
||||
| CheckKind::PPrintFound
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,4 +779,28 @@ mod tests {
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t201() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/T201.py"),
|
||||
&settings::Settings::for_rule(CheckCode::T201),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t203() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/T203.py"),
|
||||
&settings::Settings::for_rule(CheckCode::T203),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
53
src/noqa.rs
53
src/noqa.rs
@@ -47,10 +47,9 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
|
||||
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
|
||||
let mut noqa_line_for: Vec<usize> = vec![];
|
||||
|
||||
let mut last_is_string = false;
|
||||
let mut last_seen = usize::MIN;
|
||||
let mut min_line = usize::MAX;
|
||||
let mut max_line = usize::MIN;
|
||||
let mut in_string = false;
|
||||
|
||||
for (start, tok, end) in lxr.iter().flatten() {
|
||||
if matches!(tok, Tok::EndOfFile) {
|
||||
@@ -62,29 +61,20 @@ pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
|
||||
max_line = max(max_line, start.row());
|
||||
|
||||
// For now, we only care about preserving noqa directives across multi-line strings.
|
||||
if last_is_string {
|
||||
noqa_line_for.extend(vec![max_line; (max_line + 1) - min_line]);
|
||||
} else {
|
||||
for i in (min_line - 1)..(max_line) {
|
||||
if in_string {
|
||||
for i in (noqa_line_for.len())..(min_line - 1) {
|
||||
noqa_line_for.push(i + 1);
|
||||
}
|
||||
noqa_line_for.extend(vec![max_line; (max_line + 1) - min_line]);
|
||||
}
|
||||
|
||||
min_line = usize::MAX;
|
||||
max_line = usize::MIN;
|
||||
} else {
|
||||
// Handle empty lines.
|
||||
if start.row() > last_seen {
|
||||
for i in last_seen..(start.row() - 1) {
|
||||
noqa_line_for.push(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
min_line = min(min_line, start.row());
|
||||
max_line = max(max_line, end.row());
|
||||
}
|
||||
last_seen = start.row();
|
||||
last_is_string = matches!(tok, Tok::String { .. });
|
||||
in_string = matches!(tok, Tok::String { .. });
|
||||
}
|
||||
|
||||
noqa_line_for
|
||||
@@ -173,6 +163,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn extraction() -> Result<()> {
|
||||
let empty: Vec<usize> = Default::default();
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
@@ -180,7 +172,7 @@ z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3]);
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"
|
||||
@@ -190,7 +182,7 @@ z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3, 4]);
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
@@ -200,7 +192,7 @@ z = x + 1
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3]);
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
@@ -211,7 +203,7 @@ z = x + 1
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3, 4]);
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = '''abc
|
||||
@@ -222,7 +214,28 @@ y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![4, 4, 4, 4, 5, 6]);
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![4, 4, 4, 4]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
z = 2",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 5, 5, 5, 5]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 5, 5, 5, 5]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
21
src/snapshots/ruff__linter__tests__t201.snap
Normal file
21
src/snapshots/ruff__linter__tests__t201.snap
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: PrintFound
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 1
|
||||
column: 23
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 1
|
||||
applied: false
|
||||
|
||||
37
src/snapshots/ruff__linter__tests__t203.snap
Normal file
37
src/snapshots/ruff__linter__tests__t203.snap
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: PPrintFound
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 3
|
||||
column: 24
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 4
|
||||
column: 1
|
||||
applied: false
|
||||
- kind: PPrintFound
|
||||
location:
|
||||
row: 8
|
||||
column: 1
|
||||
end_location:
|
||||
row: 8
|
||||
column: 31
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 8
|
||||
column: 1
|
||||
end_location:
|
||||
row: 9
|
||||
column: 1
|
||||
applied: false
|
||||
|
||||
Reference in New Issue
Block a user