Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b44d6c2c44 | ||
|
|
2749660b1f | ||
|
|
c1eeae90f1 | ||
|
|
27025055ee | ||
|
|
e306fe0765 | ||
|
|
5ffb9c08d5 | ||
|
|
1a8940f015 |
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -1,6 +1,8 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
create:
|
||||
tags:
|
||||
- v*
|
||||
@@ -127,7 +129,7 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --release --out dist
|
||||
args: --no-default-features --release --out dist
|
||||
maturin-version: "v0.13.0"
|
||||
- uses: uraimo/run-on-arch-action@v2.0.5
|
||||
if: matrix.target != 'ppc64'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.26
|
||||
rev: v0.0.28
|
||||
hooks:
|
||||
- id: lint
|
||||
|
||||
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -1744,7 +1744,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.26"
|
||||
version = "0.0.28"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1758,6 +1758,7 @@ dependencies = [
|
||||
"fern",
|
||||
"filetime",
|
||||
"glob",
|
||||
"itertools",
|
||||
"log",
|
||||
"notify",
|
||||
"once_cell",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.26"
|
||||
version = "0.0.28"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -19,6 +19,7 @@ dirs = { version = "4.0.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = { version = "0.3.0" }
|
||||
itertools = "0.10.3"
|
||||
log = { version = "0.4.17" }
|
||||
notify = { version = "4.0.17" }
|
||||
once_cell = { version = "1.13.1" }
|
||||
@@ -28,9 +29,13 @@ rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charlier
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
toml = { version = "0.5.9" }
|
||||
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"] }
|
||||
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
|
||||
walkdir = { version = "2.3.2" }
|
||||
|
||||
[features]
|
||||
default = ["update-informer"]
|
||||
update-informer = ["dep:update-informer"]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = "thin"
|
||||
|
||||
10
README.md
10
README.md
@@ -57,7 +57,7 @@ ruff also works with [Pre-Commit](https://pre-commit.com) (requires Cargo on sys
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.26
|
||||
rev: v0.0.28
|
||||
hooks:
|
||||
- id: lint
|
||||
```
|
||||
@@ -86,7 +86,7 @@ ruff path/to/code/ --select F401 F403
|
||||
See `ruff --help` for more:
|
||||
|
||||
```shell
|
||||
ruff (v0.0.26)
|
||||
ruff (v0.0.28)
|
||||
An extremely fast Python linter.
|
||||
|
||||
USAGE:
|
||||
@@ -122,6 +122,12 @@ lint rules that are obviated by Black (e.g., stylistic rules).
|
||||
| ---- | ----- | ------- |
|
||||
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file |
|
||||
| E501 | LineTooLong | Line too long |
|
||||
| E711 | NoneComparison | Comparison to `None` should be `cond is None` |
|
||||
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` |
|
||||
| E713 | NotInTest | Test for membership should be `not in` |
|
||||
| E714 | NotIsTest | Test for object identity should be `is not` |
|
||||
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def |
|
||||
| E902 | IOError | No such file or directory: `...` |
|
||||
| F401 | UnusedImport | `...` imported but unused |
|
||||
| F403 | ImportStarUsage | Unable to detect undefined names |
|
||||
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
/// Generate a Markdown-compatible table of supported lint rules.
|
||||
use ruff::checks::CheckKind;
|
||||
use ruff::checks::{CheckKind, RejectedCmpop};
|
||||
|
||||
fn main() {
|
||||
let mut check_kinds: Vec<CheckKind> = vec![
|
||||
CheckKind::AssertTuple,
|
||||
CheckKind::DefaultExceptNotLast,
|
||||
CheckKind::DoNotAssignLambda,
|
||||
CheckKind::DuplicateArgumentName,
|
||||
CheckKind::FStringMissingPlaceholders,
|
||||
CheckKind::IOError("...".to_string()),
|
||||
CheckKind::IfTuple,
|
||||
CheckKind::ImportStarUsage,
|
||||
CheckKind::LineTooLong,
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
CheckKind::NoAssertEquals,
|
||||
CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
CheckKind::NotInTest,
|
||||
CheckKind::NotIsTest,
|
||||
CheckKind::RaiseNotImplemented,
|
||||
CheckKind::ReturnOutsideFunction,
|
||||
CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
|
||||
CheckKind::UndefinedExport("...".to_string()),
|
||||
CheckKind::UndefinedLocal("...".to_string()),
|
||||
CheckKind::UndefinedName("...".to_string()),
|
||||
|
||||
11
resources/test/fixtures/E711.py
vendored
Normal file
11
resources/test/fixtures/E711.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
if var == None:
|
||||
pass
|
||||
|
||||
if None != var:
|
||||
pass
|
||||
|
||||
if var is None:
|
||||
pass
|
||||
|
||||
if None is not var:
|
||||
pass
|
||||
14
resources/test/fixtures/E712.py
vendored
Normal file
14
resources/test/fixtures/E712.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
if var == True:
|
||||
pass
|
||||
|
||||
if False != var:
|
||||
pass
|
||||
|
||||
if var != False != True:
|
||||
pass
|
||||
|
||||
if var is True:
|
||||
pass
|
||||
|
||||
if False is not var:
|
||||
pass
|
||||
7
resources/test/fixtures/E713.py
vendored
Normal file
7
resources/test/fixtures/E713.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
my_list = [1, 2, 3]
|
||||
if not num in my_list:
|
||||
print(num)
|
||||
|
||||
my_list = [1, 2, 3]
|
||||
if num not in my_list:
|
||||
print(num)
|
||||
5
resources/test/fixtures/E714.py
vendored
Normal file
5
resources/test/fixtures/E714.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
if not user is None:
|
||||
print(user.name)
|
||||
|
||||
if user is not None:
|
||||
print(user.name)
|
||||
2
resources/test/fixtures/E731.py
vendored
Normal file
2
resources/test/fixtures/E731.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
a = lambda x: x**2
|
||||
b = map(lambda x: 2 * x, range(3))
|
||||
6
resources/test/fixtures/pyproject.toml
vendored
6
resources/test/fixtures/pyproject.toml
vendored
@@ -4,6 +4,12 @@ exclude = ["excluded.py", "**/migrations"]
|
||||
select = [
|
||||
"E402",
|
||||
"E501",
|
||||
"E711",
|
||||
"E712",
|
||||
"E713",
|
||||
"E714",
|
||||
"E731",
|
||||
"E902",
|
||||
"F401",
|
||||
"F403",
|
||||
"F541",
|
||||
|
||||
143
src/check_ast.rs
143
src/check_ast.rs
@@ -1,9 +1,10 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::Path;
|
||||
|
||||
use itertools::izip;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
Location, Stmt, StmtKind, Suite,
|
||||
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
Location, Stmt, StmtKind, Suite, Unaryop,
|
||||
};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
@@ -11,7 +12,7 @@ use crate::ast_ops::{
|
||||
extract_all_names, Binding, BindingKind, Scope, ScopeKind, SourceCodeLocator,
|
||||
};
|
||||
use crate::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::checks::{Check, CheckCode, CheckKind, Fix};
|
||||
use crate::checks::{Check, CheckCode, CheckKind, Fix, RejectedCmpop};
|
||||
use crate::settings::Settings;
|
||||
use crate::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::{autofix, fixer, visitor};
|
||||
@@ -377,7 +378,20 @@ impl Visitor for Checker<'_> {
|
||||
self.seen_non_import = true;
|
||||
}
|
||||
}
|
||||
StmtKind::Delete { .. } | StmtKind::Assign { .. } | StmtKind::AnnAssign { .. } => {
|
||||
StmtKind::Assign { value, .. } => {
|
||||
self.seen_non_import = true;
|
||||
if self
|
||||
.settings
|
||||
.select
|
||||
.contains(CheckKind::DoNotAssignLambda.code())
|
||||
{
|
||||
if let ExprKind::Lambda { .. } = &value.node {
|
||||
self.checks
|
||||
.push(Check::new(CheckKind::DoNotAssignLambda, stmt.location));
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::Delete { .. } | StmtKind::AnnAssign { .. } => {
|
||||
self.seen_non_import = true;
|
||||
}
|
||||
_ => {}
|
||||
@@ -509,6 +523,127 @@ impl Visitor for Checker<'_> {
|
||||
}
|
||||
self.in_f_string = true;
|
||||
}
|
||||
ExprKind::UnaryOp { op, operand } => {
|
||||
if matches!(op, Unaryop::Not) {
|
||||
if let ExprKind::Compare { ops, .. } = &operand.node {
|
||||
match ops[..] {
|
||||
[Cmpop::In] => {
|
||||
if self.settings.select.contains(CheckKind::NotInTest.code()) {
|
||||
self.checks
|
||||
.push(Check::new(CheckKind::NotInTest, operand.location));
|
||||
}
|
||||
}
|
||||
[Cmpop::Is] => {
|
||||
if self.settings.select.contains(CheckKind::NotIsTest.code()) {
|
||||
self.checks
|
||||
.push(Check::new(CheckKind::NotIsTest, operand.location));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Compare {
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
} => {
|
||||
let op = ops.first().unwrap();
|
||||
let comparator = left;
|
||||
|
||||
// Check `left`.
|
||||
if self.settings.select.contains(&CheckCode::E711)
|
||||
&& matches!(
|
||||
comparator.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None,
|
||||
kind: None
|
||||
}
|
||||
)
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::E712) {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Bool(value),
|
||||
kind: None,
|
||||
} = comparator.node
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check each comparator in order.
|
||||
for (op, comparator) in izip!(ops, comparators) {
|
||||
if self.settings.select.contains(&CheckCode::E711)
|
||||
&& matches!(
|
||||
comparator.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None,
|
||||
kind: None
|
||||
}
|
||||
)
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::E712) {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Bool(value),
|
||||
kind: None,
|
||||
} = comparator.node
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
|
||||
@@ -10,6 +10,12 @@ use serde::{Deserialize, Serialize};
|
||||
pub enum CheckCode {
|
||||
E402,
|
||||
E501,
|
||||
E711,
|
||||
E712,
|
||||
E713,
|
||||
E714,
|
||||
E731,
|
||||
E902,
|
||||
F401,
|
||||
F403,
|
||||
F541,
|
||||
@@ -35,6 +41,12 @@ impl FromStr for CheckCode {
|
||||
match s {
|
||||
"E402" => Ok(CheckCode::E402),
|
||||
"E501" => Ok(CheckCode::E501),
|
||||
"E711" => Ok(CheckCode::E711),
|
||||
"E712" => Ok(CheckCode::E712),
|
||||
"E713" => Ok(CheckCode::E713),
|
||||
"E714" => Ok(CheckCode::E714),
|
||||
"E731" => Ok(CheckCode::E731),
|
||||
"E902" => Ok(CheckCode::E902),
|
||||
"F401" => Ok(CheckCode::F401),
|
||||
"F403" => Ok(CheckCode::F403),
|
||||
"F541" => Ok(CheckCode::F541),
|
||||
@@ -61,6 +73,12 @@ impl CheckCode {
|
||||
match self {
|
||||
CheckCode::E402 => "E402",
|
||||
CheckCode::E501 => "E501",
|
||||
CheckCode::E711 => "E711",
|
||||
CheckCode::E712 => "E712",
|
||||
CheckCode::E713 => "E713",
|
||||
CheckCode::E714 => "E714",
|
||||
CheckCode::E731 => "E731",
|
||||
CheckCode::E902 => "E902",
|
||||
CheckCode::F401 => "F401",
|
||||
CheckCode::F403 => "F403",
|
||||
CheckCode::F541 => "F541",
|
||||
@@ -85,6 +103,12 @@ impl CheckCode {
|
||||
match self {
|
||||
CheckCode::E402 => &LintSource::AST,
|
||||
CheckCode::E501 => &LintSource::Lines,
|
||||
CheckCode::E711 => &LintSource::AST,
|
||||
CheckCode::E712 => &LintSource::AST,
|
||||
CheckCode::E713 => &LintSource::AST,
|
||||
CheckCode::E714 => &LintSource::AST,
|
||||
CheckCode::E731 => &LintSource::AST,
|
||||
CheckCode::E902 => &LintSource::FileSystem,
|
||||
CheckCode::F401 => &LintSource::AST,
|
||||
CheckCode::F403 => &LintSource::AST,
|
||||
CheckCode::F541 => &LintSource::AST,
|
||||
@@ -109,21 +133,34 @@ impl CheckCode {
|
||||
pub enum LintSource {
|
||||
AST,
|
||||
Lines,
|
||||
FileSystem,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RejectedCmpop {
|
||||
Eq,
|
||||
NotEq,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CheckKind {
|
||||
AssertTuple,
|
||||
DefaultExceptNotLast,
|
||||
DoNotAssignLambda,
|
||||
DuplicateArgumentName,
|
||||
FStringMissingPlaceholders,
|
||||
IOError(String),
|
||||
IfTuple,
|
||||
ImportStarUsage,
|
||||
LineTooLong,
|
||||
ModuleImportNotAtTopOfFile,
|
||||
NoAssertEquals,
|
||||
NoneComparison(RejectedCmpop),
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
RaiseNotImplemented,
|
||||
ReturnOutsideFunction,
|
||||
TrueFalseComparison(bool, RejectedCmpop),
|
||||
UndefinedExport(String),
|
||||
UndefinedLocal(String),
|
||||
UndefinedName(String),
|
||||
@@ -141,13 +178,19 @@ impl CheckKind {
|
||||
CheckKind::DefaultExceptNotLast => "DefaultExceptNotLast",
|
||||
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
|
||||
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
|
||||
CheckKind::IOError(_) => "IOError",
|
||||
CheckKind::IfTuple => "IfTuple",
|
||||
CheckKind::ImportStarUsage => "ImportStarUsage",
|
||||
CheckKind::LineTooLong => "LineTooLong",
|
||||
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
|
||||
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
|
||||
CheckKind::NoAssertEquals => "NoAssertEquals",
|
||||
CheckKind::NoneComparison(_) => "NoneComparison",
|
||||
CheckKind::NotInTest => "NotInTest",
|
||||
CheckKind::NotIsTest => "NotIsTest",
|
||||
CheckKind::RaiseNotImplemented => "RaiseNotImplemented",
|
||||
CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction",
|
||||
CheckKind::TrueFalseComparison(_, _) => "TrueFalseComparison",
|
||||
CheckKind::UndefinedExport(_) => "UndefinedExport",
|
||||
CheckKind::UndefinedLocal(_) => "UndefinedLocal",
|
||||
CheckKind::UndefinedName(_) => "UndefinedName",
|
||||
@@ -165,13 +208,19 @@ impl CheckKind {
|
||||
CheckKind::DefaultExceptNotLast => &CheckCode::F707,
|
||||
CheckKind::DuplicateArgumentName => &CheckCode::F831,
|
||||
CheckKind::FStringMissingPlaceholders => &CheckCode::F541,
|
||||
CheckKind::IOError(_) => &CheckCode::E902,
|
||||
CheckKind::IfTuple => &CheckCode::F634,
|
||||
CheckKind::ImportStarUsage => &CheckCode::F403,
|
||||
CheckKind::LineTooLong => &CheckCode::E501,
|
||||
CheckKind::DoNotAssignLambda => &CheckCode::E731,
|
||||
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
|
||||
CheckKind::NoAssertEquals => &CheckCode::R002,
|
||||
CheckKind::NoneComparison(_) => &CheckCode::E711,
|
||||
CheckKind::NotInTest => &CheckCode::E713,
|
||||
CheckKind::NotIsTest => &CheckCode::E714,
|
||||
CheckKind::RaiseNotImplemented => &CheckCode::F901,
|
||||
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
|
||||
CheckKind::TrueFalseComparison(_, _) => &CheckCode::E712,
|
||||
CheckKind::UndefinedExport(_) => &CheckCode::F822,
|
||||
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
|
||||
CheckKind::UndefinedName(_) => &CheckCode::F821,
|
||||
@@ -197,21 +246,53 @@ impl CheckKind {
|
||||
CheckKind::FStringMissingPlaceholders => {
|
||||
"f-string without any placeholders".to_string()
|
||||
}
|
||||
CheckKind::IOError(name) => {
|
||||
format!("No such file or directory: `{name}`")
|
||||
}
|
||||
CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(),
|
||||
CheckKind::ImportStarUsage => "Unable to detect undefined names".to_string(),
|
||||
CheckKind::LineTooLong => "Line too long".to_string(),
|
||||
CheckKind::DoNotAssignLambda => {
|
||||
"Do not assign a lambda expression, use a def".to_string()
|
||||
}
|
||||
CheckKind::ModuleImportNotAtTopOfFile => {
|
||||
"Module level import not at top of file".to_string()
|
||||
}
|
||||
CheckKind::NoAssertEquals => {
|
||||
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
|
||||
}
|
||||
CheckKind::NoneComparison(op) => match op {
|
||||
RejectedCmpop::Eq => "Comparison to `None` should be `cond is None`".to_string(),
|
||||
RejectedCmpop::NotEq => {
|
||||
"Comparison to `None` should be `cond is not None`".to_string()
|
||||
}
|
||||
},
|
||||
CheckKind::NotInTest => "Test for membership should be `not in`".to_string(),
|
||||
CheckKind::NotIsTest => "Test for object identity should be `is not`".to_string(),
|
||||
CheckKind::RaiseNotImplemented => {
|
||||
"`raise NotImplemented` should be `raise NotImplementedError`".to_string()
|
||||
}
|
||||
CheckKind::ReturnOutsideFunction => {
|
||||
"a `return` statement outside of a function/method".to_string()
|
||||
}
|
||||
CheckKind::TrueFalseComparison(value, op) => match *value {
|
||||
true => match op {
|
||||
RejectedCmpop::Eq => {
|
||||
"Comparison to `True` should be `cond is True`".to_string()
|
||||
}
|
||||
RejectedCmpop::NotEq => {
|
||||
"Comparison to `True` should be `cond is not True`".to_string()
|
||||
}
|
||||
},
|
||||
false => match op {
|
||||
RejectedCmpop::Eq => {
|
||||
"Comparison to `False` should be `cond is False`".to_string()
|
||||
}
|
||||
RejectedCmpop::NotEq => {
|
||||
"Comparison to `False` should be `cond is not False`".to_string()
|
||||
}
|
||||
},
|
||||
},
|
||||
CheckKind::UndefinedExport(name) => {
|
||||
format!("Undefined name `{name}` in `__all__`")
|
||||
}
|
||||
@@ -241,13 +322,19 @@ impl CheckKind {
|
||||
CheckKind::DefaultExceptNotLast => false,
|
||||
CheckKind::DuplicateArgumentName => false,
|
||||
CheckKind::FStringMissingPlaceholders => false,
|
||||
CheckKind::IOError(_) => false,
|
||||
CheckKind::IfTuple => false,
|
||||
CheckKind::ImportStarUsage => false,
|
||||
CheckKind::DoNotAssignLambda => false,
|
||||
CheckKind::LineTooLong => false,
|
||||
CheckKind::ModuleImportNotAtTopOfFile => false,
|
||||
CheckKind::NoAssertEquals => true,
|
||||
CheckKind::NotInTest => false,
|
||||
CheckKind::NotIsTest => false,
|
||||
CheckKind::NoneComparison(_) => false,
|
||||
CheckKind::RaiseNotImplemented => false,
|
||||
CheckKind::ReturnOutsideFunction => false,
|
||||
CheckKind::TrueFalseComparison(_, _) => false,
|
||||
CheckKind::UndefinedExport(_) => false,
|
||||
CheckKind::UndefinedLocal(_) => false,
|
||||
CheckKind::UndefinedName(_) => false,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extern crate core;
|
||||
|
||||
mod ast_ops;
|
||||
mod autofix;
|
||||
mod builtins;
|
||||
|
||||
148
src/linter.rs
148
src/linter.rs
@@ -84,7 +84,7 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::checks::{Check, CheckCode, CheckKind, Fix};
|
||||
use crate::checks::{Check, CheckCode, CheckKind, Fix, RejectedCmpop};
|
||||
use crate::linter::check_path;
|
||||
use crate::{autofix, settings};
|
||||
|
||||
@@ -136,6 +136,152 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e711() -> Result<()> {
|
||||
let actual = check_path(
|
||||
Path::new("./resources/test/fixtures/E711.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E711]),
|
||||
},
|
||||
&autofix::Mode::Generate,
|
||||
)?;
|
||||
let expected = vec![
|
||||
Check {
|
||||
kind: CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
location: Location::new(1, 11),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::NoneComparison(RejectedCmpop::NotEq),
|
||||
location: Location::new(4, 4),
|
||||
fix: None,
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e712() -> Result<()> {
|
||||
let actual = check_path(
|
||||
Path::new("./resources/test/fixtures/E712.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E712]),
|
||||
},
|
||||
&autofix::Mode::Generate,
|
||||
)?;
|
||||
let expected = vec![
|
||||
Check {
|
||||
kind: CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
|
||||
location: Location::new(1, 11),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::TrueFalseComparison(false, RejectedCmpop::NotEq),
|
||||
location: Location::new(4, 4),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::TrueFalseComparison(false, RejectedCmpop::NotEq),
|
||||
location: Location::new(7, 11),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::TrueFalseComparison(true, RejectedCmpop::NotEq),
|
||||
location: Location::new(7, 20),
|
||||
fix: None,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e713() -> Result<()> {
|
||||
let actual = check_path(
|
||||
Path::new("./resources/test/fixtures/E713.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E713]),
|
||||
},
|
||||
&autofix::Mode::Generate,
|
||||
)?;
|
||||
let expected = vec![Check {
|
||||
kind: CheckKind::NotInTest,
|
||||
location: Location::new(2, 12),
|
||||
fix: None,
|
||||
}];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e714() -> Result<()> {
|
||||
let actual = check_path(
|
||||
Path::new("./resources/test/fixtures/E714.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E714]),
|
||||
},
|
||||
&autofix::Mode::Generate,
|
||||
)?;
|
||||
let expected = vec![Check {
|
||||
kind: CheckKind::NotIsTest,
|
||||
location: Location::new(1, 13),
|
||||
fix: None,
|
||||
}];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e731() -> Result<()> {
|
||||
let actual = check_path(
|
||||
Path::new("./resources/test/fixtures/E731.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E731]),
|
||||
},
|
||||
&autofix::Mode::Generate,
|
||||
)?;
|
||||
let expected = vec![Check {
|
||||
kind: CheckKind::DoNotAssignLambda,
|
||||
location: Location::new(1, 1),
|
||||
fix: None,
|
||||
}];
|
||||
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f401() -> Result<()> {
|
||||
let actual = check_path(
|
||||
|
||||
67
src/main.rs
67
src/main.rs
@@ -12,6 +12,7 @@ use rayon::prelude::*;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use ::ruff::checks::CheckCode;
|
||||
use ::ruff::checks::CheckKind;
|
||||
use ::ruff::fs::iter_python_files;
|
||||
use ::ruff::linter::lint_path;
|
||||
use ::ruff::logging::set_up_logging;
|
||||
@@ -54,6 +55,30 @@ struct Cli {
|
||||
ignore: Vec<CheckCode>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
fn check_for_updates() {
|
||||
use update_informer::{registry, Check};
|
||||
|
||||
let informer = update_informer::new(registry::PyPI, CARGO_PKG_NAME, CARGO_PKG_VERSION);
|
||||
|
||||
if let Some(new_version) = informer.check_version().ok().flatten() {
|
||||
let msg = format!(
|
||||
"A new version of {pkg_name} is available: v{pkg_version} -> {new_version}",
|
||||
pkg_name = CARGO_PKG_NAME.italic().cyan(),
|
||||
pkg_version = CARGO_PKG_VERSION,
|
||||
new_version = new_version.to_string().green()
|
||||
);
|
||||
|
||||
let cmd = format!(
|
||||
"Run to update: {cmd} {pkg_name}",
|
||||
cmd = "pip3 install --upgrade".green(),
|
||||
pkg_name = CARGO_PKG_NAME.green()
|
||||
);
|
||||
|
||||
println!("\n{msg}\n{cmd}");
|
||||
}
|
||||
}
|
||||
|
||||
fn run_once(
|
||||
files: &[PathBuf],
|
||||
settings: &Settings,
|
||||
@@ -62,7 +87,7 @@ fn run_once(
|
||||
) -> Result<Vec<Message>> {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let files: Vec<DirEntry> = files
|
||||
let paths: Vec<DirEntry> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude))
|
||||
.collect();
|
||||
@@ -70,7 +95,7 @@ fn run_once(
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
let start = Instant::now();
|
||||
let mut messages: Vec<Message> = files
|
||||
let mut messages: Vec<Message> = paths
|
||||
.par_iter()
|
||||
.map(|entry| {
|
||||
lint_path(entry.path(), settings, &cache.into(), &autofix.into()).unwrap_or_else(|e| {
|
||||
@@ -80,6 +105,20 @@ fn run_once(
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
if settings.select.contains(&CheckCode::E902) {
|
||||
for file in files {
|
||||
if !file.exists() {
|
||||
messages.push(Message {
|
||||
kind: CheckKind::IOError(file.to_string_lossy().to_string()),
|
||||
fixed: false,
|
||||
location: Default::default(),
|
||||
filename: file.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages.sort_unstable();
|
||||
let duration = start.elapsed();
|
||||
debug!("Checked files in: {:?}", duration);
|
||||
@@ -195,6 +234,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
report_once(&messages)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
check_for_updates();
|
||||
|
||||
if !messages.is_empty() && !cli.exit_zero {
|
||||
@@ -205,29 +245,6 @@ fn inner_main() -> Result<ExitCode> {
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
|
||||
fn check_for_updates() {
|
||||
use update_informer::{registry, Check};
|
||||
|
||||
let informer = update_informer::new(registry::PyPI, CARGO_PKG_NAME, CARGO_PKG_VERSION);
|
||||
|
||||
if let Some(new_version) = informer.check_version().ok().flatten() {
|
||||
let msg = format!(
|
||||
"A new version of {pkg_name} is available: v{pkg_version} -> {new_version}",
|
||||
pkg_name = CARGO_PKG_NAME.italic().cyan(),
|
||||
pkg_version = CARGO_PKG_VERSION,
|
||||
new_version = new_version.to_string().green()
|
||||
);
|
||||
|
||||
let cmd = format!(
|
||||
"Run to update: {cmd} {pkg_name}",
|
||||
cmd = "pip3 install --upgrade".green(),
|
||||
pkg_name = CARGO_PKG_NAME.green()
|
||||
);
|
||||
|
||||
println!("\n{msg}\n{cmd}");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match inner_main() {
|
||||
Ok(code) => code,
|
||||
|
||||
@@ -239,6 +239,12 @@ other-attribute = 1
|
||||
select: Some(BTreeSet::from([
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E731,
|
||||
CheckCode::E902,
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F541,
|
||||
|
||||
@@ -46,16 +46,29 @@ impl Settings {
|
||||
BTreeSet::from([
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E731,
|
||||
CheckCode::E902,
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F541,
|
||||
CheckCode::F631,
|
||||
CheckCode::F634,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
// Disable refactoring codes by default.
|
||||
// CheckCode::R001,
|
||||
// CheckCode::R002,
|
||||
])
|
||||
}),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user