Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81ae3bfc94 | ||
|
|
62e6feadc7 | ||
|
|
18a26e8f0b | ||
|
|
2371de3895 | ||
|
|
e3c8f61340 | ||
|
|
f6628ae100 | ||
|
|
989ed9c10b |
@@ -1,5 +1,5 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.32
|
||||
rev: v0.0.33
|
||||
hooks:
|
||||
- id: lint
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1744,7 +1744,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.32"
|
||||
version = "0.0.33"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.32"
|
||||
version = "0.0.33"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
27
README.md
27
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.32
|
||||
rev: v0.0.33
|
||||
hooks:
|
||||
- id: lint
|
||||
```
|
||||
@@ -86,7 +86,7 @@ ruff path/to/code/ --select F401 F403
|
||||
See `ruff --help` for more:
|
||||
|
||||
```shell
|
||||
ruff (v0.0.32)
|
||||
ruff (v0.0.33)
|
||||
An extremely fast Python linter.
|
||||
|
||||
USAGE:
|
||||
@@ -96,16 +96,17 @@ ARGS:
|
||||
<FILES>...
|
||||
|
||||
OPTIONS:
|
||||
-e, --exit-zero Exit with status code "0", even upon detecting errors
|
||||
-f, --fix Attempt to automatically fix lint errors
|
||||
-h, --help Print help information
|
||||
--ignore <IGNORE>... Comma-separated list of error codes to ignore
|
||||
-n, --no-cache Disable cache reads
|
||||
-q, --quiet Disable all logging (but still exit with status code "1" upon
|
||||
detecting errors)
|
||||
--select <SELECT>... Comma-separated list of error codes to enable
|
||||
-v, --verbose Enable verbose logging
|
||||
-w, --watch Run in watch mode by re-running whenever files change
|
||||
-e, --exit-zero Exit with status code "0", even upon detecting errors
|
||||
--exclude <EXCLUDE>... List of file and/or directory patterns to exclude from checks
|
||||
-f, --fix Attempt to automatically fix lint errors
|
||||
-h, --help Print help information
|
||||
--ignore <IGNORE>... List of error codes to ignore
|
||||
-n, --no-cache Disable cache reads
|
||||
-q, --quiet Disable all logging (but still exit with status code "1" upon
|
||||
detecting errors)
|
||||
--select <SELECT>... List of error codes to enable
|
||||
-v, --verbose Enable verbose logging
|
||||
-w, --watch Run in watch mode by re-running whenever files change
|
||||
```
|
||||
|
||||
### Compatibility with Black
|
||||
@@ -206,7 +207,7 @@ git clone --branch 3.10 https://github.com/python/cpython.git resources/test/cpy
|
||||
Add this `pyproject.toml` to the CPython directory:
|
||||
|
||||
```toml
|
||||
[tool.linter]
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
exclude = [
|
||||
"Lib/lib2to3/tests/data/bom.py",
|
||||
|
||||
@@ -66,6 +66,40 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
names
|
||||
}
|
||||
|
||||
/// Check if a node is parent of a conditional branch.
|
||||
pub fn on_conditional_branch(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
for index in parent_stack.iter().rev() {
|
||||
let parent = parents[*index];
|
||||
if matches!(parent.node, StmtKind::If { .. })
|
||||
|| matches!(parent.node, StmtKind::While { .. })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if let StmtKind::Expr { value } = &parent.node {
|
||||
if matches!(value.node, ExprKind::IfExp { .. }) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if a node is in a nested block.
|
||||
pub fn in_nested_block(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
for index in parent_stack.iter().rev() {
|
||||
let parent = parents[*index];
|
||||
if matches!(parent.node, StmtKind::Try { .. })
|
||||
|| matches!(parent.node, StmtKind::If { .. })
|
||||
|| matches!(parent.node, StmtKind::With { .. })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Struct used to efficiently slice source code at (row, column) Locations.
|
||||
pub struct SourceCodeLocator<'a> {
|
||||
content: &'a str,
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::ast::operations::{extract_all_names, SourceCodeLocator};
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{Binding, BindingKind, Scope, ScopeKind};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::{checks, visitor};
|
||||
use crate::ast::{checks, operations, visitor};
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
@@ -37,6 +37,7 @@ struct Checker<'a> {
|
||||
deferred_annotations: Vec<(Location, &'a str)>,
|
||||
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>,
|
||||
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
|
||||
deferred_assignments: Vec<usize>,
|
||||
// Derivative state.
|
||||
in_f_string: bool,
|
||||
in_annotation: bool,
|
||||
@@ -66,6 +67,7 @@ impl<'a> Checker<'a> {
|
||||
deferred_annotations: vec![],
|
||||
deferred_functions: vec![],
|
||||
deferred_lambdas: vec![],
|
||||
deferred_assignments: vec![],
|
||||
in_f_string: false,
|
||||
in_annotation: false,
|
||||
in_literal: false,
|
||||
@@ -98,6 +100,41 @@ where
|
||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||
self.push_parent(stmt);
|
||||
|
||||
// Track whether we've seen docstrings, non-imports, etc.
|
||||
match &stmt.node {
|
||||
StmtKind::Import { .. } => {}
|
||||
StmtKind::ImportFrom { .. } => {}
|
||||
StmtKind::Expr { value } => {
|
||||
if !self.seen_docstring
|
||||
&& stmt.location.column() == 1
|
||||
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
|
||||
{
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
} = &value.node
|
||||
{
|
||||
self.seen_docstring = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.seen_non_import
|
||||
&& stmt.location.column() == 1
|
||||
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
|
||||
{
|
||||
self.seen_non_import = true;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if !self.seen_non_import
|
||||
&& stmt.location.column() == 1
|
||||
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
|
||||
{
|
||||
self.seen_non_import = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-visit.
|
||||
match &stmt.node {
|
||||
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
|
||||
@@ -357,7 +394,6 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::AugAssign { target, .. } => {
|
||||
self.seen_non_import = true;
|
||||
self.handle_node_load(target);
|
||||
}
|
||||
StmtKind::If { test, .. } => {
|
||||
@@ -368,7 +404,6 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::Assert { test, .. } => {
|
||||
self.seen_non_import = true;
|
||||
if self.settings.select.contains(CheckKind::AssertTuple.code()) {
|
||||
if let Some(check) = checks::check_assert_tuple(test, stmt.location) {
|
||||
self.checks.push(check);
|
||||
@@ -382,21 +417,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::Expr { value } => {
|
||||
if !self.seen_docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
} = &value.node
|
||||
{
|
||||
self.seen_docstring = true;
|
||||
}
|
||||
} else {
|
||||
self.seen_non_import = true;
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { value, .. } => {
|
||||
self.seen_non_import = true;
|
||||
if self.settings.select.contains(&CheckCode::E731) {
|
||||
if let Some(check) = checks::check_do_not_assign_lambda(value, stmt.location) {
|
||||
self.checks.push(check);
|
||||
@@ -404,7 +425,6 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign { value, .. } => {
|
||||
self.seen_non_import = true;
|
||||
if self.settings.select.contains(&CheckCode::E731) {
|
||||
if let Some(value) = value {
|
||||
if let Some(check) =
|
||||
@@ -415,9 +435,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::Delete { .. } => {
|
||||
self.seen_non_import = true;
|
||||
}
|
||||
StmtKind::Delete { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -986,6 +1004,11 @@ impl<'a> Checker<'a> {
|
||||
|
||||
fn handle_node_delete(&mut self, expr: &Expr) {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
// Check if we're on a conditional branch.
|
||||
if operations::on_conditional_branch(&self.parent_stack, &self.parents) {
|
||||
return;
|
||||
}
|
||||
|
||||
let scope =
|
||||
&mut self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if scope.values.remove(id).is_none() && self.settings.select.contains(&CheckCode::F821)
|
||||
@@ -1033,11 +1056,8 @@ impl<'a> Checker<'a> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F841) {
|
||||
let scope =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
self.checks.extend(checks::check_unused_variables(scope));
|
||||
}
|
||||
self.deferred_assignments
|
||||
.push(*self.scope_stack.last().expect("No current scope found."));
|
||||
|
||||
self.pop_scope();
|
||||
}
|
||||
@@ -1056,16 +1076,23 @@ impl<'a> Checker<'a> {
|
||||
self.visit_expr(body);
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F841) {
|
||||
let scope =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
self.checks.extend(checks::check_unused_variables(scope));
|
||||
}
|
||||
self.deferred_assignments
|
||||
.push(*self.scope_stack.last().expect("No current scope found."));
|
||||
|
||||
self.pop_scope();
|
||||
}
|
||||
}
|
||||
|
||||
fn check_deferred_assignments(&mut self) {
|
||||
while !self.deferred_assignments.is_empty() {
|
||||
let index = self.deferred_assignments.pop().unwrap();
|
||||
if self.settings.select.contains(&CheckCode::F841) {
|
||||
self.checks
|
||||
.extend(checks::check_unused_variables(&self.scopes[index]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_dead_scopes(&mut self) {
|
||||
if !self.settings.select.contains(&CheckCode::F822)
|
||||
&& !self.settings.select.contains(&CheckCode::F401)
|
||||
@@ -1143,6 +1170,7 @@ pub fn check_ast(
|
||||
// Check any deferred statements.
|
||||
checker.check_deferred_functions();
|
||||
checker.check_deferred_lambdas();
|
||||
checker.check_deferred_assignments();
|
||||
let mut allocator = vec![];
|
||||
checker.check_deferred_annotations(path, &mut allocator);
|
||||
|
||||
|
||||
11
src/main.rs
11
src/main.rs
@@ -6,6 +6,7 @@ use std::time::Instant;
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, ValueHint};
|
||||
use colored::Colorize;
|
||||
use glob::Pattern;
|
||||
use log::{debug, error};
|
||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||
use rayon::prelude::*;
|
||||
@@ -47,12 +48,15 @@ struct Cli {
|
||||
/// Disable cache reads.
|
||||
#[clap(short, long, action)]
|
||||
no_cache: bool,
|
||||
/// Comma-separated list of error codes to enable.
|
||||
/// List of error codes to enable.
|
||||
#[clap(long, multiple = true)]
|
||||
select: Vec<CheckCode>,
|
||||
/// Comma-separated list of error codes to ignore.
|
||||
/// List of error codes to ignore.
|
||||
#[clap(long, multiple = true)]
|
||||
ignore: Vec<CheckCode>,
|
||||
/// List of file and/or directory patterns to exclude from checks.
|
||||
#[clap(long, multiple = true)]
|
||||
exclude: Vec<Pattern>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
@@ -188,6 +192,9 @@ fn inner_main() -> Result<ExitCode> {
|
||||
if !cli.ignore.is_empty() {
|
||||
settings.ignore(&cli.ignore);
|
||||
}
|
||||
if !cli.exclude.is_empty() {
|
||||
settings.exclude(cli.exclude);
|
||||
}
|
||||
|
||||
if cli.watch {
|
||||
if cli.fix {
|
||||
|
||||
@@ -20,7 +20,7 @@ impl Ord for Message {
|
||||
(&self.filename, self.location.row(), self.location.column()).cmp(&(
|
||||
&other.filename,
|
||||
other.location.row(),
|
||||
self.location.column(),
|
||||
other.location.column(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +94,8 @@ impl Settings {
|
||||
self.select.remove(code);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exclude(&mut self, exclude: Vec<Pattern>) {
|
||||
self.exclude = exclude;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user