From ea9fde14f6b763e4edca53b8cf66acbd0352fbd4 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 28 Aug 2022 14:21:44 -0400 Subject: [PATCH] Fix pyproject.toml key --- README.md | 58 +++++++++++++++++++++++-------- pyproject.toml | 2 +- resources/test/src/pyproject.toml | 12 ++++++- scripts/poetry.lock | 35 ++++++++++--------- scripts/pyproject.toml | 2 +- src/check_ast.rs | 22 ++++++------ src/pyproject.rs | 48 ++++++++++++++----------- 7 files changed, 114 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 29c21fca0e..f275e5ffd0 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,24 @@ # ruff -A performance-focused, [Pyflakes](https://github.com/PyCQA/pyflakes)-inspired Python linter, written -in Rust. +[![Actions status](https://github.com/charliermarsh/ruff/workflows/CI/badge.svg)](https://github.com/charliermarsh/ruff/actions) +[![PyPI version](https://badge.fury.io/py/ruff.svg)](https://badge.fury.io/py/ruff) -Features: +An extremely fast Python linter, written in Rust. -- Python 3.10 compatibility -- [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache semantics -- [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` semantics -- `pyproject.toml` support +Major features: -## Installation +- 10-100x faster than your current linter. +- Python 3.10 compatibility. +- [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache semantics. +- [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` semantics. +- `pyproject.toml` support. + +`ruff` is a proof-of-concept and not yet intended for production use. It supports only a small +subset of the Flake8 rules, and may crash on your codebase. + +## Installation and usage + +### Installation Available as [`ruff`](https://pypi.org/project/ruff/) on PyPI: @@ -18,22 +26,35 @@ Available as [`ruff`](https://pypi.org/project/ruff/) on PyPI: pip install ruff ``` -## Usage +### Usage -To run the linter, try any of the following: +To run `ruff`, try any of the following: ```shell ruff path/to/code/to/check.py -# ...or... ruff path/to/code/ -# ...or... ruff path/to/code/*.py ``` -You can also run in `--watch` mode to automatically re-run the linter on-change with, e.g.: +You can run `ruff` in `--watch` mode to automatically re-run on-change: ```shell -ruff path/to/code/ --watch +ruff --watch path/to/code/ +``` + +## Configuration + +`ruff` is configurable both via `pyproject.toml` and the command line. + +For example, you could configure `ruff` to only enforce a subset of rules with: + +```toml +[tool.ruff] +line-length = 88 +select = [ + "F401", + "F403", +] ``` ## Development @@ -48,7 +69,7 @@ cargo run resources/test/src ## Deployment -`ruff` is released for Python using [`maturin`](https://github.com/PyO3/maturin): +`ruff` is distributed on [PyPI](https://pypi.org/project/ruff/), and published via [`maturin`](https://github.com/PyO3/maturin): ```shell maturin publish --skip-existing --target x86_64-apple-darwin && \ @@ -145,6 +166,8 @@ source .venv/bin/activate pip install pylint pycodestyle flake8 autoflake pyflakes hyperfine --ignore-failure --warmup 5 \ + "./target/release/ruff ./resources/test/cpython/ --no-cache" \ + "./target/release/ruff ./resources/test/cpython/" \ "pycodestyle resources/test/cpython" \ "pyflakes resources/test/cpython" \ "flake8 resources/test/cpython" \ @@ -229,3 +252,8 @@ Summary 6.14 ± 0.21 times faster than 'flake8 resources/test/cpython' 6.14 ± 0.21 times faster than 'flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython' ``` + + +## License + +MIT diff --git a/pyproject.toml b/pyproject.toml index bd535dcc65..84de25e3a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ author = "Charlie Marsh" author_email = "charlie.r.marsh@gmail.com" url = "https://github.com/charliermarsh/ruff" -description = "The fastest Python linter, written in Rust." +description = "An extremely fast Python linter, written in Rust." [build-system] requires = ["maturin>=0.13,<0.14"] diff --git a/resources/test/src/pyproject.toml b/resources/test/src/pyproject.toml index 53a96360ca..4a5591b8e4 100644 --- a/resources/test/src/pyproject.toml +++ b/resources/test/src/pyproject.toml @@ -1,3 +1,13 @@ -[tool.linter] +[tool.ruff] line-length = 88 exclude = ["excluded.py"] +select = [ + "E501", + "F401", + "F403", + "F541", + "F634", + "F706", + "F831", + "F901", +] diff --git a/scripts/poetry.lock b/scripts/poetry.lock index d95ac4e582..19e573a6ba 100644 --- a/scripts/poetry.lock +++ b/scripts/poetry.lock @@ -8,19 +8,19 @@ python-versions = ">=3.7.2" [package.dependencies] lazy-object-proxy = ">=1.4.0" -typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""} [[package]] name = "autoflake" -version = "1.4" +version = "1.5.1" description = "Removes unused imports and unused variables" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" [package.dependencies] pyflakes = ">=1.1.0" +toml = ">=0.10.2" [[package]] name = "colorama" @@ -217,7 +217,6 @@ mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] spelling = ["pyenchant (>=3.2,<4.0)"] @@ -280,6 +279,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + [[package]] name = "tomli" version = "2.0.1" @@ -296,14 +303,6 @@ category = "main" optional = false python-versions = ">=3.6,<4.0" -[[package]] -name = "typing-extensions" -version = "4.3.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" - [[package]] name = "wrapt" version = "1.14.1" @@ -314,8 +313,8 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" -python-versions = ">=3.8,<3.11" -content-hash = "049cbaedb49bbc3a4bdbd73fefbbbba7fa3f1185336862050a9b87b343cc4814" +python-versions = ">=3.10,<3.11" +content-hash = "45e5caa8c4ac70f1b0d0a4f52a19bf7e2cacd55fa2e3f2ac36480491974d3cf8" [metadata.files] astroid = [ @@ -323,7 +322,8 @@ astroid = [ {file = "astroid-2.12.4.tar.gz", hash = "sha256:39fa822c82dc112f5072a208ddf01c58184043aa90e3e469786fa0520c71aaa7"}, ] autoflake = [ - {file = "autoflake-1.4.tar.gz", hash = "sha256:61a353012cff6ab94ca062823d1fb2f692c4acda51c76ff83a8d77915fba51ea"}, + {file = "autoflake-1.5.1-py2.py3-none-any.whl", hash = "sha256:275e05e3caa8307269ad4d46f2e058b76a5c41ca7d1a7f57158d64e9df3ffdd6"}, + {file = "autoflake-1.5.1.tar.gz", hash = "sha256:8272efbecf7c6d5e2b00fa3b2998478a3ad92d7c914a49a527d733dae7f800c5"}, ] colorama = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, @@ -481,6 +481,10 @@ six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -489,7 +493,6 @@ tomlkit = [ {file = "tomlkit-0.11.4-py3-none-any.whl", hash = "sha256:25d4e2e446c453be6360c67ddfb88838cfc42026322770ba13d1fbd403a93a5c"}, {file = "tomlkit-0.11.4.tar.gz", hash = "sha256:3235a9010fae54323e727c3ac06fb720752fe6635b3426e379daec60fbd44a83"}, ] -typing-extensions = [] wrapt = [ {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, diff --git a/scripts/pyproject.toml b/scripts/pyproject.toml index 9dba789251..dd9af3d758 100644 --- a/scripts/pyproject.toml +++ b/scripts/pyproject.toml @@ -5,7 +5,7 @@ description = "" authors = ["Charles Marsh "] [tool.poetry.dependencies] -python = ">=3.8,<3.11" +python = ">=3.10,<3.11" autoflake = "^1.4" flake8 = "^5.0.4" matplotlib = "^3.5.3" diff --git a/src/check_ast.rs b/src/check_ast.rs index ed4a93534c..a8f0a263f0 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -5,7 +5,7 @@ use rustpython_parser::ast::{ }; use crate::check_ast::ScopeKind::{Class, Function, Generator, Module}; -use crate::checks::{Check, CheckKind}; +use crate::checks::{Check, CheckCode, CheckKind}; use crate::settings::Settings; use crate::visitor; use crate::visitor::Visitor; @@ -413,15 +413,17 @@ impl Checker<'_> { } fn check_dead_scopes(&mut self) { - // TODO(charlie): Handle `__all__`. - for scope in &self.dead_scopes { - for (_, binding) in scope.values.iter().rev() { - if !binding.used { - if let BindingKind::Importation(name) = &binding.kind { - self.checks.push(Check { - kind: CheckKind::UnusedImport(name.clone()), - location: binding.location, - }); + if self.settings.select.contains(&CheckCode::F401) { + // TODO(charlie): Handle `__all__`. + for scope in &self.dead_scopes { + for (_, binding) in scope.values.iter().rev() { + if !binding.used { + if let BindingKind::Importation(name) = &binding.kind { + self.checks.push(Check { + kind: CheckKind::UnusedImport(name.clone()), + location: binding.location, + }); + } } } } diff --git a/src/pyproject.rs b/src/pyproject.rs index d69ac72e68..945b7c926c 100644 --- a/src/pyproject.rs +++ b/src/pyproject.rs @@ -17,7 +17,7 @@ pub fn load_config<'a>(paths: impl IntoIterator) -> Result<(Pat let pyproject = parse_pyproject_toml(&path)?; let config = pyproject .tool - .and_then(|tool| tool.linter) + .and_then(|tool| tool.ruff) .unwrap_or_default(); Ok((project_root, config)) } @@ -37,7 +37,7 @@ pub struct Config { #[derive(Debug, PartialEq, Eq, Deserialize)] struct Tools { - linter: Option, + ruff: Option, } #[derive(Debug, PartialEq, Eq, Deserialize)] @@ -50,7 +50,6 @@ fn parse_pyproject_toml(path: &Path) -> Result { toml::from_str(&contents).map_err(|e| e.into()) } -// https://github.com/psf/black/blob/44d5da00b520a05cd56e58b3998660f64ea59ebd/src/black/files.py#L84 fn find_pyproject_toml(path: &Path) -> Option { let path_pyproject_toml = path.join("pyproject.toml"); if path_pyproject_toml.is_file() { @@ -59,12 +58,10 @@ fn find_pyproject_toml(path: &Path) -> Option { find_user_pyproject_toml() } -// https://github.com/psf/black/blob/44d5da00b520a05cd56e58b3998660f64ea59ebd/src/black/files.py#L117 fn find_user_pyproject_toml() -> Option { - dirs::home_dir().map(|path| path.join(".linter")) + dirs::home_dir().map(|path| path.join(".ruff")) } -// https://github.com/psf/black/blob/44d5da00b520a05cd56e58b3998660f64ea59ebd/src/black/files.py#L42 fn find_project_root<'a>(sources: impl IntoIterator) -> Option { if let Some(prefix) = common_path_all(sources) { for directory in prefix.ancestors() { @@ -105,18 +102,18 @@ mod tests { [tool.black] "#, )?; - assert_eq!(pyproject.tool, Some(Tools { linter: None })); + assert_eq!(pyproject.tool, Some(Tools { ruff: None })); let pyproject: PyProject = toml::from_str( r#" [tool.black] -[tool.linter] +[tool.ruff] "#, )?; assert_eq!( pyproject.tool, Some(Tools { - linter: Some(Config { + ruff: Some(Config { line_length: None, exclude: None, select: None, @@ -127,14 +124,14 @@ mod tests { let pyproject: PyProject = toml::from_str( r#" [tool.black] -[tool.linter] +[tool.ruff] line-length = 79 "#, )?; assert_eq!( pyproject.tool, Some(Tools { - linter: Some(Config { + ruff: Some(Config { line_length: Some(79), exclude: None, select: None, @@ -145,14 +142,14 @@ line-length = 79 let pyproject: PyProject = toml::from_str( r#" [tool.black] -[tool.linter] +[tool.ruff] exclude = ["foo.py"] "#, )?; assert_eq!( pyproject.tool, Some(Tools { - linter: Some(Config { + ruff: Some(Config { line_length: None, exclude: Some(vec![Path::new("foo.py").to_path_buf()]), select: None, @@ -163,14 +160,14 @@ exclude = ["foo.py"] let pyproject: PyProject = toml::from_str( r#" [tool.black] -[tool.linter] +[tool.ruff] select = ["E501"] "#, )?; assert_eq!( pyproject.tool, Some(Tools { - linter: Some(Config { + ruff: Some(Config { line_length: None, exclude: None, select: Some(BTreeSet::from([CheckCode::E501])), @@ -181,7 +178,7 @@ select = ["E501"] assert!(toml::from_str::( r#" [tool.black] -[tool.linter] +[tool.ruff] line_length = 79 "#, ) @@ -190,7 +187,7 @@ line_length = 79 assert!(toml::from_str::( r#" [tool.black] -[tool.linter] +[tool.ruff] select = ["E123"] "#, ) @@ -199,7 +196,7 @@ select = ["E123"] assert!(toml::from_str::( r#" [tool.black] -[tool.linter] +[tool.ruff] line-length = 79 other-attribute = 1 "#, @@ -221,15 +218,24 @@ other-attribute = 1 let pyproject = parse_pyproject_toml(&path)?; let config = pyproject .tool - .map(|tool| tool.linter) + .map(|tool| tool.ruff) .flatten() - .expect("Unable to find tool.linter."); + .expect("Unable to find tool.ruff."); assert_eq!( config, Config { line_length: Some(88), exclude: Some(vec![Path::new("excluded.py").to_path_buf()]), - select: None, + select: Some(BTreeSet::from([ + CheckCode::E501, + CheckCode::F401, + CheckCode::F403, + CheckCode::F541, + CheckCode::F634, + CheckCode::F706, + CheckCode::F831, + CheckCode::F901, + ])), } );