Compare commits
8 Commits
0.14.8
...
charlie/cs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8bb0632c5 | ||
|
|
d7412af996 | ||
|
|
b1f734f445 | ||
|
|
a9d1d17eac | ||
|
|
0cfa2d617a | ||
|
|
21d39a9b44 | ||
|
|
9ad82a6127 | ||
|
|
4617c997bb |
914
Cargo.lock
generated
914
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ edition = "2021"
|
||||
name = "ruff"
|
||||
|
||||
[dependencies]
|
||||
libcst = { path = "../LibCST/native/libcst" }
|
||||
anyhow = { version = "1.0.60" }
|
||||
bincode = { version = "1.3.3" }
|
||||
cacache = { version = "10.0.1" }
|
||||
@@ -29,6 +30,8 @@ serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
toml = { version = "0.5.9" }
|
||||
walkdir = { version = "2.3.2" }
|
||||
bumpalo = "3.11.0"
|
||||
bat = "0.21.0"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
10
remove_object_base.py
Normal file
10
remove_object_base.py
Normal file
@@ -0,0 +1,10 @@
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
|
||||
class Bar(Foo, object):
|
||||
pass
|
||||
|
||||
|
||||
class Baz(Foo, Bar, object):
|
||||
pass
|
||||
1
remove_redundant_fstring.py
Normal file
1
remove_redundant_fstring.py
Normal file
@@ -0,0 +1 @@
|
||||
bad: str = f"bad" + "bad"
|
||||
@@ -6,3 +6,7 @@ for _ in range(5):
|
||||
pass
|
||||
elif (3, 4):
|
||||
pass
|
||||
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
@@ -4,7 +4,7 @@ use rustpython_parser::ast::{
|
||||
PatternKind, Stmt, StmtKind, Unaryop, Withitem,
|
||||
};
|
||||
|
||||
pub trait Visitor {
|
||||
pub trait ASTVisitor {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ pub trait Visitor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
pub fn walk_stmt<V: ASTVisitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { args, body, .. } => {
|
||||
visitor.visit_arguments(args);
|
||||
@@ -238,7 +238,7 @@ pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, stmt: &Stmt) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
|
||||
pub fn walk_expr<V: ASTVisitor + ?Sized>(visitor: &mut V, expr: &Expr) {
|
||||
match &expr.node {
|
||||
ExprKind::BoolOp { op, values } => {
|
||||
visitor.visit_boolop(op);
|
||||
@@ -399,7 +399,7 @@ pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, expr: &Expr) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_constant<V: Visitor + ?Sized>(visitor: &mut V, constant: &Constant) {
|
||||
pub fn walk_constant<V: ASTVisitor + ?Sized>(visitor: &mut V, constant: &Constant) {
|
||||
if let Constant::Tuple(constants) = constant {
|
||||
for constant in constants {
|
||||
visitor.visit_constant(constant)
|
||||
@@ -407,7 +407,7 @@ pub fn walk_constant<V: Visitor + ?Sized>(visitor: &mut V, constant: &Constant)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_comprehension<V: Visitor + ?Sized>(visitor: &mut V, comprehension: &Comprehension) {
|
||||
pub fn walk_comprehension<V: ASTVisitor + ?Sized>(visitor: &mut V, comprehension: &Comprehension) {
|
||||
visitor.visit_expr(&comprehension.target, None);
|
||||
visitor.visit_expr(&comprehension.iter, None);
|
||||
for expr in &comprehension.ifs {
|
||||
@@ -415,7 +415,7 @@ pub fn walk_comprehension<V: Visitor + ?Sized>(visitor: &mut V, comprehension: &
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_excepthandler<V: Visitor + ?Sized>(visitor: &mut V, excepthandler: &Excepthandler) {
|
||||
pub fn walk_excepthandler<V: ASTVisitor + ?Sized>(visitor: &mut V, excepthandler: &Excepthandler) {
|
||||
match &excepthandler.node {
|
||||
ExcepthandlerKind::ExceptHandler { type_, body, .. } => {
|
||||
if let Some(expr) = type_ {
|
||||
@@ -428,7 +428,7 @@ pub fn walk_excepthandler<V: Visitor + ?Sized>(visitor: &mut V, excepthandler: &
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_arguments<V: Visitor + ?Sized>(visitor: &mut V, arguments: &Arguments) {
|
||||
pub fn walk_arguments<V: ASTVisitor + ?Sized>(visitor: &mut V, arguments: &Arguments) {
|
||||
for arg in &arguments.posonlyargs {
|
||||
visitor.visit_arg(arg);
|
||||
}
|
||||
@@ -452,24 +452,24 @@ pub fn walk_arguments<V: Visitor + ?Sized>(visitor: &mut V, arguments: &Argument
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_arg<V: Visitor + ?Sized>(visitor: &mut V, arg: &Arg) {
|
||||
pub fn walk_arg<V: ASTVisitor + ?Sized>(visitor: &mut V, arg: &Arg) {
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
visitor.visit_annotation(expr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_keyword<V: Visitor + ?Sized>(visitor: &mut V, keyword: &Keyword) {
|
||||
pub fn walk_keyword<V: ASTVisitor + ?Sized>(visitor: &mut V, keyword: &Keyword) {
|
||||
visitor.visit_expr(&keyword.node.value, None);
|
||||
}
|
||||
|
||||
pub fn walk_withitem<V: Visitor + ?Sized>(visitor: &mut V, withitem: &Withitem) {
|
||||
pub fn walk_withitem<V: ASTVisitor + ?Sized>(visitor: &mut V, withitem: &Withitem) {
|
||||
visitor.visit_expr(&withitem.context_expr, None);
|
||||
if let Some(expr) = &withitem.optional_vars {
|
||||
visitor.visit_expr(expr, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_match_case<V: Visitor + ?Sized>(visitor: &mut V, match_case: &MatchCase) {
|
||||
pub fn walk_match_case<V: ASTVisitor + ?Sized>(visitor: &mut V, match_case: &MatchCase) {
|
||||
visitor.visit_pattern(&match_case.pattern);
|
||||
if let Some(expr) = &match_case.guard {
|
||||
visitor.visit_expr(expr, None);
|
||||
@@ -479,7 +479,7 @@ pub fn walk_match_case<V: Visitor + ?Sized>(visitor: &mut V, match_case: &MatchC
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_pattern<V: Visitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
|
||||
pub fn walk_pattern<V: ASTVisitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
|
||||
match &pattern.node {
|
||||
PatternKind::MatchValue { value } => visitor.visit_expr(value, None),
|
||||
PatternKind::MatchSingleton { value } => visitor.visit_constant(value),
|
||||
@@ -527,24 +527,24 @@ pub fn walk_pattern<V: Visitor + ?Sized>(visitor: &mut V, pattern: &Pattern) {
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_expr_context<V: Visitor + ?Sized>(visitor: &mut V, expr_context: &ExprContext) {}
|
||||
pub fn walk_expr_context<V: ASTVisitor + ?Sized>(visitor: &mut V, expr_context: &ExprContext) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_boolop<V: Visitor + ?Sized>(visitor: &mut V, boolop: &Boolop) {}
|
||||
pub fn walk_boolop<V: ASTVisitor + ?Sized>(visitor: &mut V, boolop: &Boolop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_operator<V: Visitor + ?Sized>(visitor: &mut V, operator: &Operator) {}
|
||||
pub fn walk_operator<V: ASTVisitor + ?Sized>(visitor: &mut V, operator: &Operator) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_unaryop<V: Visitor + ?Sized>(visitor: &mut V, unaryop: &Unaryop) {}
|
||||
pub fn walk_unaryop<V: ASTVisitor + ?Sized>(visitor: &mut V, unaryop: &Unaryop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_cmpop<V: Visitor + ?Sized>(visitor: &mut V, cmpop: &Cmpop) {}
|
||||
pub fn walk_cmpop<V: ASTVisitor + ?Sized>(visitor: &mut V, cmpop: &Cmpop) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
pub fn walk_alias<V: Visitor + ?Sized>(visitor: &mut V, alias: &Alias) {}
|
||||
pub fn walk_alias<V: ASTVisitor + ?Sized>(visitor: &mut V, alias: &Alias) {}
|
||||
@@ -7,11 +7,11 @@ use rustpython_parser::ast::{
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast_ops::{extract_all_names, Binding, BindingKind, Scope, ScopeKind};
|
||||
use crate::ast_visitor;
|
||||
use crate::ast_visitor::{walk_excepthandler, ASTVisitor};
|
||||
use crate::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::settings::Settings;
|
||||
use crate::visitor;
|
||||
use crate::visitor::{walk_excepthandler, Visitor};
|
||||
|
||||
struct Checker<'a> {
|
||||
settings: &'a Settings,
|
||||
@@ -37,7 +37,7 @@ impl Checker<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Visitor for Checker<'_> {
|
||||
impl ASTVisitor for Checker<'_> {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
match &stmt.node {
|
||||
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
|
||||
@@ -255,7 +255,7 @@ impl Visitor for Checker<'_> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
visitor::walk_stmt(self, stmt);
|
||||
ast_visitor::walk_stmt(self, stmt);
|
||||
|
||||
match &stmt.node {
|
||||
StmtKind::ClassDef { .. } => {
|
||||
@@ -359,7 +359,7 @@ impl Visitor for Checker<'_> {
|
||||
_ => {}
|
||||
};
|
||||
|
||||
visitor::walk_expr(self, expr);
|
||||
ast_visitor::walk_expr(self, expr);
|
||||
|
||||
match &expr.node {
|
||||
ExprKind::GeneratorExp { .. }
|
||||
@@ -466,7 +466,7 @@ impl Visitor for Checker<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
visitor::walk_arguments(self, arguments);
|
||||
ast_visitor::walk_arguments(self, arguments);
|
||||
}
|
||||
|
||||
fn visit_arg(&mut self, arg: &Arg) {
|
||||
@@ -478,7 +478,7 @@ impl Visitor for Checker<'_> {
|
||||
location: arg.location,
|
||||
},
|
||||
);
|
||||
visitor::walk_arg(self, arg);
|
||||
ast_visitor::walk_arg(self, arg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
164
src/check_cst.rs
Normal file
164
src/check_cst.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bat::PrettyPrinter;
|
||||
use bumpalo::Bump;
|
||||
use libcst_native::{
|
||||
Arg, ClassDef, Codegen, Expression, FormattedStringContent, If, Module, SimpleString,
|
||||
};
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::cst_visitor;
|
||||
use crate::cst_visitor::CSTVisitor;
|
||||
use crate::settings::Settings;
|
||||
|
||||
enum ScopeKind {
|
||||
Class,
|
||||
Function,
|
||||
Generator,
|
||||
Module,
|
||||
}
|
||||
|
||||
struct Scope {
|
||||
kind: ScopeKind,
|
||||
values: BTreeMap<String, Binding>,
|
||||
}
|
||||
|
||||
enum BindingKind {
|
||||
Argument,
|
||||
Assignment,
|
||||
ClassDefinition,
|
||||
Definition,
|
||||
FutureImportation,
|
||||
Importation,
|
||||
StarImportation,
|
||||
SubmoduleImportation,
|
||||
}
|
||||
|
||||
struct Binding {
|
||||
kind: BindingKind,
|
||||
name: String,
|
||||
location: Location,
|
||||
used: bool,
|
||||
}
|
||||
|
||||
struct Checker<'a> {
|
||||
settings: &'a Settings,
|
||||
checks: Vec<Check>,
|
||||
arena: Vec<String>,
|
||||
}
|
||||
|
||||
impl Checker<'_> {
|
||||
pub fn new(settings: &Settings) -> Checker {
|
||||
Checker {
|
||||
settings,
|
||||
checks: vec![],
|
||||
arena: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const QUOTE: &str = "\"";
|
||||
|
||||
impl<'b> CSTVisitor for Checker<'_> {
|
||||
fn visit_Expression<'a>(&mut self, node: &'a Expression<'a>) -> Expression<'a> {
|
||||
match node {
|
||||
Expression::FormattedString(node) => match &node.parts[..] {
|
||||
[node] => match node {
|
||||
FormattedStringContent::Text(node) => {
|
||||
self.arena.push(format!("\"{}\"", node.value));
|
||||
return Expression::SimpleString(Box::new(SimpleString {
|
||||
value: node.value,
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
cst_visitor::walk_Expression(self, node)
|
||||
}
|
||||
|
||||
fn visit_ClassDef<'a>(&mut self, node: &'a ClassDef<'a>) -> ClassDef<'a> {
|
||||
let mut bases: Vec<Arg<'a>> = node
|
||||
.bases
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|node| {
|
||||
if let Expression::Name(node) = &node.value {
|
||||
node.value != "object"
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut transformed: ClassDef<'a> = node.clone();
|
||||
if bases.is_empty() {
|
||||
transformed.lpar = None;
|
||||
transformed.rpar = None;
|
||||
} else {
|
||||
let node = bases.last_mut().unwrap();
|
||||
node.comma = None;
|
||||
}
|
||||
transformed.bases = bases;
|
||||
transformed
|
||||
}
|
||||
|
||||
fn visit_If(&mut self, node: &If) {
|
||||
if let Expression::Tuple { .. } = node.test {
|
||||
self.checks.push(Check {
|
||||
kind: CheckKind::IfTuple,
|
||||
location: Default::default(),
|
||||
});
|
||||
}
|
||||
cst_visitor::walk_If(self, node);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_cst<'a>(python_cst: &'a Module<'a>, settings: &Settings) -> Vec<Check> {
|
||||
// // Create a new arena to bump allocate into.
|
||||
// let bump = Bump::new();
|
||||
//
|
||||
// // Allocate values into the arena.
|
||||
// let scooter = bump.alloc(python_cst.clone());
|
||||
|
||||
let mut x = python_cst.clone();
|
||||
let mut s = Default::default();
|
||||
x.codegen(&mut s);
|
||||
|
||||
println!("Starting from source:");
|
||||
println!("```");
|
||||
let source = s.to_string().into_bytes();
|
||||
PrettyPrinter::new()
|
||||
.input_from_bytes(&source)
|
||||
.language("python")
|
||||
.print()
|
||||
.unwrap();
|
||||
println!("```");
|
||||
|
||||
let mut checker = Checker::new(settings);
|
||||
let mut transformed = checker.visit_Module(python_cst);
|
||||
|
||||
let mut state = Default::default();
|
||||
transformed.codegen(&mut state);
|
||||
|
||||
println!("");
|
||||
println!("Generated output:");
|
||||
println!("```");
|
||||
let source = state.to_string().into_bytes();
|
||||
PrettyPrinter::new()
|
||||
.input_from_bytes(&source)
|
||||
.language("python")
|
||||
.print()
|
||||
.unwrap();
|
||||
println!("```");
|
||||
|
||||
checker.checks
|
||||
}
|
||||
1039
src/cst_visitor.rs
Normal file
1039
src/cst_visitor.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,15 @@
|
||||
mod ast_ops;
|
||||
mod ast_visitor;
|
||||
mod builtins;
|
||||
mod cache;
|
||||
pub mod check_ast;
|
||||
mod check_cst;
|
||||
mod check_lines;
|
||||
pub mod checks;
|
||||
mod cst_visitor;
|
||||
pub mod fs;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
pub mod message;
|
||||
mod pyproject;
|
||||
pub mod settings;
|
||||
mod visitor;
|
||||
|
||||
@@ -2,21 +2,19 @@ use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::check_ast::check_ast;
|
||||
use crate::check_lines::check_lines;
|
||||
use crate::checks::{Check, LintSource};
|
||||
use crate::check_cst::check_cst;
|
||||
use crate::checks::Check;
|
||||
use crate::message::Message;
|
||||
use crate::settings::Settings;
|
||||
use crate::{cache, fs};
|
||||
|
||||
pub fn check_path(path: &Path, settings: &Settings, mode: &cache::Mode) -> Result<Vec<Message>> {
|
||||
// Check the cache.
|
||||
if let Some(messages) = cache::get(path, settings, mode) {
|
||||
debug!("Cache hit for: {}", path.to_string_lossy());
|
||||
return Ok(messages);
|
||||
}
|
||||
// // Check the cache.
|
||||
// if let Some(messages) = cache::get(path, settings, mode) {
|
||||
// debug!("Cache hit for: {}", path.to_string_lossy());
|
||||
// return Ok(messages);
|
||||
// }
|
||||
|
||||
// Read the file from disk.
|
||||
let contents = fs::read_file(path)?;
|
||||
@@ -24,32 +22,44 @@ pub fn check_path(path: &Path, settings: &Settings, mode: &cache::Mode) -> Resul
|
||||
// Aggregate all checks.
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
// Run the AST-based checks.
|
||||
if settings
|
||||
.select
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::AST))
|
||||
{
|
||||
let path = path.to_string_lossy();
|
||||
let python_ast = parser::parse_program(&contents, &path)?;
|
||||
checks.extend(check_ast(&python_ast, settings, &path));
|
||||
}
|
||||
// Run the CST-based checks.
|
||||
let _ = match libcst_native::parse_module(&contents, None) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
return Err(anyhow::anyhow!("Failed to parse"));
|
||||
}
|
||||
};
|
||||
|
||||
// Run the lines-based checks.
|
||||
check_lines(&mut checks, &contents, settings);
|
||||
Ok(vec![])
|
||||
// checks.extend(check_cst(&python_cst, settings));
|
||||
|
||||
// Convert to messages.
|
||||
let messages: Vec<Message> = checks
|
||||
.into_iter()
|
||||
.map(|check| Message {
|
||||
kind: check.kind,
|
||||
location: check.location,
|
||||
filename: path.to_string_lossy().to_string(),
|
||||
})
|
||||
.collect();
|
||||
cache::set(path, settings, &messages, mode);
|
||||
|
||||
Ok(messages)
|
||||
// // Run the AST-based checks.
|
||||
// if settings
|
||||
// .select
|
||||
// .iter()
|
||||
// .any(|check_code| matches!(check_code.lint_source(), LintSource::AST))
|
||||
// {
|
||||
// let path = path.to_string_lossy();
|
||||
// let python_ast = parser::parse_program(&contents, &path)?;
|
||||
// checks.extend(check_ast(&python_ast, settings, &path));
|
||||
// }
|
||||
//
|
||||
// // Run the lines-based checks.
|
||||
// check_lines(&mut checks, &contents, settings);
|
||||
//
|
||||
// // Convert to messages.
|
||||
// let messages: Vec<Message> = checks
|
||||
// .into_iter()
|
||||
// .map(|check| Message {
|
||||
// kind: check.kind,
|
||||
// location: check.location,
|
||||
// filename: path.to_string_lossy().to_string(),
|
||||
// })
|
||||
// .collect();
|
||||
//
|
||||
// cache::set(path, settings, &messages, mode);
|
||||
//
|
||||
// Ok(messages)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -77,7 +77,7 @@ fn run_once(files: &[PathBuf], settings: &Settings, cache: bool) -> Result<Vec<M
|
||||
}
|
||||
|
||||
fn report_once(messages: &[Message]) -> Result<()> {
|
||||
println!("Found {} error(s).", messages.len());
|
||||
// println!("Found {} error(s).", messages.len());
|
||||
|
||||
if !messages.is_empty() {
|
||||
println!();
|
||||
|
||||
Reference in New Issue
Block a user