Compare commits

..

19 Commits

Author SHA1 Message Date
Charlie Marsh
56f69ce71e Bump version to 0.0.77 2022-10-16 13:43:52 -04:00
Charlie Marsh
248a6cd50b Remove offsets hacks for docstring parsing logic (#440) 2022-10-16 13:43:30 -04:00
Charlie Marsh
d9e659d817 Revert "Remove trailing colon from messages"
This reverts commit 77e5564f4b.
2022-10-16 12:35:40 -04:00
Charlie Marsh
77e5564f4b Remove trailing colon from messages 2022-10-16 12:09:46 -04:00
Charlie Marsh
e79766d5ec Use backticks for pep8-naming messages 2022-10-16 12:09:07 -04:00
Harutaka Kawamura
c55fd76743 Implement N801 ~ N805 (#439) 2022-10-16 11:58:39 -04:00
Charlie Marsh
e2aedc5ba8 Bump version to 0.0.76 2022-10-15 17:22:12 -04:00
Charlie Marsh
1b2d085460 Mark W292 as a non-AST check 2022-10-15 17:21:52 -04:00
Charlie Marsh
cb138526b1 Add a note on pydocstyle 2022-10-15 17:05:18 -04:00
Charlie Marsh
8eac270d8f Add an FAQ 2022-10-15 16:52:20 -04:00
Charlie Marsh
6e19fd20bb Add table of contents 2022-10-15 16:37:52 -04:00
Charlie Marsh
10868445f5 Use H3 for rules sections 2022-10-15 16:33:26 -04:00
Charlie Marsh
e3ecf21287 Break rules table into sections (#437) 2022-10-15 16:29:08 -04:00
Charlie Marsh
fd849e112e Remove checkmark from rule table (#436) 2022-10-15 11:28:32 -04:00
Harutaka Kawamura
af27471c77 Fix C401 and C402 (#435) 2022-10-15 09:16:43 -04:00
konstin
bb466bc8d3 Add initial wasm32-wasi support (#416) 2022-10-14 20:58:18 -04:00
Charlie Marsh
7741a713e2 Avoid checking for updates when executing via stdin (#433) 2022-10-14 20:57:40 -04:00
Charlie Marsh
3ab1cfc6f8 Make some improvements to the README 2022-10-14 17:30:40 -04:00
Charlie Marsh
e73f13473d Add a .flake8 file for benchmarking 2022-10-14 17:05:24 -04:00
38 changed files with 1317 additions and 391 deletions

133
Cargo.lock generated
View File

@@ -418,9 +418,9 @@ checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
[[package]]
name = "clap"
version = "4.0.9"
version = "4.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30607dd93c420c6f1f80b544be522a0238a7db35e6a12968d28910983fee0df0"
checksum = "6bf8832993da70a4c6d13c581f4463c2bdda27b9bf1c5498dc4365543abe6d6f"
dependencies = [
"atty",
"bitflags",
@@ -433,9 +433,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.0.9"
version = "4.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a307492e1a34939f79d3b6b9650bd2b971513cd775436bf2b78defeb5af00b"
checksum = "c42f169caba89a7d512b5418b09864543eeb4d497416c917d7137863bd2076ad"
dependencies = [
"heck",
"proc-macro-error",
@@ -466,6 +466,16 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "colored"
version = "2.0.0"
@@ -588,6 +598,50 @@ dependencies = [
"syn",
]
[[package]]
name = "cxx"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c"
[[package]]
name = "cxxbridge-macro"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "diff"
version = "0.1.13"
@@ -954,8 +1008,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -1020,17 +1076,28 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "iana-time-zone"
version = "0.1.50"
version = "0.1.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0"
checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi 0.3.9",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa"
dependencies = [
"cxx",
"cxx-build",
]
[[package]]
name = "idna"
version = "0.3.0"
@@ -1114,9 +1181,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
[[package]]
name = "joinery"
@@ -1228,9 +1295,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.134"
version = "0.2.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]]
name = "libcst"
@@ -1256,6 +1323,15 @@ dependencies = [
"syn",
]
[[package]]
name = "link-cplusplus"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
dependencies = [
"cc",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
@@ -1541,18 +1617,18 @@ checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
[[package]]
name = "path-absolutize"
version = "3.0.13"
version = "3.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3de4b40bd9736640f14c438304c09538159802388febb02c8abaae0846c1f13"
checksum = "0f1d4993b16f7325d90c18c3c6a3327db7808752db8d208cea0acee0abd52c52"
dependencies = [
"path-dedot",
]
[[package]]
name = "path-dedot"
version = "3.0.17"
version = "3.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d611d5291372b3738a34ebf0d1f849e58b1dcc1101032f76a346eaa1f8ddbb5b"
checksum = "9a81540d94551664b72b72829b12bd167c73c9d25fbac0e04fafa8023f7e4901"
dependencies = [
"once_cell",
]
@@ -1969,7 +2045,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.75"
version = "0.0.77"
dependencies = [
"anyhow",
"assert_cmd",
@@ -1983,6 +2059,7 @@ dependencies = [
"dirs 4.0.0",
"fern",
"filetime",
"getrandom 0.2.7",
"glob",
"insta",
"itertools",
@@ -2024,7 +2101,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f#778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=210db77e4274787028dc3ebc0b4841d1334c8c10#210db77e4274787028dc3ebc0b4841d1334c8c10"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2034,7 +2111,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f#778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=210db77e4274787028dc3ebc0b4841d1334c8c10#210db77e4274787028dc3ebc0b4841d1334c8c10"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -2057,7 +2134,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f#778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=210db77e4274787028dc3ebc0b4841d1334c8c10#210db77e4274787028dc3ebc0b4841d1334c8c10"
dependencies = [
"bincode",
"bitflags",
@@ -2074,7 +2151,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f#778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=210db77e4274787028dc3ebc0b4841d1334c8c10#210db77e4274787028dc3ebc0b4841d1334c8c10"
dependencies = [
"ahash",
"anyhow",
@@ -2122,6 +2199,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
[[package]]
name = "sct"
version = "0.7.0"
@@ -2160,9 +2243,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.85"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
dependencies = [
"itoa",
"ryu",
@@ -2351,9 +2434,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.101"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
dependencies = [
"proc-macro2",
"quote",
@@ -2619,9 +2702,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-ident"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "unicode-linebreak"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.75"
version = "0.0.77"
edition = "2021"
[lib]
@@ -9,10 +9,8 @@ name = "ruff"
[dependencies]
anyhow = { version = "1.0.60" }
bincode = { version = "1.3.3" }
cacache = { version = "10.0.1" }
chrono = { version = "0.4.21" }
clap = { version = "4.0.1", features = ["derive"] }
clearscreen = { version = "1.0.10" }
colored = { version = "2.0.0" }
common-path = { version = "1.0.0" }
dirs = { version = "4.0.0" }
@@ -25,12 +23,12 @@ log = { version = "0.4.17" }
notify = { version = "4.0.17" }
num-bigint = { version = "0.4.3" }
once_cell = { version = "1.13.1" }
path-absolutize = { version = "3.0.13", features = ["once_cell_cache"] }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f" }
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "210db77e4274787028dc3ebc0b4841d1334c8c10" }
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "210db77e4274787028dc3ebc0b4841d1334c8c10" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "210db77e4274787028dc3ebc0b4841d1334c8c10" }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
strum = { version = "0.24.1", features = ["strum_macros"] }
@@ -41,6 +39,15 @@ toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
cacache = { version = "10.0.1" } # uses async-std
clearscreen = { version = "1.0.10" } # uses which
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
# For (future) wasm-pack support
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
getrandom = { version = "0.2.7", features = ["js"] }
[dev-dependencies]
assert_cmd = { version = "2.0.4" }
insta = { version = "1.19.1", features = ["yaml"] }

559
README.md
View File

@@ -1,4 +1,4 @@
# ruff
# Ruff
[![image](https://img.shields.io/pypi/v/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/l/ruff.svg)](https://pypi.python.org/pypi/ruff)
@@ -20,17 +20,37 @@ An extremely fast Python linter, written in Rust.
- 🤝 Python 3.10 compatibility
- 🛠️ `pyproject.toml` support
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache support
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix)-inspired `--fix` support
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support
- ⚖️ [Near-complete parity](#Parity-with-Flake8) with the built-in Flake8 rule set
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix)-inspired autofix support (e.g., automatically remove unused imports)
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support, for continuous file monitoring
- ⚖️ [Near-parity](#Parity-with-Flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) ([`pydocstyle`](https://pypi.org/project/pydocstyle/))
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety
of plugins), [`pydocstyle`](https://pypi.org/project/pydocstyle/), [`yesqa`](https://github.com/asottile/yesqa),
and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) and [`autoflake`](https://pypi.org/project/autoflake/)
all while executing tens or hundreds of times faster than any individual tool.
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
## Installation and usage
## Table of Contents
1. [Installation and Usage](#installation-and-usage)
2. [Configuration](#configuration)
3. [Supported Rules](#supported-rules)
4. [Editor Integrations](#editor-integrations)
5. [FAQ](#faq)
6. [Development](#development)
7. [Releases](#releases)
8. [Benchmarks](#benchmarks)
9. [License](#license)
10. [Contributing](#contributing)
## Installation and Usage
### Installation
Available as [ruff](https://pypi.org/project/ruff/) on PyPI:
Available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
```shell
pip install ruff
@@ -38,7 +58,7 @@ pip install ruff
### Usage
To run ruff, try any of the following:
To run Ruff, try any of the following:
```shell
ruff path/to/code/to/check.py
@@ -46,27 +66,27 @@ ruff path/to/code/
ruff path/to/code/*.py
```
You can run ruff in `--watch` mode to automatically re-run on-change:
You can run Ruff in `--watch` mode to automatically re-run on-change:
```shell
ruff path/to/code/ --watch
```
ruff also works with [pre-commit](https://pre-commit.com):
Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.75
rev: v0.0.77
hooks:
- id: lint
```
## Configuration
ruff is configurable both via `pyproject.toml` and the command line.
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:
For example, you could configure Ruff to only enforce a subset of rules with:
```toml
[tool.ruff]
@@ -158,7 +178,7 @@ Exclusions are based on globs, and can be either:
To omit a lint check entirely, add it to the "ignore" list via `--ignore` or `--extend-ignore`,
either on the command-line or in your `project.toml` file.
To ignore an error in-line, ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
To ignore an error in-line, Ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
To ignore an individual error, add `# noqa: {code}` to the end of the line, like so:
```python
@@ -182,201 +202,218 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i
""" # noqa: E501
```
ruff supports several (experimental) workflows to aid in `noqa` management.
Ruff supports several workflows to aid in `noqa` management.
First, ruff provides a special error code, `M001`, to enforce that your `noqa` directives are
First, Ruff provides a special error code, `M001`, to enforce that your `noqa` directives are
"valid", in that the errors they _say_ they ignore are actually being triggered on that line (and
thus suppressed). **You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
directives.**
Second, ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
Second, Ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
**You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
`noqa` directives.**
Third, ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
migrating a new codebase to ruff. **You can run `ruff /path/to/file.py --add-noqa` to automatically
Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
migrating a new codebase to Ruff. **You can run `ruff /path/to/file.py --add-noqa` to automatically
add `noqa` directives to all failing lines, with the appropriate error codes.**
### Compatibility with Black
## Supported Rules
ruff is compatible with [Black](https://github.com/psf/black) out-of-the-box, as long as
the `line-length` setting is consistent between the two.
By default, Ruff enables all `E`, `W`, and `F` error codes, which correspond to those built-in to
Flake8.
As a project, ruff is designed to be used alongside Black and, as such, will defer implementing
stylistic lint rules that are obviated by autoformatting.
### Pyflakes
### Parity with Flake8
| Code | Name | Message |
| ---- | ---- | ------- |
| F401 | UnusedImport | `...` imported but unused |
| F402 | ImportShadowedByLoopVar | Import `...` from line 1 shadowed by loop variable |
| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names |
| F404 | LateFutureImport | `from __future__` imports must occur at the beginning of the file |
| F405 | ImportStarUsage | `...` may be undefined, or defined from star imports: `...` |
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level |
| F407 | FutureFeatureNotDefined | Future feature `...` is not defined |
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated |
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment |
| F622 | TwoStarredExpressions | Two starred expressions in assignment |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` |
| F632 | IsLiteral | Use `==` and `!=` to compare constant literals |
| F633 | InvalidPrintSyntax | Use of `>>` is invalid with `print` function |
| F634 | IfTuple | If test is a tuple, which is always `True` |
| F701 | BreakOutsideLoop | `break` outside loop |
| F702 | ContinueOutsideLoop | `continue` not properly in loop |
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function/method |
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method |
| F707 | DefaultExceptNotLast | An `except:` block as not the last exception handler |
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` |
| F821 | UndefinedName | Undefined name `...` |
| F822 | UndefinedExport | Undefined name `...` in `__all__` |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` |
ruff's goal is to achieve feature parity with Flake8 when used (1) without plugins, (2) alongside
Black, and (3) on Python 3 code.
### pycodestyle
**Under those conditions, ruff implements 44 out of 60 rules.** (ruff is missing: 14 rules related
to string `.format` calls, 1 rule related to docstring parsing, and 1 rule related to redefined
variables.)
| Code | Name | Message |
| ---- | ---- | ------- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file |
| E501 | LineTooLong | Line too long (89 > 88 characters) |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` |
| E713 | NotInTest | Test for membership should be `not in` |
| E714 | NotIsTest | Test for object identity should be `is not` |
| E721 | TypeComparison | Do not compare types, use `isinstance()` |
| E722 | DoNotUseBareExcept | Do not use bare `except` |
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def |
| E741 | AmbiguousVariableName | Ambiguous variable name: `...` |
| E742 | AmbiguousClassName | Ambiguous class name: `...` |
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` |
| E902 | IOError | IOError: `...` |
| E999 | SyntaxError | SyntaxError: `...` |
| W292 | NoNewLineAtEndOfFile | No newline at end of file |
ruff also implements some of the most popular Flake8 plugins natively, including:
### pydocstyle
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
| Code | Name | Message |
| ---- | ---- | ------- |
| D100 | PublicModule | Missing docstring in public module |
| D101 | PublicClass | Missing docstring in public class |
| D102 | PublicMethod | Missing docstring in public method |
| D103 | PublicFunction | Missing docstring in public function |
| D104 | PublicPackage | Missing docstring in public package |
| D105 | MagicMethod | Missing docstring in magic method |
| D106 | PublicNestedClass | Missing docstring in public nested class |
| D107 | PublicInit | Missing docstring in `__init__` |
| D200 | FitsOnOneLine | One-line docstring should fit on one line |
| D201 | NoBlankLineBeforeFunction | No blank lines allowed before function docstring (found 1) |
| D202 | NoBlankLineAfterFunction | No blank lines allowed after function docstring (found 1) |
| D203 | OneBlankLineBeforeClass | 1 blank line required before class docstring |
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring |
| D205 | NoBlankLineAfterSummary | 1 blank line required between summary line and description |
| D206 | IndentWithSpaces | Docstring should be indented with spaces, not tabs |
| D207 | NoUnderIndentation | Docstring is under-indented |
| D208 | NoOverIndentation | Docstring is over-indented |
| D209 | NewLineAfterLastParagraph | Multi-line docstring closing quotes should be on a separate line |
| D210 | NoSurroundingWhitespace | No whitespaces allowed surrounding docstring text |
| D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring |
| D212 | MultiLineSummaryFirstLine | Multi-line docstring summary should start at the first line |
| D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line |
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") |
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") |
| D300 | UsesTripleQuotes | Use """triple double quotes""" |
| D400 | EndsInPeriod | First line should end with a period |
| D402 | NoSignature | First line should not be the function's 'signature' |
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized |
| D404 | NoThisPrefix | First word of the docstring should not be `This` |
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") |
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") |
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") |
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") |
| D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its name ("Returns") |
| D410 | BlankLineAfterSection | Missing blank line after section ("Returns") |
| D411 | BlankLineBeforeSection | Missing blank line before section ("Returns") |
| D412 | NoBlankLinesBetweenHeaderAndContent | No blank lines allowed between a section header and its content ("Returns") |
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") |
| D414 | NonEmptySection | Section has no content ("Returns") |
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point |
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") |
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` |
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring |
| D419 | NonEmpty | Docstring is empty |
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
### pyupgrade
1. ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
pattern matching and parenthesized context managers.
2. Flake8 has a plugin architecture and supports writing custom lint rules.
| Code | Name | Message |
| ---- | ---- | ------- |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied |
| U002 | UnnecessaryAbspath | `abspath(__file__)` is unnecessary in Python 3.9 and later |
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` |
| U004 | UselessObjectInheritance | Class `...` inherits from object |
| U005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` instead |
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations |
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations |
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` |
## Rules
### pep8-naming
The ✅ emoji indicates a rule is enabled by default.
| Code | Name | Message |
| ---- | ---- | ------- |
| N801 | InvalidClassName | class name `...` should use CapWords convention |
| N802 | InvalidFunctionName | function name `...` should be lowercase |
| N803 | InvalidArgumentName | argument name `...` should be lowercase |
| N804 | InvalidFirstArgumentNameForClassMethod | first argument of a classmethod should be named `cls` |
| N805 | InvalidFirstArgumentNameForMethod | first argument of a method should be named `self` |
The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option.
### flake8-comprehensions
| Code | Name | Message | | |
| ---- | ---- | ------- | --- | --- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | ✅ | |
| E501 | LineTooLong | Line too long (89 > 88 characters) | ✅ | |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | ✅ | |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` | ✅ | |
| E713 | NotInTest | Test for membership should be `not in` | ✅ | |
| E714 | NotIsTest | Test for object identity should be `is not` | ✅ | |
| E721 | TypeComparison | Do not compare types, use `isinstance()` | ✅ | |
| E722 | DoNotUseBareExcept | Do not use bare `except` | ✅ | |
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def | ✅ | |
| E741 | AmbiguousVariableName | Ambiguous variable name: `...` | ✅ | |
| E742 | AmbiguousClassName | Ambiguous class name: `...` | ✅ | |
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | ✅ | |
| E902 | IOError | IOError: `...` | ✅ | |
| E999 | SyntaxError | SyntaxError: `...` | ✅ | |
| W292 | NoNewLineAtEndOfFile | No newline at end of file | ✅ | |
| F401 | UnusedImport | `...` imported but unused | ✅ | 🛠 |
| F402 | ImportShadowedByLoopVar | Import `...` from line 1 shadowed by loop variable | ✅ | |
| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names | ✅ | |
| F404 | LateFutureImport | `from __future__` imports must occur at the beginning of the file | ✅ | |
| F405 | ImportStarUsage | `...` may be undefined, or defined from star imports: `...` | ✅ | |
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level | ✅ | |
| F407 | FutureFeatureNotDefined | Future feature `...` is not defined | ✅ | |
| F541 | FStringMissingPlaceholders | f-string without any placeholders | ✅ | |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | ✅ | |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | ✅ | |
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | ✅ | |
| F622 | TwoStarredExpressions | Two starred expressions in assignment | ✅ | |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | ✅ | |
| F632 | IsLiteral | Use `==` and `!=` to compare constant literals | ✅ | |
| F633 | InvalidPrintSyntax | Use of `>>` is invalid with `print` function | ✅ | |
| F634 | IfTuple | If test is a tuple, which is always `True` | ✅ | |
| F701 | BreakOutsideLoop | `break` outside loop | ✅ | |
| F702 | ContinueOutsideLoop | `continue` not properly in loop | ✅ | |
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function/method | ✅ | |
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | ✅ | |
| F707 | DefaultExceptNotLast | An `except:` block as not the last exception handler | ✅ | |
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | ✅ | |
| F821 | UndefinedName | Undefined name `...` | ✅ | |
| F822 | UndefinedExport | Undefined name `...` in `__all__` | ✅ | |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment | ✅ | |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | ✅ | |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | ✅ | |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | ✅ | |
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | | |
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | | |
| A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin | | |
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | | 🛠 |
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | | 🛠 |
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | | |
| C400 | UnnecessaryGeneratorList | Unnecessary generator - rewrite as a list comprehension | | |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator - rewrite as a set comprehension | | |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator - rewrite as a dict comprehension | | |
| C403 | UnnecessaryListComprehensionSet | Unnecessary list comprehension - rewrite as a set comprehension | | |
| C404 | UnnecessaryListComprehensionDict | Unnecessary list comprehension - rewrite as a dict comprehension | | |
| C405 | UnnecessaryLiteralSet | Unnecessary <list/tuple> literal - rewrite as a set literal | | |
| C406 | UnnecessaryLiteralDict | Unnecessary <list/tuple> literal - rewrite as a dict literal | | |
| C408 | UnnecessaryCollectionCall | Unnecessary <dict/list/tuple> call - rewrite as a literal | | |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary <list/tuple> literal passed to tuple() - remove the outer call to tuple() | | |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary <list/tuple> literal passed to list() - rewrite as a list literal | | |
| C411 | UnnecessaryListCall | Unnecessary list call - remove the outer call to list() | | |
| C413 | UnnecessaryCallAroundSorted | Unnecessary <list/reversed> call around sorted() | | |
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary <list/reversed/set/sorted/tuple> call within <list/set/sorted/tuple>(). | | |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within <reversed/set/sorted>() | | |
| C416 | UnnecessaryComprehension | Unnecessary <list/set> comprehension - rewrite using <list/set>() | | |
| C417 | UnnecessaryMap | Unnecessary map usage - rewrite using a <list/set/dict> comprehension | | |
| T201 | PrintFound | `print` found | | 🛠 |
| T203 | PPrintFound | `pprint` found | | 🛠 |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | | 🛠 |
| U002 | UnnecessaryAbspath | `abspath(__file__)` is unnecessary in Python 3.9 and later | | 🛠 |
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` | | 🛠 |
| U004 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
| U005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | | 🛠 |
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | | 🛠 |
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
| D100 | PublicModule | Missing docstring in public module | | |
| D101 | PublicClass | Missing docstring in public class | | |
| D102 | PublicMethod | Missing docstring in public method | | |
| D103 | PublicFunction | Missing docstring in public function | | |
| D104 | PublicPackage | Missing docstring in public package | | |
| D105 | MagicMethod | Missing docstring in magic method | | |
| D106 | PublicNestedClass | Missing docstring in public nested class | | |
| D107 | PublicInit | Missing docstring in __init__ | | |
| D200 | FitsOnOneLine | One-line docstring should fit on one line | | |
| D201 | NoBlankLineBeforeFunction | No blank lines allowed before function docstring (found 1) | | |
| D202 | NoBlankLineAfterFunction | No blank lines allowed after function docstring (found 1) | | |
| D203 | OneBlankLineBeforeClass | 1 blank line required before class docstring | | |
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | | |
| D205 | NoBlankLineAfterSummary | 1 blank line required between summary line and description | | |
| D206 | IndentWithSpaces | Docstring should be indented with spaces, not tabs | | |
| D207 | NoUnderIndentation | Docstring is under-indented | | |
| D208 | NoOverIndentation | Docstring is over-indented | | |
| D209 | NewLineAfterLastParagraph | Multi-line docstring closing quotes should be on a separate line | | |
| D210 | NoSurroundingWhitespace | No whitespaces allowed surrounding docstring text | | |
| D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | | |
| D212 | MultiLineSummaryFirstLine | Multi-line docstring summary should start at the first line | | |
| D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line | | |
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") | | |
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | | |
| D300 | UsesTripleQuotes | Use """triple double quotes""" | | |
| D400 | EndsInPeriod | First line should end with a period | | |
| D402 | NoSignature | First line should not be the function's 'signature' | | |
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | | |
| D404 | NoThisPrefix | First word of the docstring should not be `This` | | |
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | | |
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | | |
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | | |
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | | |
| D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its name ("Returns") | | |
| D410 | BlankLineAfterSection | Missing blank line after section ("Returns") | | |
| D411 | BlankLineBeforeSection | Missing blank line before section ("Returns") | | |
| D412 | NoBlankLinesBetweenHeaderAndContent | No blank lines allowed between a section header and its content ("Returns") | | |
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") | | |
| D414 | NonEmptySection | Section has no content ("Returns") | | |
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | | |
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | | |
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | | |
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | | |
| D419 | NonEmpty | Docstring is empty | | |
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
| Code | Name | Message |
| ---- | ---- | ------- |
| C400 | UnnecessaryGeneratorList | Unnecessary generator - rewrite as a list comprehension |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator - rewrite as a set comprehension |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator - rewrite as a dict comprehension |
| C403 | UnnecessaryListComprehensionSet | Unnecessary list comprehension - rewrite as a set comprehension |
| C404 | UnnecessaryListComprehensionDict | Unnecessary list comprehension - rewrite as a dict comprehension |
| C405 | UnnecessaryLiteralSet | Unnecessary <list/tuple> literal - rewrite as a set literal |
| C406 | UnnecessaryLiteralDict | Unnecessary <list/tuple> literal - rewrite as a dict literal |
| C408 | UnnecessaryCollectionCall | Unnecessary <dict/list/tuple> call - rewrite as a literal |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary <list/tuple> literal passed to tuple() - remove the outer call to tuple() |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary <list/tuple> literal passed to list() - rewrite as a list literal |
| C411 | UnnecessaryListCall | Unnecessary list call - remove the outer call to list() |
| C413 | UnnecessaryCallAroundSorted | Unnecessary <list/reversed> call around sorted() |
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary <list/reversed/set/sorted/tuple> call within <list/set/sorted/tuple>(). |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within <reversed/set/sorted>() |
| C416 | UnnecessaryComprehension | Unnecessary <list/set> comprehension - rewrite using <list/set>() |
| C417 | UnnecessaryMap | Unnecessary map usage - rewrite using a <list/set/dict> comprehension |
## Integrations
### flake8-bugbear
| Code | Name | Message |
| ---- | ---- | ------- |
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` |
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` |
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` |
### flake8-builtins
| Code | Name | Message |
| ---- | ---- | ------- |
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin |
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin |
| A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin |
### flake8-print
| Code | Name | Message |
| ---- | ---- | ------- |
| T201 | PrintFound | `print` found |
| T203 | PPrintFound | `pprint` found |
### Meta rules
| Code | Name | Message |
| ---- | ---- | ------- |
| M001 | UnusedNOQA | Unused `noqa` directive |
## Editor Integrations
### PyCharm
ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
in PyCharm. Open the Preferences pane, then navigate to "Tools", then "External Tools". From there,
add a new tool with the following configuration:
![Install ruff as an External Tool](https://user-images.githubusercontent.com/1309177/193155720-336e43f0-1a8d-46b4-bc12-e60f9ae01f7e.png)
![Install Ruff as an External Tool](https://user-images.githubusercontent.com/1309177/193155720-336e43f0-1a8d-46b4-bc12-e60f9ae01f7e.png)
ruff should then appear as a runnable action:
Ruff should then appear as a runnable action:
![ruff as a runnable action](https://user-images.githubusercontent.com/1309177/193156026-732b0aaf-3dd9-4549-9b4d-2de6d2168a33.png)
![Ruff as a runnable action](https://user-images.githubusercontent.com/1309177/193156026-732b0aaf-3dd9-4549-9b4d-2de6d2168a33.png)
### GitHub Actions
GitHub Actions has everything you need to run ruff out-of-the-box:
GitHub Actions has everything you need to run Ruff out-of-the-box:
```yaml
name: CI
@@ -394,13 +431,175 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install ruff
- name: Run ruff
- name: Run Ruff
run: ruff .
```
## FAQ
### Is Ruff compatible with Black?
Yes. Ruff is compatible with [Black](https://github.com/psf/black) out-of-the-box, as long as
the `line-length` setting is consistent between the two.
As a project, Ruff is designed to be used alongside Black and, as such, will defer implementing
stylistic lint rules that are obviated by autoformatting.
### How does Ruff compare to Flake8?
Ruff can be used as a (near) drop-in replacement for Flake8 when used (1) without or with a small
number of plugins, (2) alongside Black, and (3) on Python 3 code.
Under those conditions Ruff is missing 14 rules related to string `.format` calls, 1 rule related
to docstring parsing, and 1 rule related to redefined variables.
Ruff re-implements some of the most popular Flake8 plugins and related code quality tools natively,
including:
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`yesqa`](https://github.com/asottile/yesqa)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
1. Ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
pattern matching and parenthesized context managers.
2. Flake8 has a plugin architecture and supports writing custom lint rules. (To date, popular Flake8
plugins have been re-implemented within Ruff directly.)
### Which tools does Ruff replace?
Today, Ruff can be used to replace Flake8 when used with any of the following plugins:
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
### Do I need to install Rust to use Ruff?
Nope! Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
```shell
pip install ruff
```
Ruff ships with wheels for all major platforms, which enables `pip` to install Ruff without relying
on Rust at all.
### Can I write my own plugins for Ruff?
Ruff does not yet support third-party plugins, though a plugin system is within-scope for the
project. See [#283](https://github.com/charliermarsh/ruff/issues/2830) for more.
### Does Ruff support NumPy- or Google-style docstrings?
Yes! To enable a specific docstring convention, start by enabling all `pydocstyle` error codes, and
then selectively disabling based on your [preferred convention](https://www.pydocstyle.org/en/latest/error_codes.html#default-conventions).
For example, if you're coming from `flake8-docstrings`, the following configuration is equivalent to
`--docstring-convention numpy`:
```toml
[tool.ruff]
extend-select = [
"D100",
"D101",
"D102",
"D103",
"D104",
"D105",
"D106",
"D200",
"D201",
"D202",
"D204",
"D205",
"D206",
"D207",
"D208",
"D209",
"D210",
"D211",
"D214",
"D215",
"D300",
"D400",
"D402",
"D403",
"D404",
"D405",
"D406",
"D407",
"D408",
"D409",
"D410",
"D411",
"D412",
"D413",
"D418",
"D419",
]
```
Similarly, the following is equivalent to `--docstring-convention google`:
```toml
[tool.ruff]
extend-select = [
"D100",
"D101",
"D102",
"D103",
"D104",
"D105",
"D106",
"D107",
"D200",
"D201",
"D202",
"D205",
"D206",
"D207",
"D208",
"D209",
"D210",
"D211",
"D212",
"D214",
"D300",
"D402",
"D403",
"D405",
"D410",
"D411",
"D412",
"D414",
"D415",
"D416",
"D417",
"D418",
"D419",
]
```
## Development
ruff is written in Rust (1.63.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
Ruff is written in Rust (1.63.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
for development.
Assuming you have `cargo` installed, you can run:
@@ -412,13 +611,13 @@ cargo clippy
cargo test
```
## Deployment
## Releases
ruff is distributed on [PyPI](https://pypi.org/project/ruff/), and published via [`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).
See: `.github/workflows/release.yaml`.
## Benchmarking
## Benchmarks
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,
which makes it a good target for benchmarking.
@@ -495,9 +694,9 @@ hyperfine --ignore-failure --warmup 5 \
In order, these evaluate:
- ruff
- Ruff
- Pylint
- PyFlakes
- Pyflakes
- autoflake
- pycodestyle
- Flake8

View File

@@ -1,26 +1,27 @@
/// Generate a Markdown-compatible table of supported lint rules.
//! Generate a Markdown-compatible table of supported lint rules.
use strum::IntoEnumIterator;
use ruff::checks::{CheckCode, DEFAULT_CHECK_CODES};
use ruff::checks::{CheckCategory, CheckCode};
fn main() {
println!("| Code | Name | Message | | |");
println!("| ---- | ---- | ------- | --- | --- |");
for check_code in CheckCode::iter() {
let check_kind = check_code.kind();
let default_token = if DEFAULT_CHECK_CODES.contains(&check_code) {
""
} else {
""
};
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
println!(
"| {} | {} | {} | {} | {} |",
check_kind.code().as_ref(),
check_kind.as_ref(),
check_kind.body().replace("|", r"\|"),
default_token,
fix_token
);
for check_category in CheckCategory::iter() {
println!("### {}", check_category.title());
println!();
println!("| Code | Name | Message |");
println!("| ---- | ---- | ------- |");
for check_code in CheckCode::iter() {
if check_code.category() == check_category {
let check_kind = check_code.kind();
println!(
"| {} | {} | {} |",
check_kind.code().as_ref(),
check_kind.as_ref(),
check_kind.body().replace("|", r"\|")
);
}
}
println!();
}
}

View File

@@ -1,4 +1,5 @@
/// Print the AST for a given Python file.
//! Print the AST for a given Python file.
use std::path::PathBuf;
use anyhow::Result;

View File

@@ -1,4 +1,5 @@
/// Print the token stream for a given Python file.
//! Print the token stream for a given Python file.
use std::path::PathBuf;
use anyhow::Result;

View File

@@ -61,7 +61,7 @@ Y = TypeVar("Y", bound="Dict")
Z = TypeVar("Z", "List", "Set")
a = list["Fruit"]
b = Union["Nut", None]
b = Union["""Nut""", None]
c = cast("Vegetable", b)
Field = lambda default=MISSING: field(default=default)

34
resources/test/fixtures/N801.py vendored Normal file
View File

@@ -0,0 +1,34 @@
class bad:
pass
class _bad:
pass
class bad_class:
pass
class Bad_Class:
pass
class BAD_CLASS:
pass
class Good:
pass
class _Good:
pass
class GoodClass:
pass
class GOOD:
pass

26
resources/test/fixtures/N802.py vendored Normal file
View File

@@ -0,0 +1,26 @@
def Bad():
pass
def _Bad():
pass
def BAD():
pass
def BAD_FUNC():
pass
def good():
pass
def _good():
pass
def good_func():
pass

7
resources/test/fixtures/N803.py vendored Normal file
View File

@@ -0,0 +1,7 @@
def func(a, A):
return a, A
class Class:
def method(self, a, A):
return a, A

19
resources/test/fixtures/N804.py vendored Normal file
View File

@@ -0,0 +1,19 @@
class Class:
@classmethod
def bad_class_method(this):
pass
@classmethod
def good_class_method(cls):
pass
def method(self):
pass
@staticmethod
def static_method(x):
return x
def func(x):
return x

26
resources/test/fixtures/N805.py vendored Normal file
View File

@@ -0,0 +1,26 @@
import random
class Class:
def bad_method(this):
pass
if random.random(0, 2) == 0:
def extra_bad_method(this):
pass
def good_method(self):
pass
@classmethod
def class_method(cls):
pass
@staticmethod
def static_method(x):
return x
def func(x):
return x

19
scripts/.flake8 Normal file
View File

@@ -0,0 +1,19 @@
[flake8]
exclude =
# Defaults
.svn,
CVS,
.bzr,
.hg,
.git,
__pycache__,
.tox,
.idea,
.mypy_cache,
.venv,
node_modules,
# Custom
_state_machine.py,
test_fstring.py,
bad_coding2.py,
badsyntax_*.py

View File

@@ -757,7 +757,7 @@ pub fn unnecessary_generator_set(expr: &Expr, func: &Expr, args: &Vec<Expr>) ->
if id == "set" {
if let ExprKind::GeneratorExp { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorList,
CheckKind::UnnecessaryGeneratorSet,
Range::from_located(expr),
));
}
@@ -776,7 +776,7 @@ pub fn unnecessary_generator_dict(expr: &Expr, func: &Expr, args: &Vec<Expr>) ->
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionDict,
CheckKind::UnnecessaryGeneratorDict,
Range::from_located(expr),
));
}
@@ -1331,3 +1331,99 @@ pub fn print_call(
None
}
// pep8-naming
pub fn invalid_class_name(class_def: &Stmt, name: &str) -> Option<Check> {
let stripped = name.strip_prefix('_').unwrap_or(name);
if !stripped
.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false)
|| stripped.contains('_')
{
return Some(Check::new(
CheckKind::InvalidClassName(name.to_string()),
Range::from_located(class_def),
));
}
None
}
pub fn invalid_function_name(func_def: &Stmt, name: &str) -> Option<Check> {
if name.chars().any(|c| c.is_uppercase()) {
return Some(Check::new(
CheckKind::InvalidFunctionName(name.to_string()),
Range::from_located(func_def),
));
}
None
}
pub fn invalid_argument_name(location: Range, name: &str) -> Option<Check> {
if name.chars().any(|c| c.is_uppercase()) {
return Some(Check::new(
CheckKind::InvalidArgumentName(name.to_string()),
location,
));
}
None
}
pub fn invalid_first_argument_name_for_class_method(
scope: &Scope,
decorator_list: &[Expr],
args: &Arguments,
) -> Option<Check> {
if !matches!(scope.kind, ScopeKind::Class) {
return None;
}
if decorator_list.iter().any(|decorator| {
if let ExprKind::Name { id, .. } = &decorator.node {
id == "classmethod"
} else {
false
}
}) {
if let Some(arg) = args.args.first() {
if arg.node.arg != "cls" {
return Some(Check::new(
CheckKind::InvalidFirstArgumentNameForClassMethod,
Range::from_located(arg),
));
}
}
}
None
}
pub fn invalid_first_argument_name_for_method(
scope: &Scope,
decorator_list: &[Expr],
args: &Arguments,
) -> Option<Check> {
if !matches!(scope.kind, ScopeKind::Class) {
return None;
}
if decorator_list.iter().any(|decorator| {
if let ExprKind::Name { id, .. } = &decorator.node {
id == "classmethod" || id == "staticmethod"
} else {
false
}
}) {
return None;
}
if let Some(arg) = args.args.first() {
if arg.node.arg != "self" {
return Some(Check::new(
CheckKind::InvalidFirstArgumentNameForMethod,
Range::from_located(arg),
));
}
}
None
}

View File

@@ -1,3 +1,9 @@
// cacache uses asyncd-std which has no wasm support, so currently no caching support on wasm
#![cfg_attr(
target_family = "wasm",
allow(unused_imports, unused_variables, dead_code)
)]
use std::collections::hash_map::DefaultHasher;
use std::fs::{create_dir_all, File, Metadata};
use std::hash::{Hash, Hasher};
@@ -5,6 +11,7 @@ use std::io::Write;
use std::path::Path;
use anyhow::Result;
#[cfg(not(target_family = "wasm"))]
use cacache::Error::EntryNotFound;
use filetime::FileTime;
use log::error;
@@ -107,6 +114,7 @@ pub fn get(
return None;
};
#[cfg(not(target_family = "wasm"))] // cacache needs async-std which doesn't support wasm
match cacache::read_sync(cache_dir(), cache_key(path, settings, autofix)) {
Ok(encoded) => match bincode::deserialize::<CheckResult>(&encoded[..]) {
Ok(CheckResult {
@@ -137,12 +145,14 @@ pub fn set(
return;
};
#[cfg(not(target_family = "wasm"))] // modification date not supported on wasm
let check_result = CheckResultRef {
metadata: &CacheMetadata {
mtime: FileTime::from_last_modification_time(metadata).unix_seconds(),
},
messages,
};
#[cfg(not(target_family = "wasm"))] // cacache needs async-std which doesn't support wasm
if let Err(e) = cacache::write_sync(
cache_dir(),
cache_key(path, settings, autofix),

View File

@@ -3,7 +3,6 @@ use std::ops::Deref;
use std::path::Path;
use log::error;
use rustpython_ast::Location;
use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
KeywordData, Operator, Stmt, StmtKind, Suite,
@@ -34,7 +33,6 @@ pub const GLOBAL_SCOPE_INDEX: usize = 0;
pub struct Checker<'a> {
// Input data.
pub(crate) path: &'a Path,
// TODO(charlie): Separate immutable from mutable state. (None of these should ever change.)
pub(crate) locator: SourceCodeLocator<'a>,
pub(crate) settings: &'a Settings,
pub(crate) autofix: &'a fixer::Mode,
@@ -216,6 +214,32 @@ where
}
}
if self.settings.enabled.contains(&CheckCode::N802) {
if let Some(check) = checkers::invalid_function_name(stmt, name) {
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N804) {
if let Some(check) = checkers::invalid_first_argument_name_for_class_method(
self.current_scope(),
decorator_list,
args,
) {
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N805) {
if let Some(check) = checkers::invalid_first_argument_name_for_method(
self.current_scope(),
decorator_list,
args,
) {
self.checks.push(check);
}
}
self.check_builtin_shadowing(name, Range::from_located(stmt), true);
// Visit the decorators and arguments, but avoid the body, which will be deferred.
@@ -304,6 +328,12 @@ where
}
}
if self.settings.enabled.contains(&CheckCode::N801) {
if let Some(check) = checkers::invalid_class_name(stmt, name) {
self.checks.push(check);
}
}
self.check_builtin_shadowing(
name,
self.locate_check(Range::from_located(stmt)),
@@ -1336,6 +1366,14 @@ where
}
}
if self.settings.enabled.contains(&CheckCode::N803) {
if let Some(check) =
checkers::invalid_argument_name(Range::from_located(arg), &arg.node.arg)
{
self.checks.push(check);
}
}
self.check_builtin_arg_shadowing(&arg.node.arg, Range::from_located(arg));
}
}
@@ -1711,29 +1749,6 @@ impl<'a> Checker<'a> {
'b: 'a,
{
while let Some((range, expression)) = self.deferred_string_annotations.pop() {
// HACK(charlie): We need to modify `range` such that it represents the range of the
// expression _within_ the string annotation (as opposed to the range of the string
// annotation itself). RustPython seems to return an off-by-one start column for every
// string value, so we check for double quotes (which are really triple quotes).
let contents = self.locator.slice_source_code_at(&range.location);
let range = if contents.starts_with("\"\"") || contents.starts_with("\'\'") {
Range {
location: Location::new(range.location.row(), range.location.column() + 2),
end_location: Location::new(
range.end_location.row(),
range.end_location.column() - 2,
),
}
} else {
Range {
location: Location::new(range.location.row(), range.location.column()),
end_location: Location::new(
range.end_location.row(),
range.end_location.column() - 1,
),
}
};
if let Ok(mut expr) = parser::parse_expression(expression, "<filename>") {
relocate_expr(&mut expr, range);
allocator.push(expr);
@@ -2029,10 +2044,8 @@ impl<'a> Checker<'a> {
}
fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) {
let scope = self.current_scope();
// flake8-builtins
if is_attribute && matches!(scope.kind, ScopeKind::Class) {
if is_attribute && matches!(self.current_scope().kind, ScopeKind::Class) {
if self.settings.enabled.contains(&CheckCode::A003) {
if let Some(check) = checkers::builtin_shadowing(
name,
@@ -2054,6 +2067,7 @@ impl<'a> Checker<'a> {
}
fn check_builtin_arg_shadowing(&mut self, name: &str, location: Range) {
// flake8-builtins
if self.settings.enabled.contains(&CheckCode::A002) {
if let Some(check) = checkers::builtin_shadowing(
name,

View File

@@ -200,10 +200,47 @@ pub enum CheckCode {
D417,
D418,
D419,
// pep8-naming
N801,
N802,
N803,
N804,
N805,
// Meta
M001,
}
#[derive(EnumIter, Debug, PartialEq, Eq)]
pub enum CheckCategory {
Pyflakes,
Pycodestyle,
Pydocstyle,
Pyupgrade,
Pep8Naming,
Flake8Comprehensions,
Flake8Bugbear,
Flake8Builtins,
Flake8Print,
Meta,
}
impl CheckCategory {
pub fn title(&self) -> &'static str {
match self {
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Bugbear => "flake8-bugbear",
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::Pep8Naming => "pep8-naming",
CheckCategory::Meta => "Meta rules",
}
}
}
#[allow(clippy::upper_case_acronyms)]
pub enum LintSource {
AST,
@@ -346,6 +383,12 @@ pub enum CheckKind {
SectionUnderlineNotOverIndented(String),
SkipDocstring,
UsesTripleQuotes,
// pep8-naming
InvalidClassName(String),
InvalidFunctionName(String),
InvalidArgumentName(String),
InvalidFirstArgumentNameForClassMethod,
InvalidFirstArgumentNameForMethod,
// Meta
UnusedNOQA(Option<Vec<String>>),
}
@@ -354,7 +397,7 @@ impl CheckCode {
/// The source for the check (either the AST, the filesystem, or the physical lines).
pub fn lint_source(&self) -> &'static LintSource {
match self {
CheckCode::E501 | CheckCode::M001 => &LintSource::Lines,
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 => &LintSource::Lines,
CheckCode::E902 => &LintSource::FileSystem,
_ => &LintSource::AST,
}
@@ -515,10 +558,146 @@ impl CheckCode {
}
CheckCode::D418 => CheckKind::SkipDocstring,
CheckCode::D419 => CheckKind::NonEmpty,
// pep8-naming
CheckCode::N801 => CheckKind::InvalidClassName("...".to_string()),
CheckCode::N802 => CheckKind::InvalidFunctionName("...".to_string()),
CheckCode::N803 => CheckKind::InvalidArgumentName("...".to_string()),
CheckCode::N804 => CheckKind::InvalidFirstArgumentNameForClassMethod,
CheckCode::N805 => CheckKind::InvalidFirstArgumentNameForMethod,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
}
pub fn category(&self) -> CheckCategory {
match self {
CheckCode::E402 => CheckCategory::Pycodestyle,
CheckCode::E501 => CheckCategory::Pycodestyle,
CheckCode::E711 => CheckCategory::Pycodestyle,
CheckCode::E712 => CheckCategory::Pycodestyle,
CheckCode::E713 => CheckCategory::Pycodestyle,
CheckCode::E714 => CheckCategory::Pycodestyle,
CheckCode::E721 => CheckCategory::Pycodestyle,
CheckCode::E722 => CheckCategory::Pycodestyle,
CheckCode::E731 => CheckCategory::Pycodestyle,
CheckCode::E741 => CheckCategory::Pycodestyle,
CheckCode::E742 => CheckCategory::Pycodestyle,
CheckCode::E743 => CheckCategory::Pycodestyle,
CheckCode::E902 => CheckCategory::Pycodestyle,
CheckCode::E999 => CheckCategory::Pycodestyle,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::F401 => CheckCategory::Pyflakes,
CheckCode::F402 => CheckCategory::Pyflakes,
CheckCode::F403 => CheckCategory::Pyflakes,
CheckCode::F404 => CheckCategory::Pyflakes,
CheckCode::F405 => CheckCategory::Pyflakes,
CheckCode::F406 => CheckCategory::Pyflakes,
CheckCode::F407 => CheckCategory::Pyflakes,
CheckCode::F541 => CheckCategory::Pyflakes,
CheckCode::F601 => CheckCategory::Pyflakes,
CheckCode::F602 => CheckCategory::Pyflakes,
CheckCode::F621 => CheckCategory::Pyflakes,
CheckCode::F622 => CheckCategory::Pyflakes,
CheckCode::F631 => CheckCategory::Pyflakes,
CheckCode::F632 => CheckCategory::Pyflakes,
CheckCode::F633 => CheckCategory::Pyflakes,
CheckCode::F634 => CheckCategory::Pyflakes,
CheckCode::F701 => CheckCategory::Pyflakes,
CheckCode::F702 => CheckCategory::Pyflakes,
CheckCode::F704 => CheckCategory::Pyflakes,
CheckCode::F706 => CheckCategory::Pyflakes,
CheckCode::F707 => CheckCategory::Pyflakes,
CheckCode::F722 => CheckCategory::Pyflakes,
CheckCode::F821 => CheckCategory::Pyflakes,
CheckCode::F822 => CheckCategory::Pyflakes,
CheckCode::F823 => CheckCategory::Pyflakes,
CheckCode::F831 => CheckCategory::Pyflakes,
CheckCode::F841 => CheckCategory::Pyflakes,
CheckCode::F901 => CheckCategory::Pyflakes,
CheckCode::A001 => CheckCategory::Flake8Builtins,
CheckCode::A002 => CheckCategory::Flake8Builtins,
CheckCode::A003 => CheckCategory::Flake8Builtins,
CheckCode::B011 => CheckCategory::Flake8Bugbear,
CheckCode::B014 => CheckCategory::Flake8Bugbear,
CheckCode::B025 => CheckCategory::Flake8Bugbear,
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
CheckCode::C402 => CheckCategory::Flake8Comprehensions,
CheckCode::C403 => CheckCategory::Flake8Comprehensions,
CheckCode::C404 => CheckCategory::Flake8Comprehensions,
CheckCode::C405 => CheckCategory::Flake8Comprehensions,
CheckCode::C406 => CheckCategory::Flake8Comprehensions,
CheckCode::C408 => CheckCategory::Flake8Comprehensions,
CheckCode::C409 => CheckCategory::Flake8Comprehensions,
CheckCode::C410 => CheckCategory::Flake8Comprehensions,
CheckCode::C411 => CheckCategory::Flake8Comprehensions,
CheckCode::C413 => CheckCategory::Flake8Comprehensions,
CheckCode::C414 => CheckCategory::Flake8Comprehensions,
CheckCode::C415 => CheckCategory::Flake8Comprehensions,
CheckCode::C416 => CheckCategory::Flake8Comprehensions,
CheckCode::C417 => CheckCategory::Flake8Comprehensions,
CheckCode::T201 => CheckCategory::Flake8Print,
CheckCode::T203 => CheckCategory::Flake8Print,
CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U002 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade,
CheckCode::U004 => CheckCategory::Pyupgrade,
CheckCode::U005 => CheckCategory::Pyupgrade,
CheckCode::U006 => CheckCategory::Pyupgrade,
CheckCode::U007 => CheckCategory::Pyupgrade,
CheckCode::U008 => CheckCategory::Pyupgrade,
CheckCode::D100 => CheckCategory::Pydocstyle,
CheckCode::D101 => CheckCategory::Pydocstyle,
CheckCode::D102 => CheckCategory::Pydocstyle,
CheckCode::D103 => CheckCategory::Pydocstyle,
CheckCode::D104 => CheckCategory::Pydocstyle,
CheckCode::D105 => CheckCategory::Pydocstyle,
CheckCode::D106 => CheckCategory::Pydocstyle,
CheckCode::D107 => CheckCategory::Pydocstyle,
CheckCode::D200 => CheckCategory::Pydocstyle,
CheckCode::D201 => CheckCategory::Pydocstyle,
CheckCode::D202 => CheckCategory::Pydocstyle,
CheckCode::D203 => CheckCategory::Pydocstyle,
CheckCode::D204 => CheckCategory::Pydocstyle,
CheckCode::D205 => CheckCategory::Pydocstyle,
CheckCode::D206 => CheckCategory::Pydocstyle,
CheckCode::D207 => CheckCategory::Pydocstyle,
CheckCode::D208 => CheckCategory::Pydocstyle,
CheckCode::D209 => CheckCategory::Pydocstyle,
CheckCode::D210 => CheckCategory::Pydocstyle,
CheckCode::D211 => CheckCategory::Pydocstyle,
CheckCode::D212 => CheckCategory::Pydocstyle,
CheckCode::D213 => CheckCategory::Pydocstyle,
CheckCode::D214 => CheckCategory::Pydocstyle,
CheckCode::D215 => CheckCategory::Pydocstyle,
CheckCode::D300 => CheckCategory::Pydocstyle,
CheckCode::D400 => CheckCategory::Pydocstyle,
CheckCode::D402 => CheckCategory::Pydocstyle,
CheckCode::D403 => CheckCategory::Pydocstyle,
CheckCode::D404 => CheckCategory::Pydocstyle,
CheckCode::D405 => CheckCategory::Pydocstyle,
CheckCode::D406 => CheckCategory::Pydocstyle,
CheckCode::D407 => CheckCategory::Pydocstyle,
CheckCode::D408 => CheckCategory::Pydocstyle,
CheckCode::D409 => CheckCategory::Pydocstyle,
CheckCode::D410 => CheckCategory::Pydocstyle,
CheckCode::D411 => CheckCategory::Pydocstyle,
CheckCode::D412 => CheckCategory::Pydocstyle,
CheckCode::D413 => CheckCategory::Pydocstyle,
CheckCode::D414 => CheckCategory::Pydocstyle,
CheckCode::D415 => CheckCategory::Pydocstyle,
CheckCode::D416 => CheckCategory::Pydocstyle,
CheckCode::D417 => CheckCategory::Pydocstyle,
CheckCode::D418 => CheckCategory::Pydocstyle,
CheckCode::D419 => CheckCategory::Pydocstyle,
CheckCode::N801 => CheckCategory::Pep8Naming,
CheckCode::N802 => CheckCategory::Pep8Naming,
CheckCode::N803 => CheckCategory::Pep8Naming,
CheckCode::N804 => CheckCategory::Pep8Naming,
CheckCode::N805 => CheckCategory::Pep8Naming,
CheckCode::M001 => CheckCategory::Meta,
}
}
}
impl CheckKind {
@@ -652,6 +831,12 @@ impl CheckKind {
CheckKind::SectionUnderlineNotOverIndented(_) => &CheckCode::D215,
CheckKind::SkipDocstring => &CheckCode::D418,
CheckKind::UsesTripleQuotes => &CheckCode::D300,
// pep8-naming
CheckKind::InvalidClassName(_) => &CheckCode::N801,
CheckKind::InvalidFunctionName(_) => &CheckCode::N802,
CheckKind::InvalidArgumentName(_) => &CheckCode::N803,
CheckKind::InvalidFirstArgumentNameForClassMethod => &CheckCode::N804,
CheckKind::InvalidFirstArgumentNameForMethod => &CheckCode::N805,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
@@ -966,7 +1151,7 @@ impl CheckKind {
CheckKind::PublicPackage => "Missing docstring in public package".to_string(),
CheckKind::MagicMethod => "Missing docstring in magic method".to_string(),
CheckKind::PublicNestedClass => "Missing docstring in public nested class".to_string(),
CheckKind::PublicInit => "Missing docstring in __init__".to_string(),
CheckKind::PublicInit => "Missing docstring in `__init__`".to_string(),
CheckKind::NoThisPrefix => {
"First word of the docstring should not be `This`".to_string()
}
@@ -1026,6 +1211,22 @@ impl CheckKind {
}
CheckKind::NoUnderIndentation => "Docstring is under-indented".to_string(),
CheckKind::NoOverIndentation => "Docstring is over-indented".to_string(),
// pep8-naming
CheckKind::InvalidClassName(name) => {
format!("class name `{name}` should use CapWords convention ")
}
CheckKind::InvalidFunctionName(name) => {
format!("function name `{name}` should be lowercase")
}
CheckKind::InvalidArgumentName(name) => {
format!("argument name `{name}` should be lowercase")
}
CheckKind::InvalidFirstArgumentNameForClassMethod => {
"first argument of a classmethod should be named `cls`".to_string()
}
CheckKind::InvalidFirstArgumentNameForMethod => {
"first argument of a method should be named `self`".to_string()
}
// Meta
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),

View File

@@ -8,7 +8,6 @@ use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::docstrings::google::check_google_section;
use crate::docstrings::helpers;
use crate::docstrings::helpers::{indentation, leading_space};
use crate::docstrings::numpy::check_numpy_section;
use crate::docstrings::sections::section_contexts;
@@ -138,7 +137,7 @@ pub fn one_liner(checker: &mut Checker, definition: &Definition) {
if non_empty_line_count == 1 && line_count > 1 {
checker.add_check(Check::new(
CheckKind::FitsOnOneLine,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -164,7 +163,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
{
let (before, _, after) = checker.locator.partition_source_code_at(
&Range::from_located(parent),
&helpers::range_for(docstring),
&Range::from_located(docstring),
);
if checker.settings.enabled.contains(&CheckCode::D201) {
@@ -177,7 +176,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
if blank_lines_before != 0 {
checker.add_check(Check::new(
CheckKind::NoBlankLineBeforeFunction(blank_lines_before),
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -202,7 +201,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
{
checker.add_check(Check::new(
CheckKind::NoBlankLineAfterFunction(blank_lines_after),
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -224,7 +223,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
{
let (before, _, after) = checker.locator.partition_source_code_at(
&Range::from_located(parent),
&helpers::range_for(docstring),
&Range::from_located(docstring),
);
if checker.settings.enabled.contains(&CheckCode::D203)
@@ -241,7 +240,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
{
checker.add_check(Check::new(
CheckKind::NoBlankLineBeforeClass(blank_lines_before),
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
if blank_lines_before != 1
@@ -249,7 +248,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
{
checker.add_check(Check::new(
CheckKind::OneBlankLineBeforeClass(blank_lines_before),
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -267,7 +266,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
if !all_blank_after && blank_lines_after != 1 {
checker.add_check(Check::new(
CheckKind::OneBlankLineAfterClass(blank_lines_after),
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -297,7 +296,7 @@ pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) {
if lines_count > 1 && blanks_count != 1 {
checker.add_check(Check::new(
CheckKind::NoBlankLineAfterSummary,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -327,7 +326,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
if checker.settings.enabled.contains(&CheckCode::D206) {
checker.add_check(Check::new(
CheckKind::IndentWithSpaces,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
has_seen_tab = true;
@@ -352,7 +351,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
if checker.settings.enabled.contains(&CheckCode::D206) {
checker.add_check(Check::new(
CheckKind::IndentWithSpaces,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
has_seen_tab = true;
@@ -364,7 +363,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
if checker.settings.enabled.contains(&CheckCode::D208) {
checker.add_check(Check::new(
CheckKind::NoOverIndentation,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
has_seen_over_indent = true;
@@ -376,7 +375,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
if checker.settings.enabled.contains(&CheckCode::D207) {
checker.add_check(Check::new(
CheckKind::NoUnderIndentation,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
has_seen_under_indent = true;
@@ -403,13 +402,13 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
if line_count > 1 {
let content = checker
.locator
.slice_source_code_range(&helpers::range_for(docstring));
.slice_source_code_range(&Range::from_located(docstring));
if let Some(line) = content.lines().last() {
let line = line.trim();
if line != "\"\"\"" && line != "'''" {
checker.add_check(Check::new(
CheckKind::NewLineAfterLastParagraph,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -436,7 +435,7 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
if line.starts_with(' ') || (matches!(lines.next(), None) && line.ends_with(' ')) {
checker.add_check(Check::new(
CheckKind::NoSurroundingWhitespace,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -455,21 +454,29 @@ pub fn multi_line_summary_start(checker: &mut Checker, definition: &Definition)
if string.lines().nth(1).is_some() {
let content = checker
.locator
.slice_source_code_range(&helpers::range_for(docstring));
.slice_source_code_range(&Range::from_located(docstring));
if let Some(first_line) = content.lines().next() {
let first_line = first_line.trim();
if first_line == "\"\"\"" || first_line == "'''" {
let first_line = first_line.trim().to_lowercase();
let starts_with_triple = first_line == "\"\"\""
|| first_line == "'''"
|| first_line == "u\"\"\""
|| first_line == "u'''"
|| first_line == "r\"\"\""
|| first_line == "r'''"
|| first_line == "ur\"\"\""
|| first_line == "ur'''";
if starts_with_triple {
if checker.settings.enabled.contains(&CheckCode::D212) {
checker.add_check(Check::new(
CheckKind::MultiLineSummaryFirstLine,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
} else {
if checker.settings.enabled.contains(&CheckCode::D213) {
checker.add_check(Check::new(
CheckKind::MultiLineSummarySecondLine,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -489,19 +496,26 @@ pub fn triple_quotes(checker: &mut Checker, definition: &Definition) {
{
let content = checker
.locator
.slice_source_code_range(&helpers::range_for(docstring));
if string.contains("\"\"\"") {
if !content.starts_with("'''") {
.slice_source_code_range(&Range::from_located(docstring));
if let Some(first_line) = content.lines().next() {
let first_line = first_line.trim().to_lowercase();
let starts_with_triple = if string.contains("\"\"\"") {
first_line.starts_with("'''")
|| first_line.starts_with("u'''")
|| first_line.starts_with("r'''")
|| first_line.starts_with("ur'''")
} else {
first_line.starts_with("\"\"\"")
|| first_line.starts_with("u\"\"\"")
|| first_line.starts_with("r\"\"\"")
|| first_line.starts_with("ur\"\"\"")
};
if !starts_with_triple {
checker.add_check(Check::new(
CheckKind::UsesTripleQuotes,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
} else if !content.starts_with("\"\"\"") {
checker.add_check(Check::new(
CheckKind::UsesTripleQuotes,
helpers::range_for(docstring),
));
}
}
}
@@ -519,7 +533,7 @@ pub fn ends_with_period(checker: &mut Checker, definition: &Definition) {
if !string.ends_with('.') {
checker.add_check(Check::new(
CheckKind::EndsInPeriod,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -544,7 +558,7 @@ pub fn no_signature(checker: &mut Checker, definition: &Definition) {
if first_line.contains(&format!("{name}(")) {
checker.add_check(Check::new(
CheckKind::NoSignature,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -579,7 +593,7 @@ pub fn capitalized(checker: &mut Checker, definition: &Definition) {
if !first_char.is_uppercase() {
checker.add_check(Check::new(
CheckKind::FirstLineCapitalized,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -609,7 +623,7 @@ pub fn starts_with_this(checker: &mut Checker, definition: &Definition) {
{
checker.add_check(Check::new(
CheckKind::NoThisPrefix,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -629,7 +643,7 @@ pub fn ends_with_punctuation(checker: &mut Checker, definition: &Definition) {
if !(string.ends_with('.') || string.ends_with('!') || string.ends_with('?')) {
checker.add_check(Check::new(
CheckKind::EndsInPunctuation,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -666,7 +680,7 @@ pub fn not_empty(checker: &mut Checker, definition: &Definition) -> bool {
if checker.settings.enabled.contains(&CheckCode::D419) {
checker.add_check(Check::new(
CheckKind::NonEmpty,
helpers::range_for(docstring),
Range::from_located(docstring),
));
}
return false;

View File

@@ -2,12 +2,12 @@
use std::collections::BTreeSet;
use crate::ast::types::Range;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::docstrings::helpers::range_for;
use crate::docstrings::sections;
use crate::docstrings::sections::SectionContext;
use crate::docstrings::styles::SectionStyle;
@@ -136,7 +136,7 @@ pub(crate) fn check_google_section(
.expect("Sections are only available for docstrings.");
checker.add_check(Check::new(
CheckKind::SectionNameEndsInColon(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
))
}
}

View File

@@ -20,18 +20,9 @@ pub fn leading_space(line: &str) -> String {
/// Extract the leading indentation from a docstring.
pub fn indentation<'a>(checker: &'a mut Checker, docstring: &Expr) -> &'a str {
let range = range_for(docstring);
let range = Range::from_located(docstring);
checker.locator.slice_source_code_range(&Range {
location: Location::new(range.location.row(), 1),
end_location: Location::new(range.location.row(), range.location.column()),
})
}
/// Extract the source code range for a docstring.
pub fn range_for(docstring: &Expr) -> Range {
// RustPython currently omits the first quotation mark in a string, so offset the location.
Range {
location: Location::new(docstring.location.row(), docstring.location.column() - 1),
end_location: docstring.end_location,
}
}

View File

@@ -2,11 +2,11 @@
use std::collections::BTreeSet;
use crate::ast::types::Range;
use once_cell::sync::Lazy;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::docstrings::helpers::range_for;
use crate::docstrings::sections::SectionContext;
use crate::docstrings::styles::SectionStyle;
use crate::docstrings::types::Definition;
@@ -100,7 +100,7 @@ pub(crate) fn check_numpy_section(
.expect("Sections are only available for docstrings.");
checker.add_check(Check::new(
CheckKind::NewLineAfterSectionName(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
))
}
}

View File

@@ -8,7 +8,6 @@ use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::docstrings::helpers;
use crate::docstrings::helpers::range_for;
use crate::docstrings::styles::SectionStyle;
use crate::docstrings::types::{Definition, DefinitionKind};
use crate::visibility::is_static;
@@ -132,13 +131,13 @@ fn check_blanks_and_section_underline(
if checker.settings.enabled.contains(&CheckCode::D407) {
checker.add_check(Check::new(
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
));
}
if checker.settings.enabled.contains(&CheckCode::D414) {
checker.add_check(Check::new(
CheckKind::NonEmptySection(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
));
}
return;
@@ -153,7 +152,7 @@ fn check_blanks_and_section_underline(
if checker.settings.enabled.contains(&CheckCode::D407) {
checker.add_check(Check::new(
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
));
}
if blank_lines_after_header > 0 {
@@ -162,7 +161,7 @@ fn check_blanks_and_section_underline(
CheckKind::NoBlankLinesBetweenHeaderAndContent(
context.section_name.to_string(),
),
range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -171,7 +170,7 @@ fn check_blanks_and_section_underline(
if checker.settings.enabled.contains(&CheckCode::D408) {
checker.add_check(Check::new(
CheckKind::SectionUnderlineAfterName(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -188,7 +187,7 @@ fn check_blanks_and_section_underline(
CheckKind::SectionUnderlineMatchesSectionLength(
context.section_name.to_string(),
),
range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -199,7 +198,7 @@ fn check_blanks_and_section_underline(
{
checker.add_check(Check::new(
CheckKind::SectionUnderlineNotOverIndented(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -214,7 +213,7 @@ fn check_blanks_and_section_underline(
if checker.settings.enabled.contains(&CheckCode::D414) {
checker.add_check(Check::new(
CheckKind::NonEmptySection(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
));
}
} else {
@@ -223,7 +222,7 @@ fn check_blanks_and_section_underline(
CheckKind::NoBlankLinesBetweenHeaderAndContent(
context.section_name.to_string(),
),
range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -232,7 +231,7 @@ fn check_blanks_and_section_underline(
if checker.settings.enabled.contains(&CheckCode::D414) {
checker.add_check(Check::new(
CheckKind::NonEmptySection(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
));
}
}
@@ -259,7 +258,7 @@ pub(crate) fn check_common_section(
{
checker.add_check(Check::new(
CheckKind::CapitalizeSectionName(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
))
}
}
@@ -270,7 +269,7 @@ pub(crate) fn check_common_section(
{
checker.add_check(Check::new(
CheckKind::SectionNotOverIndented(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
))
}
}
@@ -285,14 +284,14 @@ pub(crate) fn check_common_section(
if checker.settings.enabled.contains(&CheckCode::D413) {
checker.add_check(Check::new(
CheckKind::BlankLineAfterLastSection(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
))
}
} else {
if checker.settings.enabled.contains(&CheckCode::D410) {
checker.add_check(Check::new(
CheckKind::BlankLineAfterSection(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
))
}
}
@@ -302,7 +301,7 @@ pub(crate) fn check_common_section(
if !context.previous_line.is_empty() {
checker.add_check(Check::new(
CheckKind::BlankLineBeforeSection(context.section_name.to_string()),
range_for(docstring),
Range::from_located(docstring),
))
}
}

View File

@@ -4,6 +4,7 @@ use std::io::Write;
use std::path::Path;
use anyhow::Result;
#[cfg(not(target_family = "wasm"))]
use log::debug;
use rustpython_parser::lexer::LexResult;
use rustpython_parser::{lexer, parser};
@@ -122,6 +123,7 @@ pub fn lint_stdin(
.collect())
}
#[cfg_attr(target_family = "wasm", allow(unused_variables))]
pub fn lint_path(
path: &Path,
settings: &Settings,
@@ -131,6 +133,7 @@ pub fn lint_path(
let metadata = path.metadata()?;
// Check the cache.
#[cfg(not(target_family = "wasm"))]
if let Some(messages) = cache::get(path, &metadata, settings, autofix, mode) {
debug!("Cache hit for: {}", path.to_string_lossy());
return Ok(messages);
@@ -166,6 +169,7 @@ pub fn lint_path(
filename: path.to_string_lossy().to_string(),
})
.collect();
#[cfg(not(target_family = "wasm"))]
cache::set(path, &metadata, settings, autofix, &messages, mode);
Ok(messages)
@@ -349,6 +353,11 @@ mod tests {
#[test_case(CheckCode::F831, Path::new("F831.py"); "F831")]
#[test_case(CheckCode::F841, Path::new("F841.py"); "F841")]
#[test_case(CheckCode::F901, Path::new("F901.py"); "F901")]
#[test_case(CheckCode::N801, Path::new("N801.py"); "N801")]
#[test_case(CheckCode::N802, Path::new("N802.py"); "N802")]
#[test_case(CheckCode::N803, Path::new("N803.py"); "N803")]
#[test_case(CheckCode::N804, Path::new("N804.py"); "N804")]
#[test_case(CheckCode::N805, Path::new("N805.py"); "N805")]
#[test_case(CheckCode::T201, Path::new("T201.py"); "T201")]
#[test_case(CheckCode::T203, Path::new("T203.py"); "T203")]
#[test_case(CheckCode::U001, Path::new("U001.py"); "U001")]

View File

@@ -9,9 +9,11 @@ use clap::Parser;
use colored::Colorize;
use log::{debug, error};
use notify::{raw_watcher, RecursiveMode, Watcher};
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
use walkdir::DirEntry;
#[cfg(not(target_family = "wasm"))]
use ruff::cache;
use ruff::checks::CheckCode;
use ruff::checks::CheckKind;
@@ -29,9 +31,24 @@ use ruff::settings::RawSettings;
use ruff::settings::{FilePattern, PerFileIgnore, Settings};
use ruff::tell_user;
#[cfg(feature = "update-informer")]
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
#[cfg(feature = "update-informer")]
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
/// Shim that calls par_iter except for wasm because there's no wasm support in rayon yet
/// (there is a shim to be used for the web, but it requires js cooperation)
/// Unfortunately, ParallelIterator does not implement Iterator so the signatures diverge
#[cfg(not(target_family = "wasm"))]
fn par_iter<T: Sync>(iterable: &Vec<T>) -> impl ParallelIterator<Item = &T> {
iterable.par_iter()
}
#[cfg(target_family = "wasm")]
fn par_iter<T: Sync>(iterable: &Vec<T>) -> impl Iterator<Item = &T> {
iterable.iter()
}
#[cfg(feature = "update-informer")]
fn check_for_updates() {
use update_informer::{registry, Check};
@@ -104,8 +121,7 @@ fn run_once(
debug!("Identified files to lint in: {:?}", duration);
let start = Instant::now();
let mut messages: Vec<Message> = paths
.par_iter()
let mut messages: Vec<Message> = par_iter(&paths)
.map(|entry| {
match entry {
Ok(entry) => {
@@ -160,8 +176,7 @@ fn add_noqa(files: &[PathBuf], settings: &Settings) -> Result<usize> {
debug!("Identified files to lint in: {:?}", duration);
let start = Instant::now();
let modifications: usize = paths
.par_iter()
let modifications: usize = par_iter(&paths)
.map(|entry| match entry {
Ok(entry) => {
let path = entry.path();
@@ -190,8 +205,7 @@ fn autoformat(files: &[PathBuf], settings: &Settings) -> Result<usize> {
debug!("Identified files to lint in: {:?}", duration);
let start = Instant::now();
let modifications = paths
.par_iter()
let modifications = par_iter(&paths)
.map(|entry| {
let path = entry.path();
autoformat_path(path)
@@ -302,6 +316,7 @@ fn inner_main() -> Result<ExitCode> {
return Ok(ExitCode::SUCCESS);
}
#[cfg(not(target_family = "wasm"))]
cache::init()?;
let mut printer = Printer::new(cli.format, cli.verbose);
@@ -367,25 +382,30 @@ fn inner_main() -> Result<ExitCode> {
println!("Formatted {modifications} files.");
}
} else {
let (messages, print_messages) = if cli.files == vec![PathBuf::from("-")] {
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
let path = Path::new(&filename);
(
run_once_stdin(&settings, path, cli.fix)?,
!cli.quiet && !cli.fix,
)
} else {
(
run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?,
!cli.quiet,
)
};
if print_messages {
let (messages, should_print_messages, should_check_updates) =
if cli.files == vec![PathBuf::from("-")] {
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
let path = Path::new(&filename);
(
run_once_stdin(&settings, path, cli.fix)?,
!cli.quiet && !cli.fix,
false,
)
} else {
(
run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?,
!cli.quiet,
!cli.quiet,
)
};
if should_print_messages {
printer.write_once(&messages)?;
}
#[cfg(feature = "update-informer")]
check_for_updates();
if should_check_updates {
check_for_updates();
}
if messages.iter().any(|message| !message.fixed) && !cli.exit_zero {
return Ok(ExitCode::FAILURE);

View File

@@ -104,6 +104,7 @@ impl Printer {
}
pub fn clear_screen(&mut self) -> Result<()> {
#[cfg(not(target_family = "wasm"))]
clearscreen::clear()?;
Ok(())
}

View File

@@ -2,7 +2,7 @@
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryGeneratorList
- kind: UnnecessaryGeneratorSet
location:
row: 1
column: 5

View File

@@ -2,7 +2,7 @@
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryListComprehensionDict
- kind: UnnecessaryGeneratorDict
location:
row: 1
column: 5

View File

@@ -5,7 +5,7 @@ expression: checks
- kind: UsesTripleQuotes
location:
row: 302
column: 6
column: 5
end_location:
row: 302
column: 20
@@ -13,7 +13,7 @@ expression: checks
- kind: UsesTripleQuotes
location:
row: 307
column: 6
column: 5
end_location:
row: 307
column: 20
@@ -21,7 +21,7 @@ expression: checks
- kind: UsesTripleQuotes
location:
row: 312
column: 6
column: 5
end_location:
row: 312
column: 16
@@ -29,7 +29,7 @@ expression: checks
- kind: UsesTripleQuotes
location:
row: 317
column: 6
column: 5
end_location:
row: 317
column: 16
@@ -37,7 +37,7 @@ expression: checks
- kind: UsesTripleQuotes
location:
row: 323
column: 6
column: 5
end_location:
row: 323
column: 17

View File

@@ -5,7 +5,7 @@ expression: checks
- kind: FStringMissingPlaceholders
location:
row: 4
column: 7
column: 5
end_location:
row: 4
column: 11
@@ -13,7 +13,7 @@ expression: checks
- kind: FStringMissingPlaceholders
location:
row: 5
column: 7
column: 5
end_location:
row: 5
column: 11
@@ -21,7 +21,7 @@ expression: checks
- kind: FStringMissingPlaceholders
location:
row: 7
column: 7
column: 5
end_location:
row: 7
column: 11

View File

@@ -5,7 +5,7 @@ expression: checks
- kind: MultiValueRepeatedKeyLiteral
location:
row: 3
column: 6
column: 5
end_location:
row: 3
column: 8
@@ -21,7 +21,7 @@ expression: checks
- kind: MultiValueRepeatedKeyLiteral
location:
row: 11
column: 7
column: 5
end_location:
row: 11
column: 11

View File

@@ -6,9 +6,9 @@ expression: checks
ForwardAnnotationSyntaxError: ///
location:
row: 9
column: 13
column: 12
end_location:
row: 9
column: 16
column: 17
fix: ~

View File

@@ -42,10 +42,10 @@ expression: checks
UndefinedName: Bar
location:
row: 58
column: 5
column: 4
end_location:
row: 58
column: 8
column: 9
fix: ~
- kind:
UndefinedName: TOMATO
@@ -60,7 +60,7 @@ expression: checks
UndefinedName: B
location:
row: 87
column: 7
column: 5
end_location:
row: 87
column: 11
@@ -69,7 +69,7 @@ expression: checks
UndefinedName: B
location:
row: 89
column: 7
column: 5
end_location:
row: 89
column: 9
@@ -78,27 +78,27 @@ expression: checks
UndefinedName: PEP593Test123
location:
row: 114
column: 10
column: 9
end_location:
row: 114
column: 23
column: 24
fix: ~
- kind:
UndefinedName: foo
location:
row: 122
column: 15
column: 14
end_location:
row: 122
column: 18
column: 19
fix: ~
- kind:
UndefinedName: bar
location:
row: 122
column: 22
column: 21
end_location:
row: 122
column: 25
column: 26
fix: ~

View File

@@ -0,0 +1,50 @@
---
source: src/linter.rs
expression: checks
---
- kind:
InvalidClassName: bad
location:
row: 1
column: 1
end_location:
row: 5
column: 1
fix: ~
- kind:
InvalidClassName: _bad
location:
row: 5
column: 1
end_location:
row: 9
column: 1
fix: ~
- kind:
InvalidClassName: bad_class
location:
row: 9
column: 1
end_location:
row: 13
column: 1
fix: ~
- kind:
InvalidClassName: Bad_Class
location:
row: 13
column: 1
end_location:
row: 17
column: 1
fix: ~
- kind:
InvalidClassName: BAD_CLASS
location:
row: 17
column: 1
end_location:
row: 21
column: 1
fix: ~

View File

@@ -0,0 +1,41 @@
---
source: src/linter.rs
expression: checks
---
- kind:
InvalidFunctionName: Bad
location:
row: 1
column: 1
end_location:
row: 5
column: 1
fix: ~
- kind:
InvalidFunctionName: _Bad
location:
row: 5
column: 1
end_location:
row: 9
column: 1
fix: ~
- kind:
InvalidFunctionName: BAD
location:
row: 9
column: 1
end_location:
row: 13
column: 1
fix: ~
- kind:
InvalidFunctionName: BAD_FUNC
location:
row: 13
column: 1
end_location:
row: 17
column: 1
fix: ~

View File

@@ -0,0 +1,23 @@
---
source: src/linter.rs
expression: checks
---
- kind:
InvalidArgumentName: A
location:
row: 1
column: 13
end_location:
row: 1
column: 14
fix: ~
- kind:
InvalidArgumentName: A
location:
row: 6
column: 25
end_location:
row: 6
column: 26
fix: ~

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: InvalidFirstArgumentNameForClassMethod
location:
row: 3
column: 26
end_location:
row: 3
column: 30
fix: ~

View File

@@ -0,0 +1,21 @@
---
source: src/linter.rs
expression: checks
---
- kind: InvalidFirstArgumentNameForMethod
location:
row: 5
column: 20
end_location:
row: 5
column: 24
fix: ~
- kind: InvalidFirstArgumentNameForMethod
location:
row: 10
column: 30
end_location:
row: 10
column: 34
fix: ~

View File

@@ -85,49 +85,49 @@ expression: checks
- kind: UsePEP604Annotation
location:
row: 32
column: 11
column: 10
end_location:
row: 32
column: 47
column: 48
fix:
content: "str | int | Union[float, bytes]"
location:
row: 32
column: 11
column: 10
end_location:
row: 32
column: 47
column: 48
applied: false
- kind: UsePEP604Annotation
location:
row: 32
column: 11
column: 10
end_location:
row: 32
column: 47
column: 48
fix:
content: float | bytes
location:
row: 32
column: 11
column: 10
end_location:
row: 32
column: 47
column: 48
applied: false
- kind: UsePEP604Annotation
location:
row: 39
column: 11
column: 10
end_location:
row: 39
column: 33
column: 34
fix:
content: str | int
location:
row: 39
column: 11
column: 10
end_location:
row: 39
column: 33
column: 34
applied: false