Fix pyproject.toml key

This commit is contained in:
Charlie Marsh 2022-08-28 14:21:44 -04:00
parent 816bb88e3b
commit ea9fde14f6
7 changed files with 114 additions and 65 deletions

View file

@ -1,16 +1,24 @@
# ruff # ruff
A performance-focused, [Pyflakes](https://github.com/PyCQA/pyflakes)-inspired Python linter, written [![Actions status](https://github.com/charliermarsh/ruff/workflows/CI/badge.svg)](https://github.com/charliermarsh/ruff/actions)
in Rust. [![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 Major features:
- [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
## 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: 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 pip install ruff
``` ```
## Usage ### Usage
To run the linter, try any of the following: To run `ruff`, try any of the following:
```shell ```shell
ruff path/to/code/to/check.py ruff path/to/code/to/check.py
# ...or...
ruff path/to/code/ ruff path/to/code/
# ...or...
ruff path/to/code/*.py 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 ```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 ## Development
@ -48,7 +69,7 @@ cargo run resources/test/src
## Deployment ## 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 ```shell
maturin publish --skip-existing --target x86_64-apple-darwin && \ maturin publish --skip-existing --target x86_64-apple-darwin && \
@ -145,6 +166,8 @@ source .venv/bin/activate
pip install pylint pycodestyle flake8 autoflake pyflakes pip install pylint pycodestyle flake8 autoflake pyflakes
hyperfine --ignore-failure --warmup 5 \ hyperfine --ignore-failure --warmup 5 \
"./target/release/ruff ./resources/test/cpython/ --no-cache" \
"./target/release/ruff ./resources/test/cpython/" \
"pycodestyle resources/test/cpython" \ "pycodestyle resources/test/cpython" \
"pyflakes resources/test/cpython" \ "pyflakes resources/test/cpython" \
"flake8 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 resources/test/cpython'
6.14 ± 0.21 times faster than 'flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython' 6.14 ± 0.21 times faster than 'flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython'
``` ```
## License
MIT

View file

@ -20,7 +20,7 @@ classifiers = [
author = "Charlie Marsh" author = "Charlie Marsh"
author_email = "charlie.r.marsh@gmail.com" author_email = "charlie.r.marsh@gmail.com"
url = "https://github.com/charliermarsh/ruff" 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] [build-system]
requires = ["maturin>=0.13,<0.14"] requires = ["maturin>=0.13,<0.14"]

View file

@ -1,3 +1,13 @@
[tool.linter] [tool.ruff]
line-length = 88 line-length = 88
exclude = ["excluded.py"] exclude = ["excluded.py"]
select = [
"E501",
"F401",
"F403",
"F541",
"F634",
"F706",
"F831",
"F901",
]

35
scripts/poetry.lock generated
View file

@ -8,19 +8,19 @@ python-versions = ">=3.7.2"
[package.dependencies] [package.dependencies]
lazy-object-proxy = ">=1.4.0" 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\""} wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""}
[[package]] [[package]]
name = "autoflake" name = "autoflake"
version = "1.4" version = "1.5.1"
description = "Removes unused imports and unused variables" description = "Removes unused imports and unused variables"
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = ">=3.7"
[package.dependencies] [package.dependencies]
pyflakes = ">=1.1.0" pyflakes = ">=1.1.0"
toml = ">=0.10.2"
[[package]] [[package]]
name = "colorama" name = "colorama"
@ -217,7 +217,6 @@ mccabe = ">=0.6,<0.8"
platformdirs = ">=2.2.0" platformdirs = ">=2.2.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
tomlkit = ">=0.10.1" tomlkit = ">=0.10.1"
typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
[package.extras] [package.extras]
spelling = ["pyenchant (>=3.2,<4.0)"] spelling = ["pyenchant (>=3.2,<4.0)"]
@ -280,6 +279,14 @@ category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 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]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
@ -296,14 +303,6 @@ category = "main"
optional = false optional = false
python-versions = ">=3.6,<4.0" 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]] [[package]]
name = "wrapt" name = "wrapt"
version = "1.14.1" version = "1.14.1"
@ -314,8 +313,8 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = ">=3.8,<3.11" python-versions = ">=3.10,<3.11"
content-hash = "049cbaedb49bbc3a4bdbd73fefbbbba7fa3f1185336862050a9b87b343cc4814" content-hash = "45e5caa8c4ac70f1b0d0a4f52a19bf7e2cacd55fa2e3f2ac36480491974d3cf8"
[metadata.files] [metadata.files]
astroid = [ astroid = [
@ -323,7 +322,8 @@ astroid = [
{file = "astroid-2.12.4.tar.gz", hash = "sha256:39fa822c82dc112f5072a208ddf01c58184043aa90e3e469786fa0520c71aaa7"}, {file = "astroid-2.12.4.tar.gz", hash = "sha256:39fa822c82dc112f5072a208ddf01c58184043aa90e3e469786fa0520c71aaa7"},
] ]
autoflake = [ 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 = [ colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {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-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {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 = [ tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, {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-py3-none-any.whl", hash = "sha256:25d4e2e446c453be6360c67ddfb88838cfc42026322770ba13d1fbd403a93a5c"},
{file = "tomlkit-0.11.4.tar.gz", hash = "sha256:3235a9010fae54323e727c3ac06fb720752fe6635b3426e379daec60fbd44a83"}, {file = "tomlkit-0.11.4.tar.gz", hash = "sha256:3235a9010fae54323e727c3ac06fb720752fe6635b3426e379daec60fbd44a83"},
] ]
typing-extensions = []
wrapt = [ wrapt = [
{file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, {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"}, {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},

View file

@ -5,7 +5,7 @@ description = ""
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"] authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.8,<3.11" python = ">=3.10,<3.11"
autoflake = "^1.4" autoflake = "^1.4"
flake8 = "^5.0.4" flake8 = "^5.0.4"
matplotlib = "^3.5.3" matplotlib = "^3.5.3"

View file

@ -5,7 +5,7 @@ use rustpython_parser::ast::{
}; };
use crate::check_ast::ScopeKind::{Class, Function, Generator, Module}; 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::settings::Settings;
use crate::visitor; use crate::visitor;
use crate::visitor::Visitor; use crate::visitor::Visitor;
@ -413,6 +413,7 @@ impl Checker<'_> {
} }
fn check_dead_scopes(&mut self) { fn check_dead_scopes(&mut self) {
if self.settings.select.contains(&CheckCode::F401) {
// TODO(charlie): Handle `__all__`. // TODO(charlie): Handle `__all__`.
for scope in &self.dead_scopes { for scope in &self.dead_scopes {
for (_, binding) in scope.values.iter().rev() { for (_, binding) in scope.values.iter().rev() {
@ -428,6 +429,7 @@ impl Checker<'_> {
} }
} }
} }
}
pub fn check_ast(python_ast: &Suite, settings: &Settings) -> Vec<Check> { pub fn check_ast(python_ast: &Suite, settings: &Settings) -> Vec<Check> {
let mut checker = Checker::new(settings); let mut checker = Checker::new(settings);

View file

@ -17,7 +17,7 @@ pub fn load_config<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<(Pat
let pyproject = parse_pyproject_toml(&path)?; let pyproject = parse_pyproject_toml(&path)?;
let config = pyproject let config = pyproject
.tool .tool
.and_then(|tool| tool.linter) .and_then(|tool| tool.ruff)
.unwrap_or_default(); .unwrap_or_default();
Ok((project_root, config)) Ok((project_root, config))
} }
@ -37,7 +37,7 @@ pub struct Config {
#[derive(Debug, PartialEq, Eq, Deserialize)] #[derive(Debug, PartialEq, Eq, Deserialize)]
struct Tools { struct Tools {
linter: Option<Config>, ruff: Option<Config>,
} }
#[derive(Debug, PartialEq, Eq, Deserialize)] #[derive(Debug, PartialEq, Eq, Deserialize)]
@ -50,7 +50,6 @@ fn parse_pyproject_toml(path: &Path) -> Result<PyProject> {
toml::from_str(&contents).map_err(|e| e.into()) 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<PathBuf> { fn find_pyproject_toml(path: &Path) -> Option<PathBuf> {
let path_pyproject_toml = path.join("pyproject.toml"); let path_pyproject_toml = path.join("pyproject.toml");
if path_pyproject_toml.is_file() { if path_pyproject_toml.is_file() {
@ -59,12 +58,10 @@ fn find_pyproject_toml(path: &Path) -> Option<PathBuf> {
find_user_pyproject_toml() find_user_pyproject_toml()
} }
// https://github.com/psf/black/blob/44d5da00b520a05cd56e58b3998660f64ea59ebd/src/black/files.py#L117
fn find_user_pyproject_toml() -> Option<PathBuf> { fn find_user_pyproject_toml() -> Option<PathBuf> {
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<Item = &'a Path>) -> Option<PathBuf> { fn find_project_root<'a>(sources: impl IntoIterator<Item = &'a Path>) -> Option<PathBuf> {
if let Some(prefix) = common_path_all(sources) { if let Some(prefix) = common_path_all(sources) {
for directory in prefix.ancestors() { for directory in prefix.ancestors() {
@ -105,18 +102,18 @@ mod tests {
[tool.black] [tool.black]
"#, "#,
)?; )?;
assert_eq!(pyproject.tool, Some(Tools { linter: None })); assert_eq!(pyproject.tool, Some(Tools { ruff: None }));
let pyproject: PyProject = toml::from_str( let pyproject: PyProject = toml::from_str(
r#" r#"
[tool.black] [tool.black]
[tool.linter] [tool.ruff]
"#, "#,
)?; )?;
assert_eq!( assert_eq!(
pyproject.tool, pyproject.tool,
Some(Tools { Some(Tools {
linter: Some(Config { ruff: Some(Config {
line_length: None, line_length: None,
exclude: None, exclude: None,
select: None, select: None,
@ -127,14 +124,14 @@ mod tests {
let pyproject: PyProject = toml::from_str( let pyproject: PyProject = toml::from_str(
r#" r#"
[tool.black] [tool.black]
[tool.linter] [tool.ruff]
line-length = 79 line-length = 79
"#, "#,
)?; )?;
assert_eq!( assert_eq!(
pyproject.tool, pyproject.tool,
Some(Tools { Some(Tools {
linter: Some(Config { ruff: Some(Config {
line_length: Some(79), line_length: Some(79),
exclude: None, exclude: None,
select: None, select: None,
@ -145,14 +142,14 @@ line-length = 79
let pyproject: PyProject = toml::from_str( let pyproject: PyProject = toml::from_str(
r#" r#"
[tool.black] [tool.black]
[tool.linter] [tool.ruff]
exclude = ["foo.py"] exclude = ["foo.py"]
"#, "#,
)?; )?;
assert_eq!( assert_eq!(
pyproject.tool, pyproject.tool,
Some(Tools { Some(Tools {
linter: Some(Config { ruff: Some(Config {
line_length: None, line_length: None,
exclude: Some(vec![Path::new("foo.py").to_path_buf()]), exclude: Some(vec![Path::new("foo.py").to_path_buf()]),
select: None, select: None,
@ -163,14 +160,14 @@ exclude = ["foo.py"]
let pyproject: PyProject = toml::from_str( let pyproject: PyProject = toml::from_str(
r#" r#"
[tool.black] [tool.black]
[tool.linter] [tool.ruff]
select = ["E501"] select = ["E501"]
"#, "#,
)?; )?;
assert_eq!( assert_eq!(
pyproject.tool, pyproject.tool,
Some(Tools { Some(Tools {
linter: Some(Config { ruff: Some(Config {
line_length: None, line_length: None,
exclude: None, exclude: None,
select: Some(BTreeSet::from([CheckCode::E501])), select: Some(BTreeSet::from([CheckCode::E501])),
@ -181,7 +178,7 @@ select = ["E501"]
assert!(toml::from_str::<PyProject>( assert!(toml::from_str::<PyProject>(
r#" r#"
[tool.black] [tool.black]
[tool.linter] [tool.ruff]
line_length = 79 line_length = 79
"#, "#,
) )
@ -190,7 +187,7 @@ line_length = 79
assert!(toml::from_str::<PyProject>( assert!(toml::from_str::<PyProject>(
r#" r#"
[tool.black] [tool.black]
[tool.linter] [tool.ruff]
select = ["E123"] select = ["E123"]
"#, "#,
) )
@ -199,7 +196,7 @@ select = ["E123"]
assert!(toml::from_str::<PyProject>( assert!(toml::from_str::<PyProject>(
r#" r#"
[tool.black] [tool.black]
[tool.linter] [tool.ruff]
line-length = 79 line-length = 79
other-attribute = 1 other-attribute = 1
"#, "#,
@ -221,15 +218,24 @@ other-attribute = 1
let pyproject = parse_pyproject_toml(&path)?; let pyproject = parse_pyproject_toml(&path)?;
let config = pyproject let config = pyproject
.tool .tool
.map(|tool| tool.linter) .map(|tool| tool.ruff)
.flatten() .flatten()
.expect("Unable to find tool.linter."); .expect("Unable to find tool.ruff.");
assert_eq!( assert_eq!(
config, config,
Config { Config {
line_length: Some(88), line_length: Some(88),
exclude: Some(vec![Path::new("excluded.py").to_path_buf()]), 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,
])),
} }
); );