mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 04:19:18 +00:00
Fix pyproject.toml key
This commit is contained in:
parent
816bb88e3b
commit
ea9fde14f6
7 changed files with 114 additions and 65 deletions
58
README.md
58
README.md
|
@ -1,16 +1,24 @@
|
|||
# ruff
|
||||
|
||||
A performance-focused, [Pyflakes](https://github.com/PyCQA/pyflakes)-inspired Python linter, written
|
||||
in Rust.
|
||||
[](https://github.com/charliermarsh/ruff/actions)
|
||||
[](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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
[tool.linter]
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
exclude = ["excluded.py"]
|
||||
select = [
|
||||
"E501",
|
||||
"F401",
|
||||
"F403",
|
||||
"F541",
|
||||
"F634",
|
||||
"F706",
|
||||
"F831",
|
||||
"F901",
|
||||
]
|
||||
|
|
35
scripts/poetry.lock
generated
35
scripts/poetry.lock
generated
|
@ -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"},
|
||||
|
|
|
@ -5,7 +5,7 @@ description = ""
|
|||
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.8,<3.11"
|
||||
python = ">=3.10,<3.11"
|
||||
autoflake = "^1.4"
|
||||
flake8 = "^5.0.4"
|
||||
matplotlib = "^3.5.3"
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ pub fn load_config<'a>(paths: impl IntoIterator<Item = &'a Path>) -> 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<Config>,
|
||||
ruff: Option<Config>,
|
||||
}
|
||||
|
||||
#[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())
|
||||
}
|
||||
|
||||
// https://github.com/psf/black/blob/44d5da00b520a05cd56e58b3998660f64ea59ebd/src/black/files.py#L84
|
||||
fn find_pyproject_toml(path: &Path) -> Option<PathBuf> {
|
||||
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<PathBuf> {
|
|||
find_user_pyproject_toml()
|
||||
}
|
||||
|
||||
// https://github.com/psf/black/blob/44d5da00b520a05cd56e58b3998660f64ea59ebd/src/black/files.py#L117
|
||||
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> {
|
||||
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::<PyProject>(
|
||||
r#"
|
||||
[tool.black]
|
||||
[tool.linter]
|
||||
[tool.ruff]
|
||||
line_length = 79
|
||||
"#,
|
||||
)
|
||||
|
@ -190,7 +187,7 @@ line_length = 79
|
|||
assert!(toml::from_str::<PyProject>(
|
||||
r#"
|
||||
[tool.black]
|
||||
[tool.linter]
|
||||
[tool.ruff]
|
||||
select = ["E123"]
|
||||
"#,
|
||||
)
|
||||
|
@ -199,7 +196,7 @@ select = ["E123"]
|
|||
assert!(toml::from_str::<PyProject>(
|
||||
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,
|
||||
])),
|
||||
}
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue