Compare commits
276 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10b250ee57 | ||
|
|
30b1b1e15a | ||
|
|
aafe7c0c39 | ||
|
|
f060248656 | ||
|
|
bbe0220c72 | ||
|
|
129e2b6ad3 | ||
|
|
73e744b1d0 | ||
|
|
50a3fc5a67 | ||
|
|
de499f0258 | ||
|
|
e1abe37c6a | ||
|
|
7fe5945541 | ||
|
|
806f3fd4f6 | ||
|
|
2bba643dd2 | ||
|
|
54090bd7ac | ||
|
|
c62727db42 | ||
|
|
d0e1612507 | ||
|
|
5ccd907398 | ||
|
|
346610c2e3 | ||
|
|
307fa26515 | ||
|
|
136d412edd | ||
|
|
d9edec0ac9 | ||
|
|
473675fffb | ||
|
|
95dfc61315 | ||
|
|
dd496c7b52 | ||
|
|
78aafb4b34 | ||
|
|
99c66d513a | ||
|
|
04ade6a2f3 | ||
|
|
4cf2682cda | ||
|
|
e3a7357187 | ||
|
|
b60768debb | ||
|
|
25e476639f | ||
|
|
4645788205 | ||
|
|
0b9eda8836 | ||
|
|
ad23d6acee | ||
|
|
e3d1d01a1f | ||
|
|
6dfdd21a7c | ||
|
|
f17d3b3c44 | ||
|
|
da6b913317 | ||
|
|
bd34850f98 | ||
|
|
d5b33cdb40 | ||
|
|
3fb4cf7009 | ||
|
|
6e19539e28 | ||
|
|
4eac7a07f5 | ||
|
|
5141285c8e | ||
|
|
82cc139d2d | ||
|
|
df438ba051 | ||
|
|
a70624cd47 | ||
|
|
b307afc00c | ||
|
|
3d5bc1f51f | ||
|
|
aba01745f5 | ||
|
|
1eeeffab66 | ||
|
|
9b564c9cf4 | ||
|
|
5bf8b13644 | ||
|
|
f80d5e70dd | ||
|
|
44897b2a5b | ||
|
|
d1bcc919a2 | ||
|
|
03e1397427 | ||
|
|
fdb32330a9 | ||
|
|
4e6ae33a3a | ||
|
|
295ff8eb1a | ||
|
|
2449771d2f | ||
|
|
406491a3a2 | ||
|
|
bfae262359 | ||
|
|
af894f290f | ||
|
|
c901742244 | ||
|
|
7e4faf4b69 | ||
|
|
31a0b20271 | ||
|
|
0966bf2c66 | ||
|
|
64d8e25528 | ||
|
|
b049cced04 | ||
|
|
bc335f839e | ||
|
|
4819e19ba2 | ||
|
|
622b8adb79 | ||
|
|
558d9fcbe3 | ||
|
|
83f18193c2 | ||
|
|
46e6a1b3be | ||
|
|
4d0d433af9 | ||
|
|
11f7532e72 | ||
|
|
417764d309 | ||
|
|
1e36c109c6 | ||
|
|
3960016d55 | ||
|
|
75d669fa86 | ||
|
|
20989e12ba | ||
|
|
5a1b6c32eb | ||
|
|
46bdcb9080 | ||
|
|
d16a7252af | ||
|
|
ca6551eb37 | ||
|
|
43a4f5749e | ||
|
|
6fef4db433 | ||
|
|
7470d6832f | ||
|
|
63ba0bfeef | ||
|
|
91666fcaf6 | ||
|
|
643e27221d | ||
|
|
c7349b69c1 | ||
|
|
7f84753f3c | ||
|
|
e2ec62cf33 | ||
|
|
1d5592d937 | ||
|
|
886def13bd | ||
|
|
949e4d4077 | ||
|
|
c8cb2eead2 | ||
|
|
02ae494a0e | ||
|
|
dce86e065b | ||
|
|
d77979429c | ||
|
|
a3a15d2eb2 | ||
|
|
5af95428ff | ||
|
|
6338cad4e6 | ||
|
|
485881877f | ||
|
|
b8f517c70e | ||
|
|
9f601c2abd | ||
|
|
c0ce0b0c48 | ||
|
|
e5b16973a9 | ||
|
|
de9ceb2fe1 | ||
|
|
38b19b78b7 | ||
|
|
7043e15b57 | ||
|
|
9594079235 | ||
|
|
732f208e47 | ||
|
|
32e62d9209 | ||
|
|
d9e4b0cdc1 | ||
|
|
36fcfad56a | ||
|
|
65d29d9734 | ||
|
|
1e171ce0e8 | ||
|
|
2bdc500c61 | ||
|
|
f453e429b6 | ||
|
|
73874f4788 | ||
|
|
8846dcdf6a | ||
|
|
d827e6e36a | ||
|
|
71d9b2ac5f | ||
|
|
401b53cc45 | ||
|
|
aa9c1e255c | ||
|
|
f7fc702b2c | ||
|
|
50ca0d7d0a | ||
|
|
65e0284698 | ||
|
|
e4f571ea61 | ||
|
|
4ed88dd245 | ||
|
|
09b926fd59 | ||
|
|
a4869e4974 | ||
|
|
f53c4fc221 | ||
|
|
3892a49a97 | ||
|
|
27cc7e236c | ||
|
|
fa0954fe47 | ||
|
|
a0b50d7ebc | ||
|
|
afe7a04211 | ||
|
|
14806c62ca | ||
|
|
0d0c8730fa | ||
|
|
cf6a23b83c | ||
|
|
9e0daac561 | ||
|
|
f2fd7335ce | ||
|
|
b8f878df5e | ||
|
|
9bdb922c75 | ||
|
|
edecc1bba6 | ||
|
|
8e903153f6 | ||
|
|
3937885f37 | ||
|
|
24de97d951 | ||
|
|
06e5b3e457 | ||
|
|
68a0e6dc19 | ||
|
|
9d4a4478f7 | ||
|
|
6bbf3f46c4 | ||
|
|
4ac4e8c991 | ||
|
|
0091a3ae5f | ||
|
|
17b3109a8b | ||
|
|
71520213c1 | ||
|
|
f24e7a0052 | ||
|
|
507e9f7ec3 | ||
|
|
592c53c8bf | ||
|
|
a2df89dedd | ||
|
|
b8f12d2e79 | ||
|
|
67b1d0463a | ||
|
|
d008a181ec | ||
|
|
6d612a428a | ||
|
|
c0cb73ab16 | ||
|
|
2e1eb84cbf | ||
|
|
b03a8728b5 | ||
|
|
1dd3350a30 | ||
|
|
bda34945a5 | ||
|
|
85dcaa8d3c | ||
|
|
4ac74ed0ad | ||
|
|
b7e2a4b9a9 | ||
|
|
53a7758248 | ||
|
|
2ba767957d | ||
|
|
08152787e1 | ||
|
|
5f77b420cd | ||
|
|
90f9e60517 | ||
|
|
320737f6e4 | ||
|
|
dfba1416b2 | ||
|
|
2ca3f35bd1 | ||
|
|
b1c40d5fa7 | ||
|
|
a129e27b3e | ||
|
|
ad7daa008e | ||
|
|
062d7081a0 | ||
|
|
40c1e7e005 | ||
|
|
3cbd05ddff | ||
|
|
9414090617 | ||
|
|
825777edc1 | ||
|
|
4e0807e908 | ||
|
|
546be5692a | ||
|
|
43e1f20b28 | ||
|
|
97388cefda | ||
|
|
63ce579989 | ||
|
|
5f4a62aa40 | ||
|
|
02ab52b3e2 | ||
|
|
549732b1da | ||
|
|
c4565fe0f5 | ||
|
|
81ae3bfc94 | ||
|
|
62e6feadc7 | ||
|
|
18a26e8f0b | ||
|
|
2371de3895 | ||
|
|
e3c8f61340 | ||
|
|
f6628ae100 | ||
|
|
989ed9c10b | ||
|
|
8698c06c36 | ||
|
|
dfd8a4158d | ||
|
|
c247730bf5 | ||
|
|
024472d578 | ||
|
|
7d69a153e8 | ||
|
|
4fc68e0310 | ||
|
|
6a24351202 | ||
|
|
d7f95ac6b6 | ||
|
|
11234ea555 | ||
|
|
b536159541 | ||
|
|
7c17785eac | ||
|
|
f1acd28f08 | ||
|
|
c61ff9a947 | ||
|
|
2c64cf3149 | ||
|
|
fc5f34c76f | ||
|
|
a8f4faa6e4 | ||
|
|
2ac5c830c1 | ||
|
|
994f12050d | ||
|
|
fad4e4c51d | ||
|
|
c0042a3ca4 | ||
|
|
5deb63a05f | ||
|
|
5e9ea8bda2 | ||
|
|
55d1f34bae | ||
|
|
59b518a54a | ||
|
|
74ecdc73ac | ||
|
|
1ad6be7196 | ||
|
|
b44d6c2c44 | ||
|
|
2749660b1f | ||
|
|
c1eeae90f1 | ||
|
|
27025055ee | ||
|
|
e306fe0765 | ||
|
|
5ffb9c08d5 | ||
|
|
1a8940f015 | ||
|
|
45db571935 | ||
|
|
198e5cf27f | ||
|
|
79b6472c7c | ||
|
|
f902d25dc7 | ||
|
|
826bdfeb63 | ||
|
|
a3fb0d6c20 | ||
|
|
3cf9e3b201 | ||
|
|
533b4e752b | ||
|
|
cf45d520e6 | ||
|
|
b86414dc7a | ||
|
|
8f6ab8b37a | ||
|
|
312bfd8d2b | ||
|
|
e2f46537fd | ||
|
|
97cc30768d | ||
|
|
d580f2eb90 | ||
|
|
507fecfd9a | ||
|
|
4319bd1755 | ||
|
|
6bb6cb1783 | ||
|
|
e9412c9452 | ||
|
|
94faa7f301 | ||
|
|
5041f6530c | ||
|
|
3c7716ef27 | ||
|
|
26e1f4b6df | ||
|
|
17c08523dc | ||
|
|
221f4304ad | ||
|
|
c0131e65e5 | ||
|
|
bf4722a62f | ||
|
|
994f5d452c | ||
|
|
741857cdf9 | ||
|
|
4f42f51bd2 | ||
|
|
5a3092e805 | ||
|
|
0318406535 | ||
|
|
0c99b5aac5 | ||
|
|
b442402b13 |
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
# Check http://editorconfig.org for more information
|
||||
# This is the main config file for this project:
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
|
||||
[*.{rs,py}]
|
||||
indent_size = 4
|
||||
16
.github/workflows/ci.yaml
vendored
16
.github/workflows/ci.yaml
vendored
@@ -2,16 +2,16 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
cargo_build:
|
||||
name: "cargo build"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
name: "cargo fmt"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
name: "cargo clippy"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
name: "cargo test"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -99,13 +99,13 @@ jobs:
|
||||
name: "maturin build"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: "3.10"
|
||||
- run: pip install maturin
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
|
||||
34
.github/workflows/release.yaml
vendored
34
.github/workflows/release.yaml
vendored
@@ -1,6 +1,8 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
create:
|
||||
tags:
|
||||
- v*
|
||||
@@ -14,7 +16,7 @@ env:
|
||||
PYTHON_VERSION: "3.7" # to build abi3 wheels
|
||||
|
||||
jobs:
|
||||
macos:
|
||||
macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -37,6 +39,25 @@ jobs:
|
||||
- name: Install built wheel - x86_64
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
macos-universal:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
- name: Build wheels - universal2
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
@@ -127,7 +148,7 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --release --out dist
|
||||
args: --no-default-features --release --out dist
|
||||
maturin-version: "v0.13.0"
|
||||
- uses: uraimo/run-on-arch-action@v2.0.5
|
||||
if: matrix.target != 'ppc64'
|
||||
@@ -227,9 +248,9 @@ jobs:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
target: [x86_64, aarch64]
|
||||
python-version:
|
||||
- '3.7'
|
||||
- '3.8'
|
||||
- '3.9'
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
target: aarch64
|
||||
@@ -259,7 +280,8 @@ jobs:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- macos
|
||||
- macos-universal
|
||||
- macos-x86_64
|
||||
- windows
|
||||
- linux
|
||||
- linux-cross
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
# Local cache
|
||||
.cache
|
||||
.ruff_cache
|
||||
resources/test/cpython
|
||||
|
||||
###
|
||||
|
||||
10
.pre-commit-config.yaml
Normal file
10
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.40
|
||||
hooks:
|
||||
- id: lint
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.10.1
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
charlie.r.marsh@gmail.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
111
CONTRIBUTING.md
Normal file
111
CONTRIBUTING.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Contributing to ruff
|
||||
|
||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to ruff.
|
||||
|
||||
## The basics
|
||||
|
||||
ruff welcomes contributions in the form of Pull Requests. For small changes (e.g., bug fixes), feel
|
||||
free to submit a PR. For larger changes (e.g., new lint rules, new functionality, new configuration
|
||||
options), consider submitting an [Issue](https://github.com/charliermarsh/ruff/issues) outlining
|
||||
your proposed change.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
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.
|
||||
|
||||
You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
|
||||
|
||||
```shell
|
||||
cargo install cargo-insta
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run ruff locally with:
|
||||
|
||||
```shell
|
||||
cargo run resources/test/fixtures --no-cache
|
||||
```
|
||||
|
||||
Prior to opening a pull request, ensure that your code has been auto-formatted, and that it passes
|
||||
both the lint and test validation checks:
|
||||
|
||||
```shell
|
||||
cargo fmt # Auto-formatting...
|
||||
cargo clippy # Linting...
|
||||
cargo test # Testing...
|
||||
```
|
||||
|
||||
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
|
||||
will save you time and expedite the merge process.
|
||||
|
||||
Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration
|
||||
prior to merging.
|
||||
|
||||
### Example: Adding a new lint rule
|
||||
|
||||
There are three phases to adding a new lint rule:
|
||||
|
||||
1. Define the rule in `src/checks.rs`.
|
||||
2. Define the _logic_ for triggering the rule in `src/check_ast.rs` (for AST-based checks)
|
||||
or `src/check_lines.rs` (for text-based checks).
|
||||
3. Add a test fixture.
|
||||
|
||||
To define the rule, open up `src/checks.rs`. You'll need to define both a `CheckCode` and
|
||||
`CheckKind`. As an example, you can grep for `E402` and `ModuleImportNotAtTopOfFile`, and follow the
|
||||
pattern implemented therein.
|
||||
|
||||
To trigger the rule, you'll likely want to augment the logic in `src/check_ast.rs`, which defines
|
||||
the Python AST visitor, responsible for iterating over the abstract syntax tree and collecting
|
||||
lint-rule violations as it goes. Grep for the `Check::new` invocations to understand how other,
|
||||
similar rules are implemented.
|
||||
|
||||
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
|
||||
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
|
||||
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run ruff locally
|
||||
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisfied with the output,
|
||||
codify the behavior as a snapshot test by adding a new function to the `mod tests` section of
|
||||
`src/linter.rs`, like so:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn e402() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E402.py"),
|
||||
&settings::Settings::for_rule(CheckCode::E402),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Then, run `cargo test`. Your test will fail, but you'll be prompted to follow-up with
|
||||
`cargo insta review`. Accept the generated snapshot, then commit the snapshot file alongside the
|
||||
rest of your changes.
|
||||
|
||||
### Example: Adding a new configuration option
|
||||
|
||||
ruff's user-facing settings live in two places: first, the command-line options defined with
|
||||
[clap](https://docs.rs/clap/latest/clap/) via the `Cli` struct in `src/main.rs`; and second, the
|
||||
`Config` struct defined `src/pyproject.rs`, which is responsible for extracting user-defined
|
||||
settings from a `pyproject.toml` file.
|
||||
|
||||
Ultimately, these two sources of configuration are merged into the `Settings` struct defined
|
||||
in `src/settings.rs`, which is then threaded through the codebase.
|
||||
|
||||
To add a new configuration option, you'll likely want to _both_ add a CLI option to `src/main.rs`
|
||||
_and_ a `pyproject.toml` parameter to `src/pyproject.rs`. If you want to pattern-match against an
|
||||
existing example, grep for `dummy_variable_rgx`, which defines a regular expression to match against
|
||||
acceptable unused variables (e.g., `_`).
|
||||
|
||||
## Release process
|
||||
|
||||
As of now, ruff has an ad hoc release process: releases are cut with high frequency via GitHub
|
||||
Actions, which automatically generates the appropriate wheels across architectures and publishes
|
||||
them to [PyPI](https://pypi.org/project/ruff/).
|
||||
|
||||
ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
|
||||
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).
|
||||
784
Cargo.lock
generated
784
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
31
Cargo.toml
31
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.23"
|
||||
version = "0.0.64"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -11,26 +11,49 @@ anyhow = { version = "1.0.60" }
|
||||
bincode = { version = "1.3.3" }
|
||||
cacache = { version = "10.0.1" }
|
||||
chrono = { version = "0.4.21" }
|
||||
clap = { version = "3.2.16", features = ["derive"] }
|
||||
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" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = { version = "0.3.0"}
|
||||
glob = { version = "0.3.0" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "32a044c127668df44582f85699358e67803b0d73" }
|
||||
log = { version = "0.4.17" }
|
||||
notify = { version = "4.0.17" }
|
||||
once_cell = { version = "1.13.1" }
|
||||
path-absolutize = { version = "3.0.13", features = ["once_cell_cache"] }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "1613f6c6990011a4bc559e79aaf28d715f9f729b" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f" }
|
||||
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f" }
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
toml = { version = "0.5.9" }
|
||||
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
|
||||
walkdir = { version = "2.3.2" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = "0.24.3"
|
||||
num-bigint = "0.4.3"
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { version = "1.19.1", features = ["yaml"] }
|
||||
|
||||
[features]
|
||||
default = ["update-informer"]
|
||||
update-informer = ["dep:update-informer"]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.insta]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.similar]
|
||||
opt-level = 3
|
||||
|
||||
410
README.md
410
README.md
@@ -1,7 +1,9 @@
|
||||
# ruff
|
||||
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://github.com/charliermarsh/ruff/actions)
|
||||
[](https://badge.fury.io/py/ruff)
|
||||
|
||||
An extremely fast Python linter, written in Rust.
|
||||
|
||||
@@ -17,11 +19,10 @@ An extremely fast Python linter, written in Rust.
|
||||
- 🐍 Installable via `pip`
|
||||
- 🤝 Python 3.10 compatibility
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache semantics
|
||||
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` semantics
|
||||
|
||||
_ruff is a proof-of-concept and not yet intended for production use. It supports only a small subset
|
||||
of the Flake8 rules, and may crash on your codebase._
|
||||
- 📦 [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
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
@@ -51,6 +52,16 @@ You can run ruff in `--watch` mode to automatically re-run on-change:
|
||||
ruff path/to/code/ --watch
|
||||
```
|
||||
|
||||
ruff also works with [pre-commit](https://pre-commit.com):
|
||||
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.48
|
||||
hooks:
|
||||
- id: lint
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
ruff is configurable both via `pyproject.toml` and the command line.
|
||||
@@ -69,31 +80,261 @@ select = [
|
||||
Alternatively, on the command-line:
|
||||
|
||||
```shell
|
||||
ruff path/to/code/ --select F401 F403
|
||||
ruff path/to/code/ --select F401 --select F403
|
||||
```
|
||||
|
||||
See `ruff --help` for more:
|
||||
|
||||
```shell
|
||||
ruff
|
||||
An extremely fast Python linter.
|
||||
ruff: An extremely fast Python linter.
|
||||
|
||||
USAGE:
|
||||
ruff [OPTIONS] <FILES>...
|
||||
Usage: ruff [OPTIONS] <FILES>...
|
||||
|
||||
ARGS:
|
||||
<FILES>...
|
||||
Arguments:
|
||||
<FILES>...
|
||||
|
||||
OPTIONS:
|
||||
-e, --exit-zero Exit with status code "0", even upon detecting errors
|
||||
-h, --help Print help information
|
||||
--ignore <IGNORE>... Comma-separated list of error codes to ignore
|
||||
-n, --no-cache Disable cache reads
|
||||
-q, --quiet Disable all logging (but still exit with status code "1" upon
|
||||
detecting errors)
|
||||
--select <SELECT>... Comma-separated list of error codes to enable
|
||||
-v, --verbose Enable verbose logging
|
||||
-w, --watch Run in watch mode by re-running whenever files change
|
||||
Options:
|
||||
-v, --verbose
|
||||
Enable verbose logging
|
||||
-q, --quiet
|
||||
Disable all logging (but still exit with status code "1" upon detecting errors)
|
||||
-e, --exit-zero
|
||||
Exit with status code "0", even upon detecting errors
|
||||
-w, --watch
|
||||
Run in watch mode by re-running whenever files change
|
||||
-f, --fix
|
||||
Attempt to automatically fix lint errors
|
||||
-n, --no-cache
|
||||
Disable cache reads
|
||||
--select <SELECT>
|
||||
List of error codes to enable
|
||||
--extend-select <EXTEND_SELECT>
|
||||
Like --select, but adds additional error codes on top of the selected ones
|
||||
--ignore <IGNORE>
|
||||
List of error codes to ignore
|
||||
--extend-ignore <EXTEND_IGNORE>
|
||||
Like --ignore, but adds additional error codes on top of the ignored ones
|
||||
--exclude <EXCLUDE>
|
||||
List of paths, used to exclude files and/or directories from checks
|
||||
--extend-exclude <EXTEND_EXCLUDE>
|
||||
Like --exclude, but adds additional files and directories on top of the excluded ones
|
||||
--per-file-ignores <PER_FILE_IGNORES>
|
||||
List of mappings from file pattern to code to exclude
|
||||
--format <FORMAT>
|
||||
Output serialization format for error messages [default: text] [possible values: text, json]
|
||||
--show-files
|
||||
See the files ruff will be run against with the current settings
|
||||
--show-settings
|
||||
See ruff's settings
|
||||
--add-noqa
|
||||
Enable automatic additions of noqa directives to failing lines
|
||||
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
|
||||
Regular expression matching the name of dummy variables
|
||||
--target-version <TARGET_VERSION>
|
||||
The minimum Python version that should be supported
|
||||
-h, --help
|
||||
Print help information
|
||||
-V, --version
|
||||
Print version information
|
||||
```
|
||||
|
||||
### Excluding files
|
||||
|
||||
Exclusions are based on globs, and can be either:
|
||||
|
||||
- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the
|
||||
tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching
|
||||
`foo_*.py` ).
|
||||
- Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py`
|
||||
(to exclude any Python files in `directory`). Note that these paths are relative to the
|
||||
project root (e.g., the directory containing your `pyproject.toml`).
|
||||
|
||||
### Ignoring errors
|
||||
|
||||
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 individual error, add `# noqa: {code}` to the end of the line, like so:
|
||||
|
||||
```python
|
||||
# Ignore F841.
|
||||
x = 1 # noqa: F841
|
||||
|
||||
# Ignore E741 and F841.
|
||||
i = 1 # noqa: E741, F841
|
||||
|
||||
# Ignore _all_ errors.
|
||||
x = 1 # noqa
|
||||
```
|
||||
|
||||
Note that, for multi-line strings, the `noqa` directive should come at the end of the string, and
|
||||
will apply to the entire body, like so:
|
||||
|
||||
```python
|
||||
"""Lorem ipsum dolor sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501
|
||||
```
|
||||
|
||||
ruff supports several (experimental) workflows to aid in `noqa` management.
|
||||
|
||||
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.
|
||||
**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
|
||||
add `noqa` directives to all failing lines, with the appropriate error codes.**
|
||||
|
||||
### Compatibility with Black
|
||||
|
||||
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.
|
||||
|
||||
### Parity with Flake8
|
||||
|
||||
ruff's goal is to achieve feature parity with Flake8 when used (1) without plugins, (2) alongside
|
||||
Black, and (3) on Python 3 code.
|
||||
|
||||
**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.)
|
||||
|
||||
ruff also implements some of the most popular Flake8 plugins natively, including:
|
||||
|
||||
- [`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/) (partial)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (partial)
|
||||
|
||||
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.
|
||||
|
||||
## Rules
|
||||
|
||||
The ✅ emoji indicates a rule is enabled by default.
|
||||
|
||||
The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option.
|
||||
|
||||
| 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 | | |
|
||||
| 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 | | |
|
||||
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within <reversed/set/sorted>() | | |
|
||||
| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
|
||||
| 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 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
|
||||
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | | 🛠 |
|
||||
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | | 🛠 |
|
||||
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
|
||||
|
||||
## Integrations
|
||||
|
||||
### PyCharm
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
ruff should then appear as a runnable action:
|
||||
|
||||

|
||||
|
||||
### GitHub Actions
|
||||
|
||||
GitHub Actions has everything you need to run ruff out-of-the-box:
|
||||
|
||||
```yaml
|
||||
name: CI
|
||||
on: push
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ruff
|
||||
- name: Run ruff
|
||||
run: ruff .
|
||||
```
|
||||
|
||||
## Development
|
||||
@@ -104,7 +345,7 @@ for development.
|
||||
Assuming you have `cargo` installed, you can run:
|
||||
|
||||
```shell
|
||||
cargo run resources/test/src
|
||||
cargo run resources/test/fixtures
|
||||
cargo fmt
|
||||
cargo clippy
|
||||
cargo test
|
||||
@@ -128,49 +369,25 @@ git clone --branch 3.10 https://github.com/python/cpython.git resources/test/cpy
|
||||
Add this `pyproject.toml` to the CPython directory:
|
||||
|
||||
```toml
|
||||
[tool.linter]
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
exclude = [
|
||||
"Lib/ctypes/test/test_numbers.py",
|
||||
"Lib/dataclasses.py",
|
||||
extend-exclude = [
|
||||
"Lib/lib2to3/tests/data/bom.py",
|
||||
"Lib/lib2to3/tests/data/crlf.py",
|
||||
"Lib/lib2to3/tests/data/different_encoding.py",
|
||||
"Lib/lib2to3/tests/data/false_encoding.py",
|
||||
"Lib/lib2to3/tests/data/py2_test_grammar.py",
|
||||
"Lib/sqlite3/test/factory.py",
|
||||
"Lib/sqlite3/test/hooks.py",
|
||||
"Lib/sqlite3/test/regression.py",
|
||||
"Lib/sqlite3/test/transactions.py",
|
||||
"Lib/sqlite3/test/types.py",
|
||||
"Lib/test/bad_coding2.py",
|
||||
"Lib/test/badsyntax_3131.py",
|
||||
"Lib/test/badsyntax_pep3120.py",
|
||||
"Lib/test/encoded_modules/module_iso_8859_1.py",
|
||||
"Lib/test/encoded_modules/module_koi8_r.py",
|
||||
"Lib/test/sortperf.py",
|
||||
"Lib/test/test_email/torture_test.py",
|
||||
"Lib/test/test_fstring.py",
|
||||
"Lib/test/test_genericpath.py",
|
||||
"Lib/test/test_getopt.py",
|
||||
"Lib/test/test_grammar.py",
|
||||
"Lib/test/test_htmlparser.py",
|
||||
"Lib/test/test_importlib/stubs.py",
|
||||
"Lib/test/test_importlib/test_files.py",
|
||||
"Lib/test/test_importlib/test_metadata_api.py",
|
||||
"Lib/test/test_importlib/test_open.py",
|
||||
"Lib/test/test_importlib/test_util.py",
|
||||
"Lib/test/test_named_expressions.py",
|
||||
"Lib/test/test_patma.py",
|
||||
"Lib/test/test_peg_generator/__main__.py",
|
||||
"Lib/test/test_pipes.py",
|
||||
"Lib/test/test_source_encoding.py",
|
||||
"Lib/test/test_weakref.py",
|
||||
"Lib/test/test_webbrowser.py",
|
||||
"Lib/tkinter/__main__.py",
|
||||
"Lib/tkinter/test/test_tkinter/test_variables.py",
|
||||
"Modules/_decimal/libmpdec/literature/fnt.py",
|
||||
"Modules/_decimal/tests/deccheck.py",
|
||||
"Tools/c-analyzer/c_parser/parser/_delim.py",
|
||||
"Tools/i18n/pygettext.py",
|
||||
"Tools/test2to3/maintest.py",
|
||||
@@ -185,17 +402,21 @@ Next, to benchmark the release build:
|
||||
```shell
|
||||
cargo build --release
|
||||
|
||||
hyperfine --ignore-failure --warmup 1 \
|
||||
hyperfine --ignore-failure --warmup 10 --runs 100 \
|
||||
"./target/release/ruff ./resources/test/cpython/ --no-cache" \
|
||||
"./target/release/ruff ./resources/test/cpython/"
|
||||
|
||||
Benchmark 1: ./target/release/ruff ./resources/test/cpython/ --no-cache
|
||||
Time (mean ± σ): 353.6 ms ± 7.6 ms [User: 2868.8 ms, System: 171.5 ms]
|
||||
Range (min … max): 344.4 ms … 367.3 ms 10 runs
|
||||
Time (mean ± σ): 297.4 ms ± 4.9 ms [User: 2460.0 ms, System: 67.2 ms]
|
||||
Range (min … max): 287.7 ms … 312.1 ms 100 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 2: ./target/release/ruff ./resources/test/cpython/
|
||||
Time (mean ± σ): 59.6 ms ± 2.5 ms [User: 36.4 ms, System: 345.6 ms]
|
||||
Range (min … max): 55.9 ms … 67.0 ms 48 runs
|
||||
Time (mean ± σ): 79.6 ms ± 7.3 ms [User: 59.7 ms, System: 356.1 ms]
|
||||
Range (min … max): 62.4 ms … 111.2 ms 100 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
```
|
||||
|
||||
To benchmark against the ecosystem's existing tools:
|
||||
@@ -207,81 +428,76 @@ hyperfine --ignore-failure --warmup 5 \
|
||||
"pyflakes resources/test/cpython" \
|
||||
"autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython" \
|
||||
"pycodestyle resources/test/cpython" \
|
||||
"pycodestyle --select E501 resources/test/cpython" \
|
||||
"flake8 resources/test/cpython" \
|
||||
"flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython" \
|
||||
"python -m scripts.run_flake8 resources/test/cpython" \
|
||||
"python -m scripts.run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501"
|
||||
"python -m scripts.run_flake8 resources/test/cpython"
|
||||
```
|
||||
|
||||
In order, these evaluate:
|
||||
|
||||
- ruff
|
||||
- Pylint
|
||||
- PyFlakes
|
||||
- autoflake
|
||||
- pycodestyle
|
||||
- pycodestyle, limited to the checks supported by ruff
|
||||
- Flake8
|
||||
- Flake8, limited to the checks supported by ruff
|
||||
- Flake8, with a hack to enable multiprocessing on macOS
|
||||
- Flake8, with a hack to enable multiprocessing on macOS, limited to the checks supported by ruff
|
||||
|
||||
(You can `poetry install` from `./scripts` to create a working environment for the above.)
|
||||
|
||||
```shell
|
||||
Benchmark 1: ./target/release/ruff ./resources/test/cpython/ --no-cache
|
||||
Time (mean ± σ): 469.3 ms ± 16.3 ms [User: 2663.0 ms, System: 972.5 ms]
|
||||
Range (min … max): 445.2 ms … 494.8 ms 10 runs
|
||||
Time (mean ± σ): 297.9 ms ± 7.0 ms [User: 2436.6 ms, System: 65.9 ms]
|
||||
Range (min … max): 289.9 ms … 314.6 ms 10 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 2: pylint --recursive=y resources/test/cpython/
|
||||
Time (mean ± σ): 27.211 s ± 0.097 s [User: 26.405 s, System: 0.799 s]
|
||||
Range (min … max): 27.056 s … 27.349 s 10 runs
|
||||
Time (mean ± σ): 37.634 s ± 0.225 s [User: 36.728 s, System: 0.853 s]
|
||||
Range (min … max): 37.201 s … 38.106 s 10 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 3: pyflakes resources/test/cpython
|
||||
Time (mean ± σ): 27.309 s ± 0.033 s [User: 27.137 s, System: 0.169 s]
|
||||
Range (min … max): 27.267 s … 27.372 s 10 runs
|
||||
Time (mean ± σ): 40.950 s ± 0.449 s [User: 40.688 s, System: 0.229 s]
|
||||
Range (min … max): 40.348 s … 41.671 s 10 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 4: autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython
|
||||
Time (mean ± σ): 8.027 s ± 0.024 s [User: 74.255 s, System: 0.953 s]
|
||||
Range (min … max): 7.969 s … 8.052 s 10 runs
|
||||
Time (mean ± σ): 11.562 s ± 0.160 s [User: 107.022 s, System: 1.143 s]
|
||||
Range (min … max): 11.417 s … 11.917 s 10 runs
|
||||
|
||||
Benchmark 5: pycodestyle resources/test/cpython
|
||||
Time (mean ± σ): 41.666 s ± 0.266 s [User: 41.531 s, System: 0.132 s]
|
||||
Range (min … max): 41.295 s … 41.980 s 10 runs
|
||||
Time (mean ± σ): 67.428 s ± 0.985 s [User: 67.199 s, System: 0.203 s]
|
||||
Range (min … max): 65.313 s … 68.496 s 10 runs
|
||||
|
||||
Benchmark 6: pycodestyle --select E501 resources/test/cpython
|
||||
Time (mean ± σ): 14.547 s ± 0.077 s [User: 14.466 s, System: 0.079 s]
|
||||
Range (min … max): 14.429 s … 14.695 s 10 runs
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 7: flake8 resources/test/cpython
|
||||
Time (mean ± σ): 75.700 s ± 0.152 s [User: 75.254 s, System: 0.440 s]
|
||||
Range (min … max): 75.513 s … 76.014 s 10 runs
|
||||
Benchmark 6: flake8 resources/test/cpython
|
||||
Time (mean ± σ): 116.099 s ± 1.178 s [User: 115.217 s, System: 0.845 s]
|
||||
Range (min … max): 114.180 s … 117.724 s 10 runs
|
||||
|
||||
Benchmark 8: flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython
|
||||
Time (mean ± σ): 75.122 s ± 0.532 s [User: 74.677 s, System: 0.440 s]
|
||||
Range (min … max): 74.130 s … 75.606 s 10 runs
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 9: python -m scripts.run_flake8 resources/test/cpython
|
||||
Time (mean ± σ): 12.794 s ± 0.147 s [User: 90.792 s, System: 0.738 s]
|
||||
Range (min … max): 12.606 s … 13.030 s 10 runs
|
||||
|
||||
Benchmark 10: python -m scripts.run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501
|
||||
Time (mean ± σ): 12.487 s ± 0.118 s [User: 90.052 s, System: 0.714 s]
|
||||
Range (min … max): 12.265 s … 12.665 s 10 runs
|
||||
Benchmark 7: python -m scripts.run_flake8 resources/test/cpython
|
||||
Time (mean ± σ): 20.477 s ± 0.349 s [User: 142.372 s, System: 1.504 s]
|
||||
Range (min … max): 20.107 s … 21.183 s 10 runs
|
||||
|
||||
Summary
|
||||
'./target/release/ruff ./resources/test/cpython/ --no-cache' ran
|
||||
17.10 ± 0.60 times faster than 'autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython'
|
||||
26.60 ± 0.96 times faster than 'python -m scripts.run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501'
|
||||
27.26 ± 1.00 times faster than 'python -m scripts.run_flake8 resources/test/cpython'
|
||||
30.99 ± 1.09 times faster than 'pycodestyle --select E501 resources/test/cpython'
|
||||
57.98 ± 2.03 times faster than 'pylint --recursive=y resources/test/cpython/'
|
||||
58.19 ± 2.02 times faster than 'pyflakes resources/test/cpython'
|
||||
88.77 ± 3.14 times faster than 'pycodestyle resources/test/cpython'
|
||||
160.06 ± 5.68 times faster than 'flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython'
|
||||
161.29 ± 5.61 times faster than 'flake8 resources/test/cpython'
|
||||
38.81 ± 1.05 times faster than 'autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython'
|
||||
68.74 ± 1.99 times faster than 'python -m scripts.run_flake8 resources/test/cpython'
|
||||
126.33 ± 3.05 times faster than 'pylint --recursive=y resources/test/cpython/'
|
||||
137.46 ± 3.55 times faster than 'pyflakes resources/test/cpython'
|
||||
226.35 ± 6.23 times faster than 'pycodestyle resources/test/cpython'
|
||||
389.73 ± 9.92 times faster than 'flake8 resources/test/cpython'
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome and hugely appreciated. To get started, check out the
|
||||
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).
|
||||
|
||||
26
examples/generate_rules_table.rs
Normal file
26
examples/generate_rules_table.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
/// Generate a Markdown-compatible table of supported lint rules.
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff::checks::{CheckCode, DEFAULT_CHECK_CODES};
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
26
examples/generate_source_code.rs
Normal file
26
examples/generate_source_code.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use ruff::code_gen::SourceGenerator;
|
||||
use ruff::fs;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let contents = fs::read_file(&cli.file)?;
|
||||
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
|
||||
let mut generator = SourceGenerator::new();
|
||||
generator.unparse_suite(&python_ast)?;
|
||||
println!("{}", generator.generate()?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
25
examples/print_ast.rs
Normal file
25
examples/print_ast.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
/// Print the AST for a given Python file.
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use ruff::fs;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let contents = fs::read_file(&cli.file)?;
|
||||
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
|
||||
|
||||
println!("{:#?}", python_ast);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
25
examples/print_tokens.rs
Normal file
25
examples/print_tokens.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
/// Print the token stream for a given Python file.
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use rustpython_parser::lexer;
|
||||
|
||||
use ruff::fs;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let contents = fs::read_file(&cli.file)?;
|
||||
for (_, tok, _) in lexer::make_tokenizer(&contents).flatten() {
|
||||
println!("{:#?}", tok);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -8,7 +8,6 @@ classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
@@ -19,10 +18,12 @@ classifiers = [
|
||||
]
|
||||
author = "Charlie Marsh"
|
||||
author_email = "charlie.r.marsh@gmail.com"
|
||||
url = "https://github.com/charliermarsh/ruff"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
requires-python = ">=3.7"
|
||||
|
||||
[project.urls]
|
||||
repository = "https://github.com/charliermarsh/ruff"
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=0.13,<0.14"]
|
||||
build-backend = "maturin"
|
||||
|
||||
27
resources/test/fixtures/A001.py
vendored
Normal file
27
resources/test/fixtures/A001.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import some as sum
|
||||
from some import other as int
|
||||
|
||||
print = 1
|
||||
copyright: 'annotation' = 2
|
||||
(complex := 3)
|
||||
float = object = 4
|
||||
min, max = 5, 6
|
||||
|
||||
def bytes():
|
||||
pass
|
||||
|
||||
class slice:
|
||||
pass
|
||||
|
||||
try:
|
||||
...
|
||||
except ImportError as ValueError:
|
||||
...
|
||||
|
||||
for memoryview, *bytearray in []:
|
||||
pass
|
||||
|
||||
with open('file') as str, open('file2') as (all, any):
|
||||
pass
|
||||
|
||||
[0 for sum in ()]
|
||||
9
resources/test/fixtures/A002.py
vendored
Normal file
9
resources/test/fixtures/A002.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
def func1(str, /, type, *complex, Exception, **getattr):
|
||||
pass
|
||||
|
||||
|
||||
async def func2(bytes):
|
||||
pass
|
||||
|
||||
|
||||
map([], lambda float: ...)
|
||||
8
resources/test/fixtures/A003.py
vendored
Normal file
8
resources/test/fixtures/A003.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
class MyClass:
|
||||
ImportError = 4
|
||||
|
||||
def __init__(self):
|
||||
self.float = 5 # is fine
|
||||
|
||||
def str(self):
|
||||
pass
|
||||
1
resources/test/fixtures/C400.py
vendored
Normal file
1
resources/test/fixtures/C400.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
x = list(x for x in range(3))
|
||||
1
resources/test/fixtures/C401.py
vendored
Normal file
1
resources/test/fixtures/C401.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
x = set(x for x in range(3))
|
||||
1
resources/test/fixtures/C402.py
vendored
Normal file
1
resources/test/fixtures/C402.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
d = dict((x, x) for x in range(3))
|
||||
1
resources/test/fixtures/C403.py
vendored
Normal file
1
resources/test/fixtures/C403.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
s = set([x for x in range(3)])
|
||||
1
resources/test/fixtures/C404.py
vendored
Normal file
1
resources/test/fixtures/C404.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
d = dict([(i, i) for i in range(3)])
|
||||
5
resources/test/fixtures/C405.py
vendored
Normal file
5
resources/test/fixtures/C405.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
s1 = set([1, 2])
|
||||
s2 = set((1, 2))
|
||||
s3 = set([])
|
||||
s4 = set(())
|
||||
s5 = set()
|
||||
5
resources/test/fixtures/C406.py
vendored
Normal file
5
resources/test/fixtures/C406.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
d1 = dict([(1, 2)])
|
||||
d2 = dict(((1, 2),))
|
||||
d3 = dict([])
|
||||
d4 = dict(())
|
||||
d5 = dict()
|
||||
5
resources/test/fixtures/C408.py
vendored
Normal file
5
resources/test/fixtures/C408.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
t = tuple()
|
||||
l = list()
|
||||
d1 = dict()
|
||||
d2 = dict(a=1)
|
||||
d3 = dict(**d2)
|
||||
9
resources/test/fixtures/C415.py
vendored
Normal file
9
resources/test/fixtures/C415.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
lst = [2, 1, 3]
|
||||
a = set(lst[::-1])
|
||||
b = reversed(lst[::-1])
|
||||
c = sorted(lst[::-1])
|
||||
d = sorted(lst[::-1], reverse=True)
|
||||
e = set(lst[2:-1])
|
||||
f = set(lst[:1:-1])
|
||||
g = set(lst[::1])
|
||||
h = set(lst[::-2])
|
||||
32
resources/test/fixtures/E402.py
vendored
Normal file
32
resources/test/fixtures/E402.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
"""Top-level docstring."""
|
||||
|
||||
__all__ = ["y"]
|
||||
__version__: str = "0.1.0"
|
||||
|
||||
import a
|
||||
|
||||
try:
|
||||
import b
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
import c
|
||||
|
||||
if x > 0:
|
||||
import d
|
||||
else:
|
||||
import e
|
||||
|
||||
y = x + 1
|
||||
|
||||
import f
|
||||
|
||||
|
||||
def foo() -> None:
|
||||
import e
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import g
|
||||
16
resources/test/fixtures/E501.py
vendored
Normal file
16
resources/test/fixtures/E501.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
"""
|
||||
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501
|
||||
|
||||
_ = "---------------------------------------------------------------------------AAAAAAA"
|
||||
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜亜"
|
||||
50
resources/test/fixtures/E711.py
vendored
Normal file
50
resources/test/fixtures/E711.py
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
#: E711
|
||||
if res == None:
|
||||
pass
|
||||
#: E711
|
||||
if res != None:
|
||||
pass
|
||||
#: E711
|
||||
if None == res:
|
||||
pass
|
||||
#: E711
|
||||
if None != res:
|
||||
pass
|
||||
#: E711
|
||||
if res[1] == None:
|
||||
pass
|
||||
#: E711
|
||||
if res[1] != None:
|
||||
pass
|
||||
#: E711
|
||||
if None != res[1]:
|
||||
pass
|
||||
#: E711
|
||||
if None == res[1]:
|
||||
pass
|
||||
|
||||
#: Okay
|
||||
if x not in y:
|
||||
pass
|
||||
|
||||
if not (X in Y or X is Z):
|
||||
pass
|
||||
|
||||
if not (X in Y):
|
||||
pass
|
||||
|
||||
if x is not y:
|
||||
pass
|
||||
|
||||
if X is not Y is not Z:
|
||||
pass
|
||||
|
||||
if TrueElement.get_element(True) == TrueElement.get_element(False):
|
||||
pass
|
||||
|
||||
if (True) == TrueElement or x == TrueElement:
|
||||
pass
|
||||
|
||||
assert (not foo) in bar
|
||||
assert {"x": not foo} in bar
|
||||
assert [42, not foo] in bar
|
||||
46
resources/test/fixtures/E712.py
vendored
Normal file
46
resources/test/fixtures/E712.py
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
#: E712
|
||||
if res == True:
|
||||
pass
|
||||
#: E712
|
||||
if res != False:
|
||||
pass
|
||||
#: E712
|
||||
if True != res:
|
||||
pass
|
||||
#: E712
|
||||
if False == res:
|
||||
pass
|
||||
#: E712
|
||||
if res[1] == True:
|
||||
pass
|
||||
#: E712
|
||||
if res[1] != False:
|
||||
pass
|
||||
#: E712
|
||||
var = 1 if cond == True else -1 if cond == False else cond
|
||||
#: E712
|
||||
if (True) == TrueElement or x == TrueElement:
|
||||
pass
|
||||
|
||||
#: Okay
|
||||
if x not in y:
|
||||
pass
|
||||
|
||||
if not (X in Y or X is Z):
|
||||
pass
|
||||
|
||||
if not (X in Y):
|
||||
pass
|
||||
|
||||
if x is not y:
|
||||
pass
|
||||
|
||||
if X is not Y is not Z:
|
||||
pass
|
||||
|
||||
if TrueElement.get_element(True) == TrueElement.get_element(False):
|
||||
pass
|
||||
|
||||
assert (not foo) in bar
|
||||
assert {"x": not foo} in bar
|
||||
assert [42, not foo] in bar
|
||||
38
resources/test/fixtures/E713.py
vendored
Normal file
38
resources/test/fixtures/E713.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
#: E713
|
||||
if not X in Y:
|
||||
pass
|
||||
#: E713
|
||||
if not X.B in Y:
|
||||
pass
|
||||
#: E713
|
||||
if not X in Y and Z == "zero":
|
||||
pass
|
||||
#: E713
|
||||
if X == "zero" or not Y in Z:
|
||||
pass
|
||||
#: E713
|
||||
if not (X in Y):
|
||||
pass
|
||||
|
||||
#: Okay
|
||||
if x not in y:
|
||||
pass
|
||||
|
||||
if not (X in Y or X is Z):
|
||||
pass
|
||||
|
||||
if x is not y:
|
||||
pass
|
||||
|
||||
if X is not Y is not Z:
|
||||
pass
|
||||
|
||||
if TrueElement.get_element(True) == TrueElement.get_element(False):
|
||||
pass
|
||||
|
||||
if (True) == TrueElement or x == TrueElement:
|
||||
pass
|
||||
|
||||
assert (not foo) in bar
|
||||
assert {"x": not foo} in bar
|
||||
assert [42, not foo] in bar
|
||||
38
resources/test/fixtures/E714.py
vendored
Normal file
38
resources/test/fixtures/E714.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
#: E714
|
||||
if not X is Y:
|
||||
pass
|
||||
#: E714
|
||||
if not X.B is Y:
|
||||
pass
|
||||
#: E714
|
||||
if not X is Y is not Z:
|
||||
pass
|
||||
|
||||
#: Okay
|
||||
if not X is not Y:
|
||||
pass
|
||||
|
||||
if x not in y:
|
||||
pass
|
||||
|
||||
if not (X in Y or X is Z):
|
||||
pass
|
||||
|
||||
if not (X in Y):
|
||||
pass
|
||||
|
||||
if x is not y:
|
||||
pass
|
||||
|
||||
if X is not Y is not Z:
|
||||
pass
|
||||
|
||||
if TrueElement.get_element(True) == TrueElement.get_element(False):
|
||||
pass
|
||||
|
||||
if (True) == TrueElement or x == TrueElement:
|
||||
pass
|
||||
|
||||
assert (not foo) in bar
|
||||
assert {"x": not foo} in bar
|
||||
assert [42, not foo] in bar
|
||||
54
resources/test/fixtures/E721.py
vendored
Normal file
54
resources/test/fixtures/E721.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
#: E721
|
||||
if type(res) == type(42):
|
||||
pass
|
||||
#: E721
|
||||
if type(res) != type(""):
|
||||
pass
|
||||
#: E721
|
||||
import types
|
||||
|
||||
if res == types.IntType:
|
||||
pass
|
||||
#: E721
|
||||
import types
|
||||
|
||||
if type(res) is not types.ListType:
|
||||
pass
|
||||
#: E721
|
||||
assert type(res) == type(False) or type(res) == type(None)
|
||||
#: E721
|
||||
assert type(res) == type([])
|
||||
#: E721
|
||||
assert type(res) == type(())
|
||||
#: E721
|
||||
assert type(res) == type((0,))
|
||||
#: E721
|
||||
assert type(res) == type((0))
|
||||
#: E721
|
||||
assert type(res) != type((1,))
|
||||
#: E721
|
||||
assert type(res) is type((1,))
|
||||
#: E721
|
||||
assert type(res) is not type((1,))
|
||||
#: E211 E721
|
||||
assert type(res) == type(
|
||||
[
|
||||
2,
|
||||
]
|
||||
)
|
||||
#: E201 E201 E202 E721
|
||||
assert type(res) == type(())
|
||||
#: E201 E202 E721
|
||||
assert type(res) == type((0,))
|
||||
|
||||
#: Okay
|
||||
import types
|
||||
|
||||
if isinstance(res, int):
|
||||
pass
|
||||
if isinstance(res, str):
|
||||
pass
|
||||
if isinstance(res, types.MethodType):
|
||||
pass
|
||||
if type(a) != type(b) or type(a) == type(ccc):
|
||||
pass
|
||||
33
resources/test/fixtures/E722.py
vendored
Normal file
33
resources/test/fixtures/E722.py
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
#: E722
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
#: E722
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
#: E722
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
#: Okay
|
||||
fake_code = """"
|
||||
try:
|
||||
do_something()
|
||||
except:
|
||||
pass
|
||||
"""
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
#: Okay
|
||||
from . import compute_type
|
||||
|
||||
if compute_type(foo) == 5:
|
||||
pass
|
||||
21
resources/test/fixtures/E731.py
vendored
Normal file
21
resources/test/fixtures/E731.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
#: E731
|
||||
f = lambda x: 2 * x
|
||||
#: E731
|
||||
f = lambda x: 2 * x
|
||||
#: E731
|
||||
while False:
|
||||
this = lambda y, z: 2 * x
|
||||
|
||||
|
||||
f = object()
|
||||
#: E731
|
||||
f.method = lambda: "Method"
|
||||
|
||||
f = {}
|
||||
#: E731
|
||||
f["a"] = lambda x: x ** 2
|
||||
|
||||
f = []
|
||||
f.append(lambda x: x ** 2)
|
||||
|
||||
lambda: "no-op"
|
||||
75
resources/test/fixtures/E741.py
vendored
Normal file
75
resources/test/fixtures/E741.py
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
from contextlib import contextmanager
|
||||
|
||||
l = 0
|
||||
I = 0
|
||||
O = 0
|
||||
l: int = 0
|
||||
|
||||
a, l = 0, 1
|
||||
[a, l] = 0, 1
|
||||
a, *l = 0, 1, 2
|
||||
a = l = 0
|
||||
|
||||
o = 0
|
||||
i = 0
|
||||
|
||||
for l in range(3):
|
||||
pass
|
||||
|
||||
|
||||
for a, l in zip(range(3), range(3)):
|
||||
pass
|
||||
|
||||
|
||||
def f1():
|
||||
global l
|
||||
l = 0
|
||||
|
||||
|
||||
def f2():
|
||||
l = 0
|
||||
|
||||
def f3():
|
||||
nonlocal l
|
||||
l = 1
|
||||
|
||||
f3()
|
||||
return l
|
||||
|
||||
|
||||
def f4(l, /, I):
|
||||
return l, I, O
|
||||
|
||||
|
||||
def f5(l=0, *, I=1):
|
||||
return l, I
|
||||
|
||||
|
||||
def f6(*l, **I):
|
||||
return l, I
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ctx1():
|
||||
yield 0
|
||||
|
||||
|
||||
with ctx1() as l:
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ctx2():
|
||||
yield 0, 1
|
||||
|
||||
|
||||
with ctx2() as (a, l):
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError as l:
|
||||
pass
|
||||
|
||||
if (l := 5) > 0:
|
||||
pass
|
||||
14
resources/test/fixtures/E742.py
vendored
Normal file
14
resources/test/fixtures/E742.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
class l:
|
||||
pass
|
||||
|
||||
|
||||
class I:
|
||||
pass
|
||||
|
||||
|
||||
class O:
|
||||
pass
|
||||
|
||||
|
||||
class X:
|
||||
pass
|
||||
15
resources/test/fixtures/E743.py
vendored
Normal file
15
resources/test/fixtures/E743.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
def l():
|
||||
pass
|
||||
|
||||
|
||||
def I():
|
||||
pass
|
||||
|
||||
|
||||
class X:
|
||||
def O(self):
|
||||
pass
|
||||
|
||||
|
||||
def x():
|
||||
pass
|
||||
2
resources/test/fixtures/E999.py
vendored
Normal file
2
resources/test/fixtures/E999.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
def x():
|
||||
|
||||
88
resources/test/fixtures/F401.py
vendored
Normal file
88
resources/test/fixtures/F401.py
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
from __future__ import all_feature_names
|
||||
import functools, os
|
||||
from datetime import datetime
|
||||
from collections import (
|
||||
Counter,
|
||||
OrderedDict,
|
||||
namedtuple,
|
||||
)
|
||||
import multiprocessing.pool
|
||||
import multiprocessing.process
|
||||
import logging.config
|
||||
import logging.handlers
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
NamedTuple,
|
||||
Dict,
|
||||
Type,
|
||||
TypeVar,
|
||||
List,
|
||||
Set,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
from dataclasses import MISSING, field
|
||||
|
||||
from blah import ClassA, ClassB, ClassC
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from models import Fruit, Nut, Vegetable
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import shelve
|
||||
import importlib
|
||||
|
||||
if TYPE_CHECKING:
|
||||
"""Hello, world!"""
|
||||
import pathlib
|
||||
|
||||
z = 1
|
||||
|
||||
|
||||
class X:
|
||||
datetime: datetime
|
||||
foo: Type["NamedTuple"]
|
||||
|
||||
def a(self) -> "namedtuple":
|
||||
x = os.environ["1"]
|
||||
y = Counter()
|
||||
z = multiprocessing.pool.ThreadPool()
|
||||
|
||||
def b(self) -> None:
|
||||
import pickle
|
||||
|
||||
|
||||
__all__ = ["ClassA"] + ["ClassB"]
|
||||
__all__ += ["ClassC"]
|
||||
|
||||
X = TypeVar("X")
|
||||
Y = TypeVar("Y", bound="Dict")
|
||||
Z = TypeVar("Z", "List", "Set")
|
||||
|
||||
a = list["Fruit"]
|
||||
b = Union["Nut", None]
|
||||
c = cast("Vegetable", b)
|
||||
|
||||
Field = lambda default=MISSING: field(default=default)
|
||||
|
||||
|
||||
# Test: access a sub-importation via an alias.
|
||||
import pyarrow as pa
|
||||
import pyarrow.csv
|
||||
|
||||
print(pa.csv.read_csv("test.csv"))
|
||||
|
||||
|
||||
# Test: referencing an import via TypeAlias.
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from typing import TypeAlias
|
||||
else:
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
|
||||
CustomInt: TypeAlias = "np.int8 | np.int16"
|
||||
9
resources/test/fixtures/F402.py
vendored
Normal file
9
resources/test/fixtures/F402.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import os
|
||||
import os.path as path
|
||||
|
||||
|
||||
for os in range(3):
|
||||
pass
|
||||
|
||||
for path in range(3):
|
||||
pass
|
||||
7
resources/test/fixtures/F404.py
vendored
Normal file
7
resources/test/fixtures/F404.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import print_function
|
||||
"""Docstring"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from __future__ import print_function
|
||||
11
resources/test/fixtures/F405.py
vendored
Normal file
11
resources/test/fixtures/F405.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
from mymodule import *
|
||||
|
||||
|
||||
def print_name():
|
||||
print(name)
|
||||
|
||||
|
||||
def print_name(name):
|
||||
print(name)
|
||||
|
||||
__all__ = ['a']
|
||||
9
resources/test/fixtures/F406.py
vendored
Normal file
9
resources/test/fixtures/F406.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from F634 import *
|
||||
|
||||
|
||||
def f():
|
||||
from F634 import *
|
||||
|
||||
|
||||
class F:
|
||||
from F634 import *
|
||||
2
resources/test/fixtures/F407.py
vendored
Normal file
2
resources/test/fixtures/F407.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
from __future__ import print_function
|
||||
from __future__ import non_existent_feature
|
||||
12
resources/test/fixtures/F601.py
vendored
Normal file
12
resources/test/fixtures/F601.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
x = {
|
||||
"a": 1,
|
||||
"a": 2,
|
||||
"b": 3,
|
||||
("a", "b"): 3,
|
||||
("a", "b"): 4,
|
||||
1.0: 2,
|
||||
1: 0,
|
||||
1: 3,
|
||||
b"123": 1,
|
||||
b"123": 4,
|
||||
}
|
||||
7
resources/test/fixtures/F602.py
vendored
Normal file
7
resources/test/fixtures/F602.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
a = 1
|
||||
b = 2
|
||||
x = {
|
||||
a: 1,
|
||||
a: 2,
|
||||
b: 3,
|
||||
}
|
||||
3
resources/test/fixtures/F622.py
vendored
Normal file
3
resources/test/fixtures/F622.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*a, *b, c = (1, 2, 3)
|
||||
*a, b, c = (1, 2, 3)
|
||||
a, b, *c = (1, 2, 3)
|
||||
4
resources/test/fixtures/F631.py
vendored
Normal file
4
resources/test/fixtures/F631.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
assert (False, "x")
|
||||
assert (False,)
|
||||
assert ()
|
||||
assert True
|
||||
5
resources/test/fixtures/F632.py
vendored
Normal file
5
resources/test/fixtures/F632.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
if x is "abc":
|
||||
pass
|
||||
|
||||
if 123 is not y:
|
||||
pass
|
||||
4
resources/test/fixtures/F633.py
vendored
Normal file
4
resources/test/fixtures/F633.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
|
||||
print >> sys.stderr, "Hello"
|
||||
@@ -6,3 +6,5 @@ for _ in range(5):
|
||||
pass
|
||||
elif (3, 4):
|
||||
pass
|
||||
elif ():
|
||||
pass
|
||||
23
resources/test/fixtures/F701.py
vendored
Normal file
23
resources/test/fixtures/F701.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
for i in range(10):
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
i = 0
|
||||
while i < 10:
|
||||
i += 1
|
||||
break
|
||||
|
||||
|
||||
def f():
|
||||
for i in range(10):
|
||||
break
|
||||
|
||||
break
|
||||
|
||||
|
||||
class Foo:
|
||||
break
|
||||
|
||||
|
||||
break
|
||||
23
resources/test/fixtures/F702.py
vendored
Normal file
23
resources/test/fixtures/F702.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
for i in range(10):
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
||||
i = 0
|
||||
while i < 10:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
|
||||
def f():
|
||||
for i in range(10):
|
||||
continue
|
||||
|
||||
continue
|
||||
|
||||
|
||||
class Foo:
|
||||
continue
|
||||
|
||||
|
||||
continue
|
||||
46
resources/test/fixtures/F707.py
vendored
Normal file
46
resources/test/fixtures/F707.py
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
finally:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
pass
|
||||
10
resources/test/fixtures/F722.py
vendored
Normal file
10
resources/test/fixtures/F722.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
class A:
|
||||
pass
|
||||
|
||||
|
||||
def f() -> "A":
|
||||
pass
|
||||
|
||||
|
||||
def g() -> "///":
|
||||
pass
|
||||
124
resources/test/fixtures/F821.py
vendored
Normal file
124
resources/test/fixtures/F821.py
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
def get_name():
|
||||
return self.name
|
||||
|
||||
|
||||
def get_name():
|
||||
return (self.name,)
|
||||
|
||||
|
||||
def get_name():
|
||||
del self.name
|
||||
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
|
||||
x = list()
|
||||
|
||||
|
||||
def randdec(maxprec, maxexp):
|
||||
return numeric_string(maxprec, maxexp)
|
||||
|
||||
|
||||
def ternary_optarg(prec, exp_range, itr):
|
||||
for _ in range(100):
|
||||
a = randdec(prec, 2 * exp_range)
|
||||
b = randdec(prec, 2 * exp_range)
|
||||
c = randdec(prec, 2 * exp_range)
|
||||
yield a, b, c, None
|
||||
yield a, b, c, None, None
|
||||
|
||||
|
||||
class Foo:
|
||||
CLASS_VAR = 1
|
||||
REFERENCES_CLASS_VAR = {"CLASS_VAR": CLASS_VAR}
|
||||
ANNOTATED_CLASS_VAR: int = 2
|
||||
|
||||
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Class:
|
||||
copy_on_model_validation: Literal["none", "deep", "shallow"]
|
||||
post_init_call: Literal["before_validation", "after_validation"]
|
||||
|
||||
def __init__(self):
|
||||
Class
|
||||
|
||||
|
||||
try:
|
||||
x = 1 / 0
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
y: int = 1
|
||||
|
||||
x: "Bar" = 1
|
||||
|
||||
[first] = ["yup"]
|
||||
|
||||
|
||||
from typing import List, TypedDict
|
||||
|
||||
|
||||
class Item(TypedDict):
|
||||
nodes: List[TypedDict("Node", {"name": str})]
|
||||
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Ticket:
|
||||
class Status(Enum):
|
||||
OPEN = "OPEN"
|
||||
CLOSED = "CLOSED"
|
||||
|
||||
def set_status(self, status: Status):
|
||||
self.status = status
|
||||
|
||||
|
||||
def update_tomato():
|
||||
print(TOMATO)
|
||||
TOMATO = "cherry tomato"
|
||||
|
||||
|
||||
A = f'{B}'
|
||||
A = (
|
||||
f'B'
|
||||
f'{B}'
|
||||
)
|
||||
|
||||
|
||||
from typing import Annotated, Literal
|
||||
|
||||
|
||||
def arbitrary_callable() -> None:
|
||||
...
|
||||
|
||||
|
||||
class PEP593Test:
|
||||
field: Annotated[
|
||||
int,
|
||||
"base64",
|
||||
arbitrary_callable(),
|
||||
123,
|
||||
(1, 2, 3),
|
||||
]
|
||||
field_with_stringified_type: Annotated[
|
||||
"PEP593Test",
|
||||
123,
|
||||
]
|
||||
field_with_undefined_stringified_type: Annotated[
|
||||
"PEP593Test123",
|
||||
123,
|
||||
]
|
||||
field_with_nested_subscript: Annotated[
|
||||
dict[Literal["foo"], str],
|
||||
123,
|
||||
]
|
||||
field_with_undefined_nested_subscript: Annotated[
|
||||
dict["foo", "bar"], # Expected to fail as undefined.
|
||||
123,
|
||||
]
|
||||
3
resources/test/fixtures/F822.py
vendored
Normal file
3
resources/test/fixtures/F822.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
a = 1
|
||||
|
||||
__all__ = ["a", "b"]
|
||||
@@ -15,3 +15,13 @@ def baz():
|
||||
global my_var
|
||||
global my_dict
|
||||
my_dict[my_var] += 1
|
||||
|
||||
|
||||
def dec(x):
|
||||
return x
|
||||
|
||||
|
||||
@dec
|
||||
def f():
|
||||
dec = 1
|
||||
return dec
|
||||
37
resources/test/fixtures/F841.py
vendored
Normal file
37
resources/test/fixtures/F841.py
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
try:
|
||||
1 / 0
|
||||
except ValueError as e:
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
1 / 0
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def f1():
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + y
|
||||
|
||||
|
||||
def f2():
|
||||
foo = (1, 2)
|
||||
(a, b) = (1, 2)
|
||||
|
||||
bar = (1, 2)
|
||||
(c, d) = bar
|
||||
|
||||
(x, y) = baz = bar
|
||||
|
||||
|
||||
def f3():
|
||||
locals()
|
||||
x = 1
|
||||
|
||||
|
||||
def f4():
|
||||
_ = 1
|
||||
__ = 1
|
||||
_discarded = 1
|
||||
57
resources/test/fixtures/M001.py
vendored
Normal file
57
resources/test/fixtures/M001.py
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
def f() -> None:
|
||||
# Valid
|
||||
a = 1 # noqa
|
||||
|
||||
# Valid
|
||||
b = 2 # noqa: F841
|
||||
|
||||
# Invalid
|
||||
c = 1 # noqa
|
||||
print(c)
|
||||
|
||||
# Invalid
|
||||
d = 1 # noqa: E501
|
||||
|
||||
# Invalid
|
||||
d = 1 # noqa: F841, E501
|
||||
|
||||
|
||||
# Valid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501
|
||||
|
||||
# Valid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa
|
||||
|
||||
# Invalid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501, F841
|
||||
|
||||
# Invalid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
""" # noqa: E501
|
||||
|
||||
# Invalid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
""" # noqa
|
||||
65
resources/test/fixtures/SPR001.py
vendored
Normal file
65
resources/test/fixtures/SPR001.py
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
class Parent:
|
||||
def method(self):
|
||||
pass
|
||||
|
||||
def wrong(self):
|
||||
pass
|
||||
|
||||
|
||||
class Child(Parent):
|
||||
def method(self):
|
||||
parent = super() # ok
|
||||
super().method() # ok
|
||||
Parent.method(self) # ok
|
||||
Parent.super(1, 2) # ok
|
||||
|
||||
def wrong(self):
|
||||
parent = super(Child, self) # wrong
|
||||
super(Child, self).method # wrong
|
||||
super(
|
||||
Child,
|
||||
self,
|
||||
).method() # wrong
|
||||
|
||||
|
||||
class BaseClass:
|
||||
def f(self):
|
||||
print("f")
|
||||
|
||||
|
||||
def defined_outside(self):
|
||||
super(MyClass, self).f() # CANNOT use super()
|
||||
|
||||
|
||||
class MyClass(BaseClass):
|
||||
def normal(self):
|
||||
super(MyClass, self).f() # can use super()
|
||||
super().f()
|
||||
|
||||
def different_argument(self, other):
|
||||
super(MyClass, other).f() # CANNOT use super()
|
||||
|
||||
def comprehension_scope(self):
|
||||
[super(MyClass, self).f() for x in [1]] # CANNOT use super()
|
||||
|
||||
def inner_functions(self):
|
||||
def outer_argument():
|
||||
super(MyClass, self).f() # CANNOT use super()
|
||||
|
||||
def inner_argument(self):
|
||||
super(MyClass, self).f() # can use super()
|
||||
super().f()
|
||||
|
||||
outer_argument()
|
||||
inner_argument(self)
|
||||
|
||||
def inner_class(self):
|
||||
class InnerClass:
|
||||
super(MyClass, self).f() # CANNOT use super()
|
||||
|
||||
def method(inner_self):
|
||||
super(MyClass, self).f() # CANNOT use super()
|
||||
|
||||
InnerClass().method()
|
||||
|
||||
defined_outside = defined_outside
|
||||
1
resources/test/fixtures/T201.py
vendored
Normal file
1
resources/test/fixtures/T201.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
print("Hello, world!") # T201
|
||||
10
resources/test/fixtures/T203.py
vendored
Normal file
10
resources/test/fixtures/T203.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
from pprint import pprint
|
||||
|
||||
pprint("Hello, world!") # T203
|
||||
|
||||
|
||||
import pprint
|
||||
|
||||
pprint.pprint("Hello, world!") # T203
|
||||
|
||||
pprint.pformat("Hello, world!")
|
||||
13
resources/test/fixtures/U001.py
vendored
Normal file
13
resources/test/fixtures/U001.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
class A:
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class B:
|
||||
__metaclass__ = type
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class C(metaclass=type):
|
||||
pass
|
||||
15
resources/test/fixtures/U002.py
vendored
Normal file
15
resources/test/fixtures/U002.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
from os.path import abspath
|
||||
|
||||
x = abspath(__file__)
|
||||
|
||||
|
||||
import os
|
||||
|
||||
|
||||
y = os.path.abspath(__file__)
|
||||
|
||||
|
||||
from os import path
|
||||
|
||||
|
||||
z = path.abspath(__file__)
|
||||
5
resources/test/fixtures/U003.py
vendored
Normal file
5
resources/test/fixtures/U003.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
type('')
|
||||
type(b'')
|
||||
type(0)
|
||||
type(0.)
|
||||
type(0j)
|
||||
141
resources/test/fixtures/U004.py
vendored
Normal file
141
resources/test/fixtures/U004.py
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
class A:
|
||||
...
|
||||
|
||||
|
||||
class A(object):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object,
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object,
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object,
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
object
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class B(A, object):
|
||||
...
|
||||
|
||||
|
||||
class B(object, A):
|
||||
...
|
||||
|
||||
|
||||
class B(
|
||||
object,
|
||||
A,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class B(
|
||||
A,
|
||||
object,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class B(
|
||||
object,
|
||||
# Comment on A.
|
||||
A,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class B(
|
||||
# Comment on A.
|
||||
A,
|
||||
object,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
class A(object):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object, # )
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
object # )
|
||||
,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
object = A
|
||||
|
||||
|
||||
class B(object):
|
||||
...
|
||||
3
resources/test/fixtures/U005.py
vendored
Normal file
3
resources/test/fixtures/U005.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
self.assertEquals (1, 2)
|
||||
self.assertEquals(1, 2)
|
||||
self.assertEqual(3, 4)
|
||||
12
resources/test/fixtures/U006.py
vendored
Normal file
12
resources/test/fixtures/U006.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
def f(x: List[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
def f(x: typing.List[str]) -> None:
|
||||
...
|
||||
40
resources/test/fixtures/U007.py
vendored
Normal file
40
resources/test/fixtures/U007.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def f(x: Optional[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
def f(x: typing.Optional[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
||||
def f(x: Union[str, int, Union[float, bytes]]) -> None:
|
||||
...
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
def f(x: typing.Union[str, int]) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
||||
def f(x: "Union[str, int, Union[float, bytes]]") -> None:
|
||||
...
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
def f(x: "typing.Union[str, int]") -> None:
|
||||
...
|
||||
2
resources/test/fixtures/W292_0.py
vendored
Normal file
2
resources/test/fixtures/W292_0.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
def fn() -> None:
|
||||
pass
|
||||
2
resources/test/fixtures/W292_1.py
vendored
Normal file
2
resources/test/fixtures/W292_1.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
def fn() -> None:
|
||||
pass # noqa: W292
|
||||
2
resources/test/fixtures/W292_2.py
vendored
Normal file
2
resources/test/fixtures/W292_2.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
def fn() -> None:
|
||||
pass
|
||||
3
resources/test/fixtures/__init__.py
vendored
Normal file
3
resources/test/fixtures/__init__.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
print(__path__)
|
||||
|
||||
__all__ = ["a", "b", "c"]
|
||||
9
resources/test/fixtures/foo/migrations/migration.py
vendored
Normal file
9
resources/test/fixtures/foo/migrations/migration.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
a = "abc"
|
||||
b = f"ghi{'jkl'}"
|
||||
|
||||
c = f"def"
|
||||
d = f"def" + "ghi"
|
||||
e = (
|
||||
f"def" +
|
||||
"ghi"
|
||||
)
|
||||
30
resources/test/fixtures/future_annotations.py
vendored
Normal file
30
resources/test/fixtures/future_annotations.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from models import (
|
||||
Fruit,
|
||||
Nut,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Foo:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
@classmethod
|
||||
def a(cls) -> Foo:
|
||||
return cls(x=0, y=0)
|
||||
|
||||
@classmethod
|
||||
def b(cls) -> "Foo":
|
||||
return cls(x=0, y=0)
|
||||
|
||||
@classmethod
|
||||
def c(cls) -> Bar:
|
||||
return cls(x=0, y=0)
|
||||
|
||||
@classmethod
|
||||
def d(cls) -> Fruit:
|
||||
return cls(x=0, y=0)
|
||||
7
resources/test/fixtures/pyproject.toml
vendored
Normal file
7
resources/test/fixtures/pyproject.toml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
extend-exclude = [
|
||||
"excluded.py",
|
||||
"migrations",
|
||||
"directory/also_excluded.py",
|
||||
]
|
||||
@@ -1,6 +0,0 @@
|
||||
"""Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
"""
|
||||
@@ -1,18 +0,0 @@
|
||||
import os
|
||||
import functools
|
||||
from collections import (
|
||||
Counter,
|
||||
OrderedDict,
|
||||
namedtuple,
|
||||
)
|
||||
import multiprocessing.pool
|
||||
import multiprocessing.process
|
||||
import logging.config
|
||||
import logging.handlers
|
||||
|
||||
|
||||
class X:
|
||||
def a(self) -> "namedtuple":
|
||||
x = os.environ["1"]
|
||||
y = Counter()
|
||||
z = multiprocessing.pool.ThreadPool()
|
||||
@@ -1,46 +0,0 @@
|
||||
def get_name():
|
||||
return self.name
|
||||
|
||||
|
||||
def get_name():
|
||||
return (self.name,)
|
||||
|
||||
|
||||
def get_name():
|
||||
del self.name
|
||||
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
|
||||
x = list()
|
||||
|
||||
|
||||
def randdec(maxprec, maxexp):
|
||||
return numeric_string(maxprec, maxexp)
|
||||
|
||||
|
||||
def ternary_optarg(prec, exp_range, itr):
|
||||
for _ in range(100):
|
||||
a = randdec(prec, 2 * exp_range)
|
||||
b = randdec(prec, 2 * exp_range)
|
||||
c = randdec(prec, 2 * exp_range)
|
||||
yield a, b, c, None
|
||||
yield a, b, c, None, None
|
||||
|
||||
|
||||
class Foo:
|
||||
CLASS_VAR = 1
|
||||
REFERENCES_CLASS_VAR = {"CLASS_VAR": CLASS_VAR}
|
||||
|
||||
|
||||
class Class:
|
||||
def __init__(self):
|
||||
# TODO(charlie): This should be recognized as a defined variable.
|
||||
Class # noqa: F821
|
||||
|
||||
try:
|
||||
x = 1 / 0
|
||||
except Exception as e:
|
||||
print(e)
|
||||
@@ -1,16 +0,0 @@
|
||||
try:
|
||||
1 / 0
|
||||
except ValueError as e:
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
1 / 0
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + y
|
||||
@@ -1,17 +0,0 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
exclude = ["excluded.py", "**/migrations"]
|
||||
select = [
|
||||
"E501",
|
||||
"F401",
|
||||
"F403",
|
||||
"F541",
|
||||
"F634",
|
||||
"F704",
|
||||
"F706",
|
||||
"F821",
|
||||
"F831",
|
||||
"F832",
|
||||
"F841",
|
||||
"F901",
|
||||
]
|
||||
6
src/ast.rs
Normal file
6
src/ast.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod checks;
|
||||
pub mod helpers;
|
||||
pub mod operations;
|
||||
pub mod relocate;
|
||||
pub mod types;
|
||||
pub mod visitor;
|
||||
1124
src/ast/checks.rs
Normal file
1124
src/ast/checks.rs
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user