Compare commits

..

1 Commits

Author SHA1 Message Date
Charlie Marsh
6f317fc3e7 Try out rust-python-parser (nom-based) 2022-10-05 15:57:04 -04:00
40 changed files with 366 additions and 1144 deletions

View File

@@ -14,12 +14,6 @@ your proposed change.
ruff is written in Rust (1.63.0). You'll need to install the
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
```shell
cargo install cargo-insta
```
### Development
After cloning the repository, run ruff locally with:
@@ -64,7 +58,7 @@ similar rules are implemented.
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run ruff locally
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisfied with the output,
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisified with the output,
codify the behavior as a snapshot test by adding a new function to the `mod tests` section of
`src/linter.rs`, like so:

106
Cargo.lock generated
View File

@@ -16,7 +16,7 @@ checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.7",
"once_cell",
"version_check",
"version_check 0.9.4",
]
[[package]]
@@ -319,6 +319,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "bytecount"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f861d9ce359f56dbcb6e0c2a1cb84e52ad732cadb57b806adeb3c7668caccbd8"
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -907,7 +913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
dependencies = [
"typenum",
"version_check",
"version_check 0.9.4",
]
[[package]]
@@ -1137,7 +1143,7 @@ dependencies = [
"string_cache",
"term",
"tiny-keccak",
"unicode-xid",
"unicode-xid 0.2.4",
]
[[package]]
@@ -1366,6 +1372,16 @@ dependencies = [
"libc",
]
[[package]]
name = "nom"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
dependencies = [
"memchr",
"version_check 0.1.5",
]
[[package]]
name = "nom"
version = "5.1.2"
@@ -1373,7 +1389,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
"version_check 0.9.4",
]
[[package]]
name = "nom_locate"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a47c112b3861d81f7fbf73892b9271af933af32bd5dee6889aa3c3fa9caed7e"
dependencies = [
"bytecount",
"memchr",
"nom 4.2.3",
]
[[package]]
@@ -1394,6 +1421,17 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.3"
@@ -1695,7 +1733,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn",
"version_check",
"version_check 0.9.4",
]
[[package]]
@@ -1706,7 +1744,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
"version_check 0.9.4",
]
[[package]]
@@ -1718,6 +1756,21 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "python-parser"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683594d14f9c4db9e899643108918ad9db99aedad77121b5179aa205343c10de"
dependencies = [
"nom 4.2.3",
"nom_locate",
"num-bigint 0.2.6",
"num-traits",
"unicode-xid 0.1.0",
"unicode_names2 0.2.2",
"wtf8",
]
[[package]]
name = "quote"
version = "1.0.21"
@@ -1907,7 +1960,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.60"
version = "0.0.56"
dependencies = [
"anyhow",
"bincode",
@@ -1928,6 +1981,7 @@ dependencies = [
"notify",
"once_cell",
"path-absolutize",
"python-parser",
"rayon",
"regex",
"rustpython-ast",
@@ -1957,7 +2011,7 @@ name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
dependencies = [
"num-bigint",
"num-bigint 0.4.3",
"rustpython-common",
"rustpython-compiler-core",
]
@@ -1973,7 +2027,7 @@ dependencies = [
"lexical-parse-float",
"libc",
"lock_api",
"num-bigint",
"num-bigint 0.4.3",
"num-complex",
"num-traits",
"once_cell",
@@ -1995,7 +2049,7 @@ dependencies = [
"bstr",
"itertools",
"lz4_flex",
"num-bigint",
"num-bigint 0.4.3",
"num-complex",
"serde",
"static_assertions",
@@ -2013,7 +2067,7 @@ dependencies = [
"lalrpop",
"lalrpop-util",
"log",
"num-bigint",
"num-bigint 0.4.3",
"num-traits",
"phf 0.10.1",
"phf_codegen 0.10.0",
@@ -2023,7 +2077,7 @@ dependencies = [
"tiny-keccak",
"unic-emoji-char",
"unic-ucd-ident",
"unicode_names2",
"unicode_names2 0.5.1",
]
[[package]]
@@ -2315,7 +2369,7 @@ checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e"
dependencies = [
"dirs 2.0.2",
"fnv",
"nom",
"nom 5.1.2",
"phf 0.8.0",
"phf_codegen 0.8.0",
]
@@ -2485,12 +2539,24 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unicode_names2"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc6c5da0c8d7200f9488cc346bd30ba62bcd9f79ef937ea6573132e3d507df9"
[[package]]
name = "unicode_names2"
version = "0.5.1"
@@ -2553,9 +2619,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55"
dependencies = [
"ctor",
"version_check",
"version_check 0.9.4",
]
[[package]]
name = "version_check"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
[[package]]
name = "version_check"
version = "0.9.4"
@@ -2820,6 +2892,12 @@ dependencies = [
"winapi-build",
]
[[package]]
name = "wtf8"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6b9309a86639c488a8eb2b5331cb5127cc9feb0a94a0db4b5d1ab5b84977956"
[[package]]
name = "yaml-rust"
version = "0.4.5"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.60"
version = "0.0.56"
edition = "2021"
[lib]
@@ -35,6 +35,7 @@ serde_json = { version = "1.0.83" }
toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
python-parser = "0.2.0"
[dev-dependencies]
insta = { version = "1.19.1", features = ["yaml"] }

View File

@@ -130,8 +130,6 @@ Options:
Enable automatic additions of noqa directives to failing lines
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
Regular expression matching the name of dummy variables
--target-version <TARGET_VERSION>
The minimum Python version that should be supported
-h, --help
Print help information
-V, --version
@@ -215,7 +213,6 @@ 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/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (partial)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (partial)
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
@@ -277,18 +274,12 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | | |
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | | |
| A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin | | |
| C400 | UnnecessaryGeneratorList | Unnecessary generator - rewrite as a list comprehension | | |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator - 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 | | |
| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
| T201 | PrintFound | `print` found | | 🛠 |
| T203 | PPrintFound | `pprint` found | | 🛠 |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | | 🛠 |
| U002 | UnnecessaryAbspath | `abspath(__file__)` is unnecessary in Python 3.9 and later | | 🛠 |
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` | | 🛠 |
| U004 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
| U005 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| R001 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
## Integrations

23
examples/parse_python.rs Normal file
View File

@@ -0,0 +1,23 @@
/// Print the AST for a given Python file.
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use ruff::fs;
#[derive(Debug, Parser)]
struct Cli {
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_file(&cli.file)?;
let ast = python_parser::file_input(python_parser::make_strspan(&contents))?.1;
println!("{:?}", ast);
Ok(())
}

37
output.py Normal file
View File

@@ -0,0 +1,37 @@
from collections import Counter
def f() -> None:
"""Docstring goes here."""
for x in range(5):
print(x)
else:
print("Nope!")
a = {
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
}
cls(title=title, before_text=before_text, after_text=after_text, before_description=before_description, after_description=after_description, transform_type=transform_type)

View File

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

View File

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

View File

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

View File

@@ -1 +0,0 @@
d = dict([(i, i) for i in range(3)])

View File

@@ -89,36 +89,3 @@ A = (
f'B'
f'{B}'
)
from typing import Annotated, Literal
def arbitrary_callable() -> None:
...
class PEP593Test:
field: Annotated[
int,
"base64",
arbitrary_callable(),
123,
(1, 2, 3),
]
field_with_stringified_type: Annotated[
"PEP593Test",
123,
]
field_with_undefined_stringified_type: Annotated[
"PEP593Test123",
123,
]
field_with_nested_subscript: Annotated[
dict[Literal["foo"], str],
123,
]
field_with_undefined_nested_subscript: Annotated[
dict["foo", "bar"], # Expected to fail as undefined.
123,
]

View File

@@ -1,15 +0,0 @@
from os.path import abspath
x = abspath(__file__)
import os
y = os.path.abspath(__file__)
from os import path
z = path.abspath(__file__)

View File

@@ -1,5 +0,0 @@
type('')
type(b'')
type(0)
type(0.)
type(0j)

View File

@@ -6,7 +6,6 @@ use rustpython_parser::ast::{
Arg, ArgData, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind,
Stmt, StmtKind, Unaryop,
};
use serde::{Deserialize, Serialize};
use crate::ast::types::{
Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind,
@@ -135,86 +134,6 @@ pub fn check_useless_metaclass_type(
None
}
/// Check UnnecessaryAbspath compliance.
pub fn check_unnecessary_abspath(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
// Validate the arguments.
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &args[0].node {
if id == "__file__" {
match &func.node {
ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => {
if id == "abspath" {
return Some(Check::new(CheckKind::UnnecessaryAbspath, location));
}
}
_ => {}
}
}
}
}
None
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Primitive {
Bool,
Str,
Bytes,
Int,
Float,
Complex,
}
impl Primitive {
fn from_constant(constant: &Constant) -> Option<Self> {
match constant {
Constant::Bool(_) => Some(Primitive::Bool),
Constant::Str(_) => Some(Primitive::Str),
Constant::Bytes(_) => Some(Primitive::Bytes),
Constant::Int(_) => Some(Primitive::Int),
Constant::Float(_) => Some(Primitive::Float),
Constant::Complex { .. } => Some(Primitive::Complex),
_ => None,
}
}
pub fn builtin(&self) -> String {
match self {
Primitive::Bool => "bool".to_string(),
Primitive::Str => "str".to_string(),
Primitive::Bytes => "bytes".to_string(),
Primitive::Int => "int".to_string(),
Primitive::Float => "float".to_string(),
Primitive::Complex => "complex".to_string(),
}
}
}
/// Check TypeOfPrimitive compliance.
pub fn check_type_of_primitive(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
// Validate the arguments.
if args.len() == 1 {
match &func.node {
ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => {
if id == "type" {
if let ExprKind::Constant { value, .. } = &args[0].node {
if let Some(primitive) = Primitive::from_constant(value) {
return Some(Check::new(
CheckKind::TypeOfPrimitive(primitive),
location,
));
}
}
}
}
_ => {}
}
}
None
}
fn is_ambiguous_name(name: &str) -> bool {
name == "l" || name == "I" || name == "O"
}
@@ -773,88 +692,6 @@ pub fn is_super_call_with_arguments(func: &Expr, args: &Vec<Expr>) -> bool {
}
}
// flakes8-comprehensions
/// Check `list(generator)` compliance.
pub fn unnecessary_generator_list(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "list" {
if let ExprKind::GeneratorExp { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorList,
Range::from_located(expr),
));
}
}
}
}
None
}
/// Check `set(generator)` compliance.
pub fn unnecessary_generator_set(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "set" {
if let ExprKind::GeneratorExp { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorList,
Range::from_located(expr),
));
}
}
}
}
None
}
/// Check `set([...])` compliance.
pub fn unnecessary_list_comprehension_set(
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "set" {
if let ExprKind::ListComp { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionSet,
Range::from_located(expr),
));
}
}
}
}
None
}
/// Check `dict([...])` compliance.
pub fn unnecessary_list_comprehension_dict(
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "dict" {
if let ExprKind::ListComp { elt, .. } = &args[0].node {
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionDict,
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
}
None
}
// flake8-super
/// Check that `super()` has no args
pub fn check_super_args(

View File

@@ -25,7 +25,7 @@ use crate::plugins;
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
use crate::python::future::ALL_FEATURE_NAMES;
use crate::python::typing;
use crate::settings::{PythonVersion, Settings};
use crate::settings::Settings;
pub const GLOBAL_SCOPE_INDEX: usize = 0;
@@ -108,32 +108,11 @@ fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
}
}
enum SubscriptKind {
AnnotatedSubscript,
PEP593AnnotatedSubscript,
}
fn match_annotated_subscript(expr: &Expr) -> Option<SubscriptKind> {
fn is_annotated_subscript(expr: &Expr) -> bool {
match &expr.node {
ExprKind::Attribute { attr, .. } => {
if typing::is_annotated_subscript(attr) {
Some(SubscriptKind::AnnotatedSubscript)
} else if typing::is_pep593_annotated_subscript(attr) {
Some(SubscriptKind::PEP593AnnotatedSubscript)
} else {
None
}
}
ExprKind::Name { id, .. } => {
if typing::is_annotated_subscript(id) {
Some(SubscriptKind::AnnotatedSubscript)
} else if typing::is_pep593_annotated_subscript(id) {
Some(SubscriptKind::PEP593AnnotatedSubscript)
} else {
None
}
}
_ => None,
ExprKind::Attribute { attr, .. } => typing::is_annotated_subscript(attr),
ExprKind::Name { id, .. } => typing::is_annotated_subscript(id),
_ => false,
}
}
@@ -388,7 +367,7 @@ where
decorator_list,
..
} => {
if self.settings.enabled.contains(&CheckCode::U004) {
if self.settings.enabled.contains(&CheckCode::R001) {
plugins::useless_object_inheritance(self, stmt, name, bases, keywords);
}
@@ -749,7 +728,7 @@ where
ExprContext::Del => self.handle_node_delete(expr),
},
ExprKind::Call { func, args, .. } => {
if self.settings.enabled.contains(&CheckCode::U005) {
if self.settings.enabled.contains(&CheckCode::R002) {
plugins::assert_equals(self, func);
}
@@ -765,46 +744,6 @@ where
plugins::print_call(self, expr, func);
}
// flake8-comprehensions
if self.settings.enabled.contains(&CheckCode::C400) {
if let Some(check) = checks::unnecessary_generator_list(expr, func, args) {
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C401) {
if let Some(check) = checks::unnecessary_generator_set(expr, func, args) {
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C403) {
if let Some(check) =
checks::unnecessary_list_comprehension_set(expr, func, args)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C404) {
if let Some(check) =
checks::unnecessary_list_comprehension_dict(expr, func, args)
{
self.checks.push(check);
};
}
// pyupgrade
if self.settings.enabled.contains(&CheckCode::U002)
&& self.settings.target_version >= PythonVersion::Py310
{
plugins::unnecessary_abspath(self, expr, func, args);
}
if self.settings.enabled.contains(&CheckCode::U003) {
plugins::type_of_primitive(self, expr, func, args);
}
if let ExprKind::Name { id, ctx } = &func.node {
if id == "locals" && matches!(ctx, ExprContext::Load) {
let scope = &mut self.scopes[*(self
@@ -923,11 +862,9 @@ where
ExprKind::Constant {
value: Constant::Str(value),
..
} => {
if self.in_annotation && !self.in_literal {
self.deferred_string_annotations
.push((Range::from_located(expr), value));
}
} if self.in_annotation && !self.in_literal => {
self.deferred_string_annotations
.push((Range::from_located(expr), value));
}
ExprKind::Lambda { args, .. } => {
// Visit the arguments, but avoid the body, which will be deferred.
@@ -1078,35 +1015,12 @@ where
}
}
ExprKind::Subscript { value, slice, ctx } => {
match match_annotated_subscript(value) {
Some(subscript) => match subscript {
// Ex) Optional[int]
SubscriptKind::AnnotatedSubscript => {
self.visit_expr(value);
self.visit_annotation(slice);
self.visit_expr_context(ctx);
}
// Ex) Annotated[int, "Hello, world!"]
SubscriptKind::PEP593AnnotatedSubscript => {
// First argument is a type (including forward references); the rest are
// arbitrary Python objects.
self.visit_expr(value);
if let ExprKind::Tuple { elts, ctx } = &slice.node {
if let Some(expr) = elts.first() {
self.visit_expr(expr);
self.in_annotation = false;
for expr in elts.iter().skip(1) {
self.visit_expr(expr);
}
self.in_annotation = true;
self.visit_expr_context(ctx);
}
} else {
error!("Found non-ExprKind::Tuple argument to PEP 593 Annotation.")
}
}
},
None => visitor::walk_expr(self, expr),
if is_annotated_subscript(value) {
self.visit_expr(value);
self.visit_annotation(slice);
self.visit_expr_context(ctx);
} else {
visitor::walk_expr(self, expr);
}
}
_ => visitor::walk_expr(self, expr),
@@ -1702,7 +1616,7 @@ impl<'a> Checker<'a> {
context.defined_by,
context.defined_in,
))
.or_default();
.or_insert(vec![]);
full_names.push(full_name);
}
BindingKind::Importation(full_name, context)
@@ -1713,7 +1627,7 @@ impl<'a> Checker<'a> {
context.defined_by,
context.defined_in,
))
.or_default();
.or_insert(vec![]);
full_names.push(full_name);
}
_ => {}
@@ -1725,8 +1639,12 @@ impl<'a> Checker<'a> {
let child = self.parents[defined_by];
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
let fix = if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{
let mut check = Check::new(
CheckKind::UnusedImport(full_names.join(", ")),
self.locate_check(Range::from_located(child)),
);
if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
let deleted: Vec<&Stmt> = self
.deletions
.iter()
@@ -1739,22 +1657,14 @@ impl<'a> Checker<'a> {
};
match removal_fn(&mut self.locator, &full_names, child, parent, &deleted) {
Ok(fix) => Some(fix),
Err(e) => {
error!("Failed to fix unused imports: {}", e);
None
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
self.deletions.insert(defined_by);
}
check.amend(fix)
}
Err(e) => error!("Failed to fix unused imports: {}", e),
}
} else {
None
};
let mut check = Check::new(
CheckKind::UnusedImport(full_names.into_iter().map(String::from).collect()),
self.locate_check(Range::from_located(child)),
);
if let Some(fix) = fix {
check.amend(fix);
}
self.checks.push(check);

View File

@@ -1,13 +1,11 @@
use std::str::FromStr;
use crate::ast::checks::Primitive;
use crate::ast::types::Range;
use anyhow::Result;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use crate::ast::types::Range;
pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
// pycodestyle
CheckCode::E402,
@@ -55,7 +53,7 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
CheckCode::F901,
];
pub const ALL_CHECK_CODES: [CheckCode; 58] = [
pub const ALL_CHECK_CODES: [CheckCode; 52] = [
// pycodestyle
CheckCode::E402,
CheckCode::E501,
@@ -104,11 +102,6 @@ pub const ALL_CHECK_CODES: [CheckCode; 58] = [
CheckCode::A001,
CheckCode::A002,
CheckCode::A003,
// flake8-comprehensions
CheckCode::C400,
CheckCode::C401,
CheckCode::C403,
CheckCode::C404,
// flake8-super
CheckCode::SPR001,
// flake8-print
@@ -116,10 +109,9 @@ pub const ALL_CHECK_CODES: [CheckCode; 58] = [
CheckCode::T203,
// pyupgrade
CheckCode::U001,
CheckCode::U002,
CheckCode::U003,
CheckCode::U004,
CheckCode::U005,
// Refactor
CheckCode::R001,
CheckCode::R002,
// Meta
CheckCode::M001,
];
@@ -174,11 +166,6 @@ pub enum CheckCode {
A001,
A002,
A003,
// flake8-comprehensions
C400,
C401,
C403,
C404,
// flake8-super
SPR001,
// flake8-print
@@ -186,10 +173,9 @@ pub enum CheckCode {
T203,
// pyupgrade
U001,
U002,
U003,
U004,
U005,
// Refactor
R001,
R002,
// Meta
M001,
}
@@ -247,9 +233,6 @@ impl FromStr for CheckCode {
"A001" => Ok(CheckCode::A001),
"A002" => Ok(CheckCode::A002),
"A003" => Ok(CheckCode::A003),
// flake8-comprehensions
"C403" => Ok(CheckCode::C403),
"C404" => Ok(CheckCode::C404),
// flake8-super
"SPR001" => Ok(CheckCode::SPR001),
// flake8-print
@@ -257,10 +240,9 @@ impl FromStr for CheckCode {
"T203" => Ok(CheckCode::T203),
// pyupgrade
"U001" => Ok(CheckCode::U001),
"U002" => Ok(CheckCode::U002),
"U003" => Ok(CheckCode::U003),
"U004" => Ok(CheckCode::U004),
"U005" => Ok(CheckCode::U005),
// Refactor
"R001" => Ok(CheckCode::R001),
"R002" => Ok(CheckCode::R002),
// Meta
"M001" => Ok(CheckCode::M001),
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
@@ -319,11 +301,6 @@ impl CheckCode {
CheckCode::A001 => "A001",
CheckCode::A002 => "A002",
CheckCode::A003 => "A003",
// flake8-comprehensions
CheckCode::C400 => "C400",
CheckCode::C401 => "C401",
CheckCode::C403 => "C403",
CheckCode::C404 => "C404",
// flake8-super
CheckCode::SPR001 => "SPR001",
// flake8-print
@@ -331,10 +308,9 @@ impl CheckCode {
CheckCode::T203 => "T203",
// pyupgrade
CheckCode::U001 => "U001",
CheckCode::U002 => "U002",
CheckCode::U003 => "U003",
CheckCode::U004 => "U004",
CheckCode::U005 => "U005",
// Refactor
CheckCode::R001 => "R001",
CheckCode::R002 => "R002",
// Meta
CheckCode::M001 => "M001",
}
@@ -368,7 +344,7 @@ impl CheckCode {
CheckCode::E902 => CheckKind::IOError("IOError: `...`".to_string()),
CheckCode::E999 => CheckKind::SyntaxError("`...`".to_string()),
// pyflakes
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()]),
CheckCode::F401 => CheckKind::UnusedImport("...".to_string()),
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
CheckCode::F404 => CheckKind::LateFutureImport,
@@ -402,11 +378,6 @@ impl CheckCode {
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
// flake8-comprehensions
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
CheckCode::C403 => CheckKind::UnnecessaryListComprehensionSet,
CheckCode::C404 => CheckKind::UnnecessaryListComprehensionDict,
// flake8-super
CheckCode::SPR001 => CheckKind::SuperCallWithParameters,
// flake8-print
@@ -414,10 +385,9 @@ impl CheckCode {
CheckCode::T203 => CheckKind::PPrintFound,
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
CheckCode::U003 => CheckKind::TypeOfPrimitive(Primitive::Str),
CheckCode::U004 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::U005 => CheckKind::NoAssertEquals,
// Refactor
CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::R002 => CheckKind::NoAssertEquals,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
@@ -466,6 +436,7 @@ pub enum CheckKind {
ModuleImportNotAtTopOfFile,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable(String),
NoAssertEquals,
NoneComparison(RejectedCmpop),
NotInTest,
NotIsTest,
@@ -478,31 +449,21 @@ pub enum CheckKind {
UndefinedExport(String),
UndefinedLocal(String),
UndefinedName(String),
UnusedImport(Vec<String>),
UnusedImport(String),
UnusedNOQA(Option<String>),
UnusedVariable(String),
UselessMetaclassType,
UselessObjectInheritance(String),
YieldOutsideFunction,
// flake8-builtin
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
BuiltinAttributeShadowing(String),
// flakes8-comprehensions
UnnecessaryGeneratorList,
UnnecessaryGeneratorSet,
UnnecessaryListComprehensionSet,
UnnecessaryListComprehensionDict,
// flake8-super
SuperCallWithParameters,
// flake8-print
PrintFound,
PPrintFound,
// pyupgrade
TypeOfPrimitive(Primitive),
UnnecessaryAbspath,
UselessMetaclassType,
NoAssertEquals,
UselessObjectInheritance(String),
// Meta
UnusedNOQA(Option<String>),
}
impl CheckKind {
@@ -555,20 +516,14 @@ impl CheckKind {
CheckKind::BuiltinVariableShadowing(_) => "BuiltinVariableShadowing",
CheckKind::BuiltinArgumentShadowing(_) => "BuiltinArgumentShadowing",
CheckKind::BuiltinAttributeShadowing(_) => "BuiltinAttributeShadowing",
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => "UnnecessaryGeneratorList",
CheckKind::UnnecessaryGeneratorSet => "UnnecessaryGeneratorSet",
CheckKind::UnnecessaryListComprehensionSet => "UnnecessaryListComprehensionSet",
CheckKind::UnnecessaryListComprehensionDict => "UnnecessaryListComprehensionDict",
// flake8-super
CheckKind::SuperCallWithParameters => "SuperCallWithParameters",
// flake8-print
CheckKind::PrintFound => "PrintFound",
CheckKind::PPrintFound => "PPrintFound",
// pyupgrade
CheckKind::TypeOfPrimitive(_) => "TypeOfPrimitive",
CheckKind::UnnecessaryAbspath => "UnnecessaryAbspath",
CheckKind::UselessMetaclassType => "UselessMetaclassType",
// Refactor
CheckKind::NoAssertEquals => "NoAssertEquals",
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
// Meta
@@ -625,22 +580,16 @@ impl CheckKind {
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
CheckKind::UnnecessaryListComprehensionSet => &CheckCode::C403,
CheckKind::UnnecessaryListComprehensionDict => &CheckCode::C404,
// flake8-super
CheckKind::SuperCallWithParameters => &CheckCode::SPR001,
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
// pyupgrade
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
CheckKind::UselessMetaclassType => &CheckCode::U001,
CheckKind::NoAssertEquals => &CheckCode::U005,
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
// Refactor
CheckKind::NoAssertEquals => &CheckCode::R002,
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
@@ -766,10 +715,7 @@ impl CheckKind {
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedImport(names) => {
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
format!("{names} imported but unused")
}
CheckKind::UnusedImport(name) => format!("`{name}` imported but unused"),
CheckKind::UnusedVariable(name) => {
format!("Local variable `{name}` is assigned to but never used")
}
@@ -787,19 +733,6 @@ impl CheckKind {
CheckKind::BuiltinAttributeShadowing(name) => {
format!("Class attribute `{name}` is shadowing a python builtin")
}
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => {
"Unnecessary generator - rewrite as a list comprehension".to_string()
}
CheckKind::UnnecessaryGeneratorSet => {
"Unnecessary generator - rewrite as a set comprehension".to_string()
}
CheckKind::UnnecessaryListComprehensionSet => {
"Unnecessary list comprehension - rewrite as a set comprehension".to_string()
}
CheckKind::UnnecessaryListComprehensionDict => {
"Unnecessary list comprehension - rewrite as a dict comprehension".to_string()
}
// flake8-super
CheckKind::SuperCallWithParameters => {
"Use `super()` instead of `super(__class__, self)`".to_string()
@@ -808,13 +741,8 @@ impl CheckKind {
CheckKind::PrintFound => "`print` found".to_string(),
CheckKind::PPrintFound => "`pprint` found".to_string(),
// pyupgrade
CheckKind::TypeOfPrimitive(primitive) => {
format!("Use `{}` instead of `type(...)`", primitive.builtin())
}
CheckKind::UnnecessaryAbspath => {
"`abspath(__file__)` is unnecessary in Python 3.9 and later".to_string()
}
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
// Refactor
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
}
@@ -837,8 +765,6 @@ impl CheckKind {
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::SuperCallWithParameters
| CheckKind::TypeOfPrimitive(_)
| CheckKind::UnnecessaryAbspath
| CheckKind::UnusedImport(_)
| CheckKind::UnusedNOQA(_)
| CheckKind::UselessMetaclassType

View File

@@ -1,136 +0,0 @@
use std::fmt;
use std::path::PathBuf;
use clap::{command, Parser};
use log::warn;
use regex::Regex;
use crate::checks::CheckCode;
use crate::printer::SerializationFormat;
use crate::pyproject::StrCheckCodePair;
use crate::settings::PythonVersion;
use crate::RawSettings;
#[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")]
#[command(version)]
pub struct Cli {
#[arg(required = true)]
pub files: Vec<PathBuf>,
/// Enable verbose logging.
#[arg(short, long)]
pub verbose: bool,
/// Disable all logging (but still exit with status code "1" upon detecting errors).
#[arg(short, long)]
pub quiet: bool,
/// Exit with status code "0", even upon detecting errors.
#[arg(short, long)]
pub exit_zero: bool,
/// Run in watch mode by re-running whenever files change.
#[arg(short, long)]
pub watch: bool,
/// Attempt to automatically fix lint errors.
#[arg(short, long)]
pub fix: bool,
/// Disable cache reads.
#[arg(short, long)]
pub no_cache: bool,
/// List of error codes to enable.
#[arg(long, value_delimiter = ',')]
pub select: Vec<CheckCode>,
/// Like --select, but adds additional error codes on top of the selected ones.
#[arg(long, value_delimiter = ',')]
pub extend_select: Vec<CheckCode>,
/// List of error codes to ignore.
#[arg(long, value_delimiter = ',')]
pub ignore: Vec<CheckCode>,
/// Like --ignore, but adds additional error codes on top of the ignored ones.
#[arg(long, value_delimiter = ',')]
pub extend_ignore: Vec<CheckCode>,
/// List of paths, used to exclude files and/or directories from checks.
#[arg(long, value_delimiter = ',')]
pub exclude: Vec<String>,
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
#[arg(long, value_delimiter = ',')]
pub extend_exclude: Vec<String>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')]
pub per_file_ignores: Vec<StrCheckCodePair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
pub format: SerializationFormat,
/// See the files ruff will be run against with the current settings.
#[arg(long)]
pub show_files: bool,
/// See ruff's settings.
#[arg(long)]
pub show_settings: bool,
/// Enable automatic additions of noqa directives to failing lines.
#[arg(long)]
pub add_noqa: bool,
/// Regular expression matching the name of dummy variables.
#[arg(long)]
pub dummy_variable_rgx: Option<Regex>,
/// The minimum Python version that should be supported.
#[arg(long)]
pub target_version: Option<PythonVersion>,
/// Round-trip auto-formatting.
// TODO(charlie): This should be a sub-command.
#[arg(long, hide = true)]
pub autoformat: bool,
}
pub enum Warnable {
Select,
ExtendSelect,
}
impl fmt::Display for Warnable {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Warnable::Select => fmt.write_str("--select"),
Warnable::ExtendSelect => fmt.write_str("--extend-select"),
}
}
}
/// Warn the user if they attempt to enable a code that won't be respected.
pub fn warn_on(
flag: Warnable,
codes: &Vec<CheckCode>,
cli_ignore: &Vec<CheckCode>,
cli_extend_ignore: &Vec<CheckCode>,
pyproject_settings: &RawSettings,
pyproject_path: &Option<PathBuf>,
) {
for code in codes {
if !cli_ignore.is_empty() {
if cli_ignore.contains(code) {
warn!("{code:?} was passed to {flag}, but ignored via --ignore")
}
} else if pyproject_settings.ignore.contains(code) {
if let Some(path) = pyproject_path {
warn!(
"{code:?} was passed to {flag}, but ignored by the `ignore` field in {}",
path.to_string_lossy()
)
} else {
warn!("{code:?} was passed to {flag}, but ignored by the default `ignore` field",)
}
}
if !cli_extend_ignore.is_empty() {
if cli_extend_ignore.contains(code) {
warn!("{code:?} was passed to {flag}, but ignored via --extend-ignore")
}
} else if pyproject_settings.extend_ignore.contains(code) {
if let Some(path) = pyproject_path {
warn!(
"{code:?} was passed to {flag}, but ignored by the `extend_ignore` field in {}",
path.to_string_lossy()
)
} else {
warn!("{code:?} was passed to {flag}, but ignored by the default `extend_ignore` field")
}
}
}
}

View File

@@ -15,7 +15,6 @@ pub mod cache;
pub mod check_ast;
mod check_lines;
pub mod checks;
pub mod cli;
pub mod code_gen;
pub mod fs;
pub mod linter;
@@ -42,7 +41,7 @@ pub fn check(path: &Path, contents: &str) -> Result<Vec<Message>> {
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
let settings = Settings::from_raw(RawSettings::from_pyproject(&pyproject, &project_root)?);
let settings = Settings::from_raw(RawSettings::from_pyproject(pyproject, project_root)?);
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(contents);

View File

@@ -8,7 +8,6 @@ use rustpython_parser::{lexer, parser};
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::autofix::fixer::fix_file;
use crate::check_ast::check_ast;
use crate::check_lines::check_lines;
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
@@ -101,33 +100,19 @@ pub fn lint_path(
let contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(&contents);
// let tokens: Vec<LexResult> = tokenize(&contents);
// Determine the noqa line for every line in the source.
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
// let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
// Generate checks.
let mut checks = check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)?;
match python_parser::file_input(python_parser::make_strspan(&contents)) {
Ok(x) => {}
Err(e) => return Err(anyhow::anyhow!("Err: failed")),
}
// Apply autofix.
if matches!(autofix, fixer::Mode::Apply) {
fix_file(&mut checks, &contents, path)?;
};
// let _ = parser::parse_program_tokens(tokens, "<filename>")?;
// Convert to messages.
let messages: Vec<Message> = checks
.into_iter()
.map(|check| Message {
kind: check.kind,
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
location: check.location,
end_location: check.end_location,
filename: path.to_string_lossy().to_string(),
})
.collect();
cache::set(path, &metadata, settings, autofix, &messages, mode);
Ok(messages)
Ok(vec![])
}
pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
@@ -690,6 +675,30 @@ mod tests {
Ok(())
}
#[test]
fn r001() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/R001.py"),
&settings::Settings::for_rule(CheckCode::R001),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn r002() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/R002.py"),
&settings::Settings::for_rule(CheckCode::R002),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn init() -> Result<()> {
let mut checks = check_path(
@@ -762,54 +771,6 @@ mod tests {
Ok(())
}
#[test]
fn c400() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C400.py"),
&settings::Settings::for_rule(CheckCode::C400),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c401() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C401.py"),
&settings::Settings::for_rule(CheckCode::C401),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c403() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C403.py"),
&settings::Settings::for_rule(CheckCode::C403),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c404() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C404.py"),
&settings::Settings::for_rule(CheckCode::C404),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn spr001() -> Result<()> {
let mut checks = check_path(
@@ -857,52 +818,4 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u002() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U002.py"),
&settings::Settings::for_rule(CheckCode::U002),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u003() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U003.py"),
&settings::Settings::for_rule(CheckCode::U003),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u004() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U004.py"),
&settings::Settings::for_rule(CheckCode::U004),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u005() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U005.py"),
&settings::Settings::for_rule(CheckCode::U005),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -5,33 +5,98 @@ use std::sync::mpsc::channel;
use std::time::Instant;
use anyhow::Result;
use clap::Parser;
use clap::{command, Parser};
use colored::Colorize;
use log::{debug, error};
use notify::{raw_watcher, RecursiveMode, Watcher};
use rayon::prelude::*;
use regex::Regex;
use walkdir::DirEntry;
use ruff::cache;
use ruff::checks::CheckCode;
use ruff::checks::CheckKind;
use ruff::cli::{warn_on, Cli, Warnable};
use ruff::fs::iter_python_files;
use ruff::linter::add_noqa_to_path;
use ::ruff::cache;
use ::ruff::checks::CheckCode;
use ::ruff::checks::CheckKind;
use ::ruff::fs::iter_python_files;
use ::ruff::linter::add_noqa_to_path;
use ::ruff::linter::lint_path;
use ::ruff::logging::set_up_logging;
use ::ruff::message::Message;
use ::ruff::printer::{Printer, SerializationFormat};
use ::ruff::pyproject::{self, StrCheckCodePair};
use ::ruff::settings::CurrentSettings;
use ::ruff::settings::{FilePattern, PerFileIgnore, Settings};
use ::ruff::tell_user;
use ruff::linter::autoformat_path;
use ruff::linter::lint_path;
use ruff::logging::set_up_logging;
use ruff::message::Message;
use ruff::printer::{Printer, SerializationFormat};
use ruff::pyproject::{self};
use ruff::settings::CurrentSettings;
use ruff::settings::RawSettings;
use ruff::settings::{FilePattern, PerFileIgnore, Settings};
use ruff::tell_user;
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")]
#[command(version)]
struct Cli {
#[arg(required = true)]
files: Vec<PathBuf>,
/// Enable verbose logging.
#[arg(short, long)]
verbose: bool,
/// Disable all logging (but still exit with status code "1" upon detecting errors).
#[arg(short, long)]
quiet: bool,
/// Exit with status code "0", even upon detecting errors.
#[arg(short, long)]
exit_zero: bool,
/// Run in watch mode by re-running whenever files change.
#[arg(short, long)]
watch: bool,
/// Attempt to automatically fix lint errors.
#[arg(short, long)]
fix: bool,
/// Disable cache reads.
#[arg(short, long)]
no_cache: bool,
/// List of error codes to enable.
#[arg(long, value_delimiter = ',')]
select: Vec<CheckCode>,
/// Like --select, but adds additional error codes on top of the selected ones.
#[arg(long, value_delimiter = ',')]
extend_select: Vec<CheckCode>,
/// List of error codes to ignore.
#[arg(long, value_delimiter = ',')]
ignore: Vec<CheckCode>,
/// Like --ignore, but adds additional error codes on top of the ignored ones.
#[arg(long, value_delimiter = ',')]
extend_ignore: Vec<CheckCode>,
/// List of paths, used to exclude files and/or directories from checks.
#[arg(long, value_delimiter = ',')]
exclude: Vec<String>,
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
#[arg(long, value_delimiter = ',')]
extend_exclude: Vec<String>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')]
per_file_ignores: Vec<StrCheckCodePair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
format: SerializationFormat,
/// See the files ruff will be run against with the current settings.
#[arg(long)]
show_files: bool,
/// See ruff's settings.
#[arg(long)]
show_settings: bool,
/// Enable automatic additions of noqa directives to failing lines.
#[arg(long)]
add_noqa: bool,
/// Enable automatic formatting.
#[arg(long)]
autoformat: bool,
/// Regular expression matching the name of dummy variables.
#[arg(long)]
dummy_variable_rgx: Option<Regex>,
}
#[cfg(feature = "update-informer")]
fn check_for_updates() {
use update_informer::{registry, Check};
@@ -56,11 +121,8 @@ fn check_for_updates() {
}
}
fn show_settings(settings: RawSettings, project_root: Option<PathBuf>, pyproject: Option<PathBuf>) {
println!(
"{:#?}",
CurrentSettings::from_settings(settings, project_root, pyproject)
);
fn show_settings(settings: RawSettings) {
println!("{:#?}", CurrentSettings::from_settings(settings));
}
fn show_files(files: &[PathBuf], settings: &Settings) {
@@ -226,7 +288,7 @@ fn inner_main() -> Result<ExitCode> {
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect();
let mut settings = RawSettings::from_pyproject(&pyproject, &project_root)?;
let mut settings = RawSettings::from_pyproject(pyproject, project_root)?;
if !exclude.is_empty() {
settings.exclude = exclude;
}
@@ -237,25 +299,9 @@ fn inner_main() -> Result<ExitCode> {
settings.per_file_ignores = per_file_ignores;
}
if !cli.select.is_empty() {
warn_on(
Warnable::Select,
&cli.select,
&cli.ignore,
&cli.extend_ignore,
&settings,
&pyproject,
);
settings.select = cli.select;
}
if !cli.extend_select.is_empty() {
warn_on(
Warnable::ExtendSelect,
&cli.extend_select,
&cli.ignore,
&cli.extend_ignore,
&settings,
&pyproject,
);
settings.extend_select = cli.extend_select;
}
if !cli.ignore.is_empty() {
@@ -264,9 +310,6 @@ fn inner_main() -> Result<ExitCode> {
if !cli.extend_ignore.is_empty() {
settings.extend_ignore = cli.extend_ignore;
}
if let Some(target_version) = cli.target_version {
settings.target_version = target_version;
}
if let Some(dummy_variable_rgx) = cli.dummy_variable_rgx {
settings.dummy_variable_rgx = dummy_variable_rgx;
}
@@ -276,7 +319,7 @@ fn inner_main() -> Result<ExitCode> {
return Ok(ExitCode::FAILURE);
}
if cli.show_settings {
show_settings(settings, project_root, pyproject);
show_settings(settings);
return Ok(ExitCode::SUCCESS);
}
@@ -360,7 +403,7 @@ fn inner_main() -> Result<ExitCode> {
#[cfg(feature = "update-informer")]
check_for_updates();
if messages.iter().any(|message| !message.fixed) && !cli.exit_zero {
if !messages.is_empty() && !cli.exit_zero {
return Ok(ExitCode::FAILURE);
}
}

View File

@@ -104,7 +104,9 @@ fn add_noqa_inner(
.unwrap_or(lineno);
if !codes.is_empty() {
let matches = matches_by_line.entry(noqa_lineno).or_default();
let matches = matches_by_line
.entry(noqa_lineno)
.or_insert_with(BTreeSet::new);
matches.append(&mut codes);
}
}

View File

@@ -4,8 +4,6 @@ mod if_tuple;
mod invalid_print_syntax;
mod print_call;
mod super_call_with_parameters;
mod type_of_primitive;
mod unnecessary_abspath;
mod useless_metaclass_type;
mod useless_object_inheritance;
@@ -15,7 +13,5 @@ pub use if_tuple::if_tuple;
pub use invalid_print_syntax::invalid_print_syntax;
pub use print_call::print_call;
pub use super_call_with_parameters::super_call_with_parameters;
pub use type_of_primitive::type_of_primitive;
pub use unnecessary_abspath::unnecessary_abspath;
pub use useless_metaclass_type::useless_metaclass_type;
pub use useless_object_inheritance::useless_object_inheritance;

View File

@@ -1,25 +0,0 @@
use rustpython_ast::Expr;
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::{CheckKind, Fix};
pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &Vec<Expr>) {
if let Some(mut check) =
checks::check_type_of_primitive(func, args, checker.locate_check(Range::from_located(expr)))
{
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let CheckKind::TypeOfPrimitive(primitive) = &check.kind {
check.amend(Fix {
content: primitive.builtin(),
location: expr.location,
end_location: expr.end_location,
applied: false,
});
}
}
checker.add_check(check);
}
}

View File

@@ -1,25 +0,0 @@
use rustpython_ast::Expr;
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::Fix;
pub fn unnecessary_abspath(checker: &mut Checker, expr: &Expr, func: &Expr, args: &Vec<Expr>) {
if let Some(mut check) = checks::check_unnecessary_abspath(
func,
args,
checker.locate_check(Range::from_located(expr)),
) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "__file__".to_string(),
location: expr.location,
end_location: expr.end_location,
applied: false,
});
}
checker.add_check(check);
}
}

View File

@@ -9,7 +9,6 @@ use serde::{Deserialize, Deserializer};
use crate::checks::CheckCode;
use crate::fs;
use crate::settings::PythonVersion;
pub fn load_config(pyproject: &Option<PathBuf>) -> Result<Config> {
match pyproject {
@@ -42,7 +41,6 @@ pub struct Config {
#[serde(default)]
pub per_file_ignores: Vec<StrCheckCodePair>,
pub dummy_variable_rgx: Option<String>,
pub target_version: Option<PythonVersion>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -191,7 +189,6 @@ mod tests {
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
})
})
);
@@ -216,7 +213,6 @@ line-length = 79
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
})
})
);
@@ -241,7 +237,6 @@ exclude = ["foo.py"]
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
})
})
);
@@ -266,7 +261,6 @@ select = ["E501"]
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
})
})
);
@@ -292,7 +286,6 @@ ignore = ["E501"]
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
})
})
);
@@ -361,7 +354,6 @@ other-attribute = 1
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
}
);

View File

@@ -7,7 +7,6 @@ static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
"AbstractAsyncContextManager",
"AbstractContextManager",
"AbstractSet",
// "Annotated",
"AsyncContextManager",
"AsyncGenerator",
"AsyncIterable",
@@ -88,7 +87,3 @@ static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
pub fn is_annotated_subscript(name: &str) -> bool {
ANNOTATED_SUBSCRIPTS.contains(name)
}
pub fn is_pep593_annotated_subscript(name: &str) -> bool {
name == "Annotated"
}

View File

@@ -1,50 +1,16 @@
use std::collections::BTreeSet;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use anyhow::{anyhow, Result};
use glob::Pattern;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::checks::{CheckCode, DEFAULT_CHECK_CODES};
use crate::fs;
use crate::pyproject::{load_config, StrCheckCodePair};
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize)]
pub enum PythonVersion {
Py33,
Py34,
Py35,
Py36,
Py37,
Py38,
Py39,
Py310,
Py311,
}
impl FromStr for PythonVersion {
type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"py33" => Ok(PythonVersion::Py33),
"py34" => Ok(PythonVersion::Py34),
"py35" => Ok(PythonVersion::Py35),
"py36" => Ok(PythonVersion::Py36),
"py37" => Ok(PythonVersion::Py37),
"py38" => Ok(PythonVersion::Py38),
"py39" => Ok(PythonVersion::Py39),
"py310" => Ok(PythonVersion::Py310),
"py311" => Ok(PythonVersion::Py311),
_ => Err(anyhow!("Unknown version: {}", string)),
}
}
}
#[derive(Debug, Clone, Hash)]
pub enum FilePattern {
Simple(&'static str),
@@ -94,8 +60,9 @@ pub struct RawSettings {
pub ignore: Vec<CheckCode>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub project_root: Option<PathBuf>,
pub pyproject: Option<PathBuf>,
pub select: Vec<CheckCode>,
pub target_version: PythonVersion,
}
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
@@ -127,43 +94,44 @@ static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
impl RawSettings {
pub fn from_pyproject(
pyproject: &Option<PathBuf>,
project_root: &Option<PathBuf>,
pyproject: Option<PathBuf>,
project_root: Option<PathBuf>,
) -> Result<Self> {
let config = load_config(pyproject)?;
let config = load_config(&pyproject)?;
Ok(RawSettings {
dummy_variable_rgx: match config.dummy_variable_rgx {
Some(pattern) => Regex::new(&pattern)
.map_err(|e| anyhow!("Invalid dummy-variable-rgx value: {e}"))?,
None => DEFAULT_DUMMY_VARIABLE_RGX.clone(),
},
target_version: config.target_version.unwrap_or(PythonVersion::Py310),
exclude: config
.exclude
.map(|paths| {
paths
.iter()
.map(|path| FilePattern::from_user(path, project_root))
.map(|path| FilePattern::from_user(path, &project_root))
.collect()
})
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
extend_exclude: config
.extend_exclude
.iter()
.map(|path| FilePattern::from_user(path, project_root))
.map(|path| FilePattern::from_user(path, &project_root))
.collect(),
extend_ignore: config.extend_ignore,
select: config
.select
.unwrap_or_else(|| DEFAULT_CHECK_CODES.to_vec()),
extend_select: config.extend_select,
ignore: config.ignore,
line_length: config.line_length.unwrap_or(88),
per_file_ignores: config
.per_file_ignores
.into_iter()
.map(|pair| PerFileIgnore::new(pair, project_root))
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect(),
project_root,
pyproject,
select: config
.select
.unwrap_or_else(|| DEFAULT_CHECK_CODES.to_vec()),
})
}
}
@@ -176,7 +144,6 @@ pub struct Settings {
pub extend_exclude: Vec<FilePattern>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub target_version: PythonVersion,
}
impl Settings {
@@ -198,7 +165,6 @@ impl Settings {
extend_exclude: settings.extend_exclude,
line_length: settings.line_length,
per_file_ignores: settings.per_file_ignores,
target_version: PythonVersion::Py310,
}
}
@@ -210,7 +176,6 @@ impl Settings {
extend_exclude: vec![],
line_length: 88,
per_file_ignores: vec![],
target_version: PythonVersion::Py310,
}
}
@@ -222,7 +187,6 @@ impl Settings {
extend_exclude: vec![],
line_length: 88,
per_file_ignores: vec![],
target_version: PythonVersion::Py310,
}
}
}
@@ -274,18 +238,13 @@ pub struct CurrentSettings {
pub ignore: Vec<CheckCode>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub select: Vec<CheckCode>,
pub target_version: PythonVersion,
pub project_root: Option<PathBuf>,
pub pyproject: Option<PathBuf>,
pub select: Vec<CheckCode>,
}
impl CurrentSettings {
pub fn from_settings(
settings: RawSettings,
project_root: Option<PathBuf>,
pyproject: Option<PathBuf>,
) -> Self {
pub fn from_settings(settings: RawSettings) -> Self {
Self {
dummy_variable_rgx: settings.dummy_variable_rgx,
exclude: settings
@@ -303,10 +262,9 @@ impl CurrentSettings {
ignore: settings.ignore,
line_length: settings.line_length,
per_file_ignores: settings.per_file_ignores,
project_root: settings.project_root,
pyproject: settings.pyproject,
select: settings.select,
target_version: settings.target_version,
project_root,
pyproject,
}
}
}

View File

@@ -1,13 +0,0 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryGeneratorList
location:
row: 1
column: 5
end_location:
row: 1
column: 30
fix: ~

View File

@@ -1,13 +0,0 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryGeneratorList
location:
row: 1
column: 5
end_location:
row: 1
column: 29
fix: ~

View File

@@ -1,13 +0,0 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryListComprehensionSet
location:
row: 1
column: 5
end_location:
row: 1
column: 31
fix: ~

View File

@@ -1,13 +0,0 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryListComprehensionDict
location:
row: 1
column: 5
end_location:
row: 1
column: 37
fix: ~

View File

@@ -3,8 +3,7 @@ source: src/linter.rs
expression: checks
---
- kind:
UnusedImport:
- functools
UnusedImport: functools
location:
row: 2
column: 1
@@ -21,8 +20,7 @@ expression: checks
column: 21
applied: false
- kind:
UnusedImport:
- collections.OrderedDict
UnusedImport: collections.OrderedDict
location:
row: 4
column: 1
@@ -39,8 +37,7 @@ expression: checks
column: 2
applied: false
- kind:
UnusedImport:
- logging.handlers
UnusedImport: logging.handlers
location:
row: 12
column: 1
@@ -57,8 +54,7 @@ expression: checks
column: 24
applied: false
- kind:
UnusedImport:
- shelve
UnusedImport: shelve
location:
row: 33
column: 5
@@ -75,8 +71,7 @@ expression: checks
column: 1
applied: false
- kind:
UnusedImport:
- importlib
UnusedImport: importlib
location:
row: 34
column: 5
@@ -84,17 +79,16 @@ expression: checks
row: 34
column: 21
fix:
content: ""
content: pass
location:
row: 34
column: 1
column: 5
end_location:
row: 35
column: 1
row: 34
column: 21
applied: false
- kind:
UnusedImport:
- pathlib
UnusedImport: pathlib
location:
row: 38
column: 5
@@ -111,8 +105,7 @@ expression: checks
column: 1
applied: false
- kind:
UnusedImport:
- pickle
UnusedImport: pickle
location:
row: 53
column: 9

View File

@@ -74,31 +74,4 @@ expression: checks
row: 89
column: 9
fix: ~
- kind:
UndefinedName: PEP593Test123
location:
row: 114
column: 10
end_location:
row: 114
column: 24
fix: ~
- kind:
UndefinedName: foo
location:
row: 122
column: 15
end_location:
row: 122
column: 19
fix: ~
- kind:
UndefinedName: bar
location:
row: 122
column: 22
end_location:
row: 122
column: 26
fix: ~

View File

@@ -3,8 +3,7 @@ source: src/linter.rs
expression: checks
---
- kind:
UnusedImport:
- models.Nut
UnusedImport: models.Nut
location:
row: 5
column: 1

View File

@@ -1,53 +0,0 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryAbspath
location:
row: 3
column: 5
end_location:
row: 3
column: 22
fix:
content: __file__
location:
row: 3
column: 5
end_location:
row: 3
column: 22
applied: false
- kind: UnnecessaryAbspath
location:
row: 9
column: 5
end_location:
row: 9
column: 30
fix:
content: __file__
location:
row: 9
column: 5
end_location:
row: 9
column: 30
applied: false
- kind: UnnecessaryAbspath
location:
row: 15
column: 5
end_location:
row: 15
column: 27
fix:
content: __file__
location:
row: 15
column: 5
end_location:
row: 15
column: 27
applied: false

View File

@@ -1,90 +0,0 @@
---
source: src/linter.rs
expression: checks
---
- kind:
TypeOfPrimitive: Str
location:
row: 1
column: 1
end_location:
row: 1
column: 9
fix:
content: str
location:
row: 1
column: 1
end_location:
row: 1
column: 9
applied: false
- kind:
TypeOfPrimitive: Bytes
location:
row: 2
column: 1
end_location:
row: 2
column: 10
fix:
content: bytes
location:
row: 2
column: 1
end_location:
row: 2
column: 10
applied: false
- kind:
TypeOfPrimitive: Int
location:
row: 3
column: 1
end_location:
row: 3
column: 8
fix:
content: int
location:
row: 3
column: 1
end_location:
row: 3
column: 8
applied: false
- kind:
TypeOfPrimitive: Float
location:
row: 4
column: 1
end_location:
row: 4
column: 9
fix:
content: float
location:
row: 4
column: 1
end_location:
row: 4
column: 9
applied: false
- kind:
TypeOfPrimitive: Complex
location:
row: 5
column: 1
end_location:
row: 5
column: 9
fix:
content: complex
location:
row: 5
column: 1
end_location:
row: 5
column: 9
applied: false