diff --git a/README.md b/README.md index 6f5f8dc899..ef88d14af4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,16 @@ # rust-python-linter +A [Pyflakes](https://github.com/PyCQA/pyflakes)-inspired Python linter, written in Rust. + +Supports: + +- Python 3.10 +- True parallelism +- [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired + caching + ``` cargo fmt cargo clippy -cargo run test_sources +cargo run resources/test/src ``` diff --git a/test_sources/__init__.py b/resources/test/src/__init__.py similarity index 100% rename from test_sources/__init__.py rename to resources/test/src/__init__.py diff --git a/resources/test/src/bar.py b/resources/test/src/bar.py new file mode 100644 index 0000000000..d0c5c49e10 --- /dev/null +++ b/resources/test/src/bar.py @@ -0,0 +1,8 @@ +if (1, 2): + pass + +for _ in range(5): + if True: + pass + elif (1, 2): + pass diff --git a/test_sources/foo.py b/resources/test/src/foo.py similarity index 100% rename from test_sources/foo.py rename to resources/test/src/foo.py diff --git a/src/check.rs b/src/check.rs index 195da0cb65..811916a2ff 100644 --- a/src/check.rs +++ b/src/check.rs @@ -1,57 +1,94 @@ use std::path::Path; -use rustpython_parser::ast::{Located, StmtKind, Suite}; +use rustpython_parser::ast::{ExprKind, Stmt, StmtKind, Suite}; use crate::message::Message; -pub fn check_ast(path: &Path, python_ast: &Suite) -> Vec { +fn check_statement(path: &Path, stmt: &Stmt) -> Vec { let mut messages: Vec = vec![]; - for statement in python_ast { - let Located { - location, - custom: _, - node, - } = statement; - match node { - StmtKind::FunctionDef { .. } => {} - StmtKind::AsyncFunctionDef { .. } => {} - StmtKind::ClassDef { .. } => {} - StmtKind::Return { .. } => {} - StmtKind::Delete { .. } => {} - StmtKind::Assign { .. } => {} - StmtKind::AugAssign { .. } => {} - StmtKind::AnnAssign { .. } => {} - StmtKind::For { .. } => {} - StmtKind::AsyncFor { .. } => {} - StmtKind::While { .. } => {} - StmtKind::If { .. } => {} - StmtKind::With { .. } => {} - StmtKind::AsyncWith { .. } => {} - StmtKind::Raise { .. } => {} - StmtKind::Try { .. } => {} - StmtKind::Assert { .. } => {} - StmtKind::Import { .. } => {} - StmtKind::ImportFrom { - level: _, - module: _, - names, - } => { - for alias in names { - if alias.name == "*" { - messages.push(Message::ImportStarUsage { - filename: path.to_path_buf(), - location: *location, - }); - } + match &stmt.node { + StmtKind::FunctionDef { body, .. } => { + messages.extend(body.iter().flat_map(|stmt| check_statement(path, stmt))); + } + StmtKind::AsyncFunctionDef { body, .. } => { + messages.extend(body.iter().flat_map(|stmt| check_statement(path, stmt))); + } + StmtKind::ClassDef { body, .. } => { + messages.extend(body.iter().flat_map(|stmt| check_statement(path, stmt))); + } + StmtKind::Return { .. } => {} + StmtKind::Delete { .. } => {} + StmtKind::Assign { .. } => {} + StmtKind::AugAssign { .. } => {} + StmtKind::AnnAssign { .. } => {} + StmtKind::For { body, orelse, .. } => { + messages.extend(body.iter().flat_map(|stmt| check_statement(path, stmt))); + messages.extend(orelse.iter().flat_map(|stmt| check_statement(path, stmt))); + } + StmtKind::AsyncFor { body, orelse, .. } => { + messages.extend(body.iter().flat_map(|stmt| check_statement(path, stmt))); + messages.extend(orelse.iter().flat_map(|stmt| check_statement(path, stmt))); + } + StmtKind::While { body, orelse, .. } => { + messages.extend(body.iter().flat_map(|stmt| check_statement(path, stmt))); + messages.extend(orelse.iter().flat_map(|stmt| check_statement(path, stmt))); + } + StmtKind::If { test, body, orelse } => { + if let ExprKind::Tuple { .. } = test.node { + messages.push(Message::IfTuple { + filename: path.to_path_buf(), + location: stmt.location, + }); + } + messages.extend(body.iter().flat_map(|stmt| check_statement(path, stmt))); + messages.extend(orelse.iter().flat_map(|stmt| check_statement(path, stmt))); + } + StmtKind::With { body, .. } => { + messages.extend(body.iter().flat_map(|stmt| check_statement(path, stmt))); + } + StmtKind::AsyncWith { body, .. } => { + messages.extend(body.iter().flat_map(|stmt| check_statement(path, stmt))); + } + StmtKind::Raise { .. } => {} + StmtKind::Try { + body, + orelse, + finalbody, + .. + } => { + messages.extend(body.iter().flat_map(|stmt| check_statement(path, stmt))); + messages.extend(orelse.iter().flat_map(|stmt| check_statement(path, stmt))); + messages.extend( + finalbody + .iter() + .flat_map(|stmt| check_statement(path, stmt)), + ); + } + StmtKind::Assert { .. } => {} + StmtKind::Import { .. } => {} + StmtKind::ImportFrom { names, .. } => { + for alias in names { + if alias.name == "*" { + messages.push(Message::ImportStarUsage { + filename: path.to_path_buf(), + location: stmt.location, + }); } } - StmtKind::Global { .. } => {} - StmtKind::Nonlocal { .. } => {} - StmtKind::Expr { .. } => {} - StmtKind::Pass => {} - StmtKind::Break => {} - StmtKind::Continue => {} } + StmtKind::Global { .. } => {} + StmtKind::Nonlocal { .. } => {} + StmtKind::Expr { .. } => {} + StmtKind::Pass => {} + StmtKind::Break => {} + StmtKind::Continue => {} } messages } + +pub fn check_ast(path: &Path, python_ast: &Suite) -> Vec { + python_ast + .iter() + .flat_map(|stmt| check_statement(path, stmt)) + .collect() +} diff --git a/src/message.rs b/src/message.rs index 94e0b67b83..31a62709e7 100644 --- a/src/message.rs +++ b/src/message.rs @@ -27,26 +27,27 @@ pub enum Message { #[serde(with = "LocationDef")] location: Location, }, + IfTuple { + filename: PathBuf, + #[serde(with = "LocationDef")] + location: Location, + }, } impl Message { /// A four-letter shorthand code for the message. pub fn code(&self) -> &'static str { match self { - Message::ImportStarUsage { - filename: _, - location: _, - } => "F403", + Message::ImportStarUsage { .. } => "F403", + Message::IfTuple { .. } => "F634", } } /// The body text for the message. pub fn body(&self) -> &'static str { match self { - Message::ImportStarUsage { - filename: _, - location: _, - } => "Unable to detect undefined names", + Message::ImportStarUsage { .. } => "Unable to detect undefined names", + Message::IfTuple { .. } => "If test is a tuple, which is always `True`", } } } @@ -65,6 +66,17 @@ impl fmt::Display for Message { self.code().red().bold(), self.body() ), + Message::IfTuple { filename, location } => write!( + f, + "{}{}{}{}{}\t{}\t{}", + filename.to_string_lossy().white().bold(), + ":".cyan(), + location.column(), + ":".cyan(), + location.row(), + self.code().red().bold(), + self.body() + ), } } } diff --git a/test_sources/bar.py b/test_sources/bar.py deleted file mode 100644 index e69de29bb2..0000000000