Compare commits

...

83 Commits

Author SHA1 Message Date
Charlie Marsh
add7fefeb5 Bump version to 0.0.237 2023-01-28 10:52:14 -05:00
Charlie Marsh
ec24947865 Fix version shorthand detection to use -V instead of -v (#2301)
Fixes a regression introduced in eda2be6350 (but not yet released to users). (`-v` is a real flag, but it's an alias for `--verbose`, not `--version`.)

Closes #2299.
2023-01-28 10:47:47 -05:00
Charlie Marsh
8038d32649 Deploy under docs subdirectory 2023-01-28 10:28:40 -05:00
Charlie Marsh
0362cc1098 Allow manual trigger for docs 2023-01-28 10:24:52 -05:00
Charlie Marsh
860e3110c0 Serve docs under /docs subdirectory 2023-01-28 10:22:35 -05:00
Jonxslays
0fa8c578cb Fix typo in typing_extensions (#2298) 2023-01-28 10:03:54 -05:00
Charlie Marsh
861df12269 Preserve global binding kind during reassignments (#2297) 2023-01-28 08:40:09 -05:00
Charlie Marsh
071e3fd196 Split MkDocs site into multiple pages (#2296) 2023-01-28 08:31:16 -05:00
Martin Fischer
dd79ec293a Rename new explain subcommand to rule
We probably want to introduce multiple explain subcommands and
overloading `explain` to explain it all seems like a bad idea.
We may want to introduce a subcommand to explain config options and
config options may end up having the same name as their rules, e.g. the
current `banned-api` is both a rule name (although not yet exposed to
the user) and a config option.

The idea is:

* `ruff rule` lists all rules supported by ruff
* `ruff rule <code>` explains a specific rule
* `ruff linter` lists all linters supported by ruff
* `ruff linter <name>` lists all rules/options supported by a specific linter

(After this commit only the 2nd case is implemented.)
2023-01-28 07:26:20 -05:00
Martin Fischer
5d331e43bf fix: help text and env for --format option of explain subcommand
The doc comment and the env attribute were copied by mistake.
2023-01-28 07:26:20 -05:00
Martin Fischer
ff3563b8ce Remove --check alias introduced in eda2be63
We do not want to support the --{subcommand} legacy format for new
subcommands ... only for subcommands that used this format previously.
2023-01-28 07:26:20 -05:00
Matt Morris
caada2f8bb add missing backticks to flake8 plugin urls in README (#2291) 2023-01-28 07:16:23 -05:00
messense
0b4cc5ac12 Add readme field to pyproject.toml (#2293) 2023-01-28 07:14:58 -05:00
Charlie Marsh
8c70247188 Switch to red MkDocs theme 2023-01-27 23:15:49 -05:00
Charlie Marsh
eaac3cae5e Add MkDocs version of README (#2287)
Co-authored-by: Justin Flannery <juftin@juftin.com>
2023-01-27 22:57:42 -05:00
Charlie Marsh
fd56414b2f Re-add ALL disclaimer 2023-01-27 22:18:20 -05:00
Martin Fischer
9731f96fb4 fix: typo in BREAKING_CHANGES.md & improve wrapping 2023-01-27 21:35:41 -05:00
Charlie Marsh
1a0191f1ac Add release to breaking changes 2023-01-27 20:34:24 -05:00
Charlie Marsh
249cf73d4e Tweak some wording in CLI help (#2285) 2023-01-27 20:25:58 -05:00
Martin Fischer
eda2be6350 Use subcommands for CLI instead of incompatible boolean flags
This commit greatly simplifies the implementation of the CLI,
as well as the user expierence (since --help no longer lists all
options even though many of them are in fact incompatible).

To preserve backwards-compatability as much as possible aliases have
been added for the new subcommands, so for example the following two
commands are equivalent:

    ruff explain E402 --format json
    ruff --explain E402 --format json

However for this to work the legacy-format double-dash command has to
come first, i.e. the following no longer works:

    ruff --format json --explain E402

Since ruff previously had an implicitly default subcommand,
this is preserved for backwards compatibility, i.e. the following two
commands are equivalent:

    ruff .
    ruff check .

Previously ruff didn't complain about several argument combinations that
should have never been allowed, e.g:

    ruff --explain RUF001 --line-length 33

previously worked but now rightfully fails since the explain command
doesn't support a `--line-length` option.
2023-01-27 19:38:17 -05:00
Charlie Marsh
57a68f7c7d Document the location of the personal config file (#2283) 2023-01-27 19:25:55 -05:00
Charlie Marsh
a19dd9237b Add comparison to type checkers (#2282) 2023-01-27 19:18:40 -05:00
Simon Brugman
4f067d806e add clippy and rust_dev to pre-commit (#2256)
I presume the reasoning for not including clippy in `pre-commit` was that it passes all files. This can be turned off with `pass_filenames`, in which case it only runs once.

`cargo +nightly dev generate-all` is also added (when excluding `target` is does not give false positives).

(The overhead of these commands is not much when the build is there. People can always choose to run only certain hooks with `pre-commit run [hook] --all-files`)
2023-01-27 18:53:44 -05:00
Samuel Cormier-Iijima
dd15c69181 [flake8-bandit] Add Rule S110 (try/except/pass) (#2197) 2023-01-27 18:52:55 -05:00
Charlie Marsh
b692921160 Use rustup show in lieu of actions-rs/toolchain (#2280) 2023-01-27 18:51:41 -05:00
Charlie Marsh
b3e8b1b787 Expand heuristic for detecting logging calls (#2279) 2023-01-27 18:41:16 -05:00
Charlie Marsh
0b34ca7107 Move off nightly Rust for dev workflows (#2278) 2023-01-27 18:35:19 -05:00
Charlie Marsh
df44c5124e Add missing autofix levels to sometimes-fixable rules 2023-01-27 18:25:23 -05:00
Simon Brugman
0e27f78b3f feat: include os.getcwdb (bytes) into flake8-use-pathlib (#2276) 2023-01-27 18:25:02 -05:00
Florian Best
cd8ad1df08 mark some fixers as sometimes-fixable (#2271) 2023-01-27 18:23:32 -05:00
Charlie Marsh
d1aaf16e40 Omit typing module from flake8-type-checking by default (#2277) 2023-01-27 18:19:45 -05:00
Ville Skyttä
7320058ce2 Incompatiblity warning updates (#2272) 2023-01-27 18:17:23 -05:00
Aarni Koskela
3a8b367b1c flake8-annotations: Move has_any_typed_arg into correct nested if (#2269) 2023-01-27 18:15:46 -05:00
Ville Skyttä
221b87332c feat: add more DTZ fix suggestions in messages (#2274) 2023-01-27 18:14:17 -05:00
Franck Nijhof
8149c8cbc4 Treat attribute constants as constant for yoda-conditions (#2266)
Accessed attributes that are Python constants should be considered for yoda-conditions


```py
## Error
JediOrder.YODA == age  # SIM300

## OK
age == JediOrder.YODA
```

~~PS: This PR will fail CI, as the `main` branch currently failing.~~
2023-01-27 12:55:12 -05:00
Charlie Marsh
2c415016a6 Update F401 snapshots 2023-01-27 12:43:42 -05:00
Sladyn
bb85119ba8 Convert UnusedImport violation to struct fields (#2141) 2023-01-27 11:31:39 -05:00
Simon Brugman
94551a203e feat: pylint PLE0604 and PLE0605 (#2241) 2023-01-27 11:26:33 -05:00
Charlie Marsh
64c4e4c6c7 Treat constant tuples as constants for yoda-conditions (#2265) 2023-01-27 11:25:57 -05:00
Charlie Marsh
84e4b7c96f Treat builtins as synthetically used (#2251) 2023-01-27 11:25:45 -05:00
Franck Nijhof
ca26f664ec Fix SIM300 to take Python constants into account (#2255)
SIM300 currently doesn't take Python constants into account when looking for Yoda conditions, this PR fixes that behavior.

```python
# Errors
YODA == age  # SIM300
YODA > age  # SIM300
YODA >= age  # SIM300

# OK
age == YODA
age < YODA
age <= YODA
```

Ref: <https://github.com/home-assistant/core/pull/86793>
2023-01-27 11:20:21 -05:00
Aarni Koskela
779b232db9 Fix typo: RelatveImportsOrder (#2264) 2023-01-27 11:15:43 -05:00
Charlie Marsh
a316b26b49 Rewrite some string-format violation messages (#2242) 2023-01-26 19:42:16 -05:00
Charlie Marsh
685d9ab848 Bump version to 0.0.236 2023-01-26 18:47:00 -05:00
Charlie Marsh
093f9156e1 Wrap return-bool-condition-directly fixes in bool() (#2240) 2023-01-26 18:22:34 -05:00
Charlie Marsh
615e62ae24 Clarify E-category rule support (#2239) 2023-01-26 18:12:28 -05:00
Charlie Marsh
76a0c45773 Track type-checking blocks during tree traversal (#2238) 2023-01-26 18:09:28 -05:00
Charlie Marsh
3ec46f0936 Allow pytest in shebang (#2237) 2023-01-26 17:32:23 -05:00
Charlie Marsh
a6ec2eb044 Avoid removing trailing comments on pass statements (#2235)
This isn't super consistent with some other rules, but... if you have a lone body, with a `pass`, followed by a comment, it's probably surprising if it gets removed. Let's retain the comment.

Closes #2231.
2023-01-26 17:29:13 -05:00
Simon Brugman
9d3a5530af refactor: move violations to linters (#2234) 2023-01-26 17:28:14 -05:00
Simon Brugman
8766e6a666 docs(readme): add featuretools (#2236) 2023-01-26 17:24:45 -05:00
Charlie Marsh
b08367b5a8 Avoid flagging blind exceptions with valid logging (#2232) 2023-01-26 17:05:01 -05:00
Charlie Marsh
98a8330124 Add stylist settings to all LibCST invocations (#2225) 2023-01-26 16:59:33 -05:00
Charlie Marsh
4d52ea87ef Implement exempt-modules setting from flake8-type-checking (#2230) 2023-01-26 16:55:32 -05:00
Charlie Marsh
291239b9f1 Fix range for try-consider-else (#2228) 2023-01-26 16:36:18 -05:00
Charlie Marsh
224334b6d1 Avoid erroneous class autofixes in indented blocks (#2226) 2023-01-26 16:24:21 -05:00
Charlie Marsh
0cab3f8437 Preserve indentation when fixing via LibCST (#2223) 2023-01-26 16:09:35 -05:00
Charlie Marsh
5f8810e987 Add strictness setting for flake8-typing-imports (#2221) 2023-01-26 16:04:21 -05:00
Charlie Marsh
f15c562a1c Remove unused overridden property (#2217) 2023-01-26 14:46:46 -05:00
Martin Fischer
4f3b63edd4 fix: --explain reporting the wrong linter
Fixes a regression introduced in 4e4643aa5d.

We want the longest prefixes to be checked first so we of course
have to reverse the sorting when sorting by prefix length.

Fixes #2210.
2023-01-26 13:53:35 -05:00
Simon Brugman
bab8691132 chore: fix script indent (#2213) 2023-01-26 13:53:22 -05:00
Charlie Marsh
50c85fd192 Add a fixable and unfixable example to the docs (#2211) 2023-01-26 13:23:21 -05:00
Martin Fischer
23819ae338 Group options in --help output and sort them by importance
`ruff --help` previously listed 37 options in no particular order
(with niche options like --isolated being listed before before essential
options such as --select).  This commit remedies that and additionally
groups the options by making use of the Clap help_heading feature.

Note that while the source code has previously also referred to
--add-noqa, --show-settings, and --show-files as "subcommands"
this commit intentionally does not list them under the new
Subcommands section since contrary to --explain and --clean
combining them with most of the other options makes sense.
2023-01-26 13:06:29 -05:00
Martin Fischer
4bf2879067 refactor: Move add_noqa if branch up 2023-01-26 13:06:29 -05:00
Martin Fischer
b359f3a9ff refactor: Get rid of ruff::resolver::FileDiscovery 2023-01-26 13:06:29 -05:00
Martin Fischer
73d0461d55 refactor: Check required_version in Settings::from_configuration
The idiomatic way in Rust is to make invalid types unrepresentable
instead of paranoidly calling a validate method everywhere.
2023-01-26 13:06:29 -05:00
Martin Fischer
55bb36fb8b refactor: Introduce LogLevelArgs 2023-01-26 13:06:29 -05:00
Martin Fischer
cebea16fe4 refactor: Move Args::partition call after panic::set_hook 2023-01-26 13:06:29 -05:00
Charlie Marsh
f7be192f8b Alphabetize Flake8 plugins in the README (#2209) 2023-01-26 13:05:30 -05:00
Edgar R. M
e88275280b Implement some rules from flake8-logging-format (#2150) 2023-01-26 12:58:10 -05:00
Samuel Cormier-Iijima
7370a27c09 Don't flag B009/B010 if identifiers would be mangled (#2204) 2023-01-26 12:56:18 -05:00
jvstme
0ad6b8224d Fix typo in src option docs (#2201) 2023-01-26 12:23:09 -05:00
Henry Schreiner
f3aa409d9a docs(readme): add pypa's build (#2200) 2023-01-26 12:18:04 -05:00
Charlie Marsh
adb5c5b150 Fix respect_gitignore reference (#2196) 2023-01-26 09:53:17 -05:00
Martin Fischer
b69b6a7ec8 refactor: Move comments before if conditions 2023-01-25 22:08:35 -05:00
Martin Fischer
a7d2def9cd refactor: Move ruff_cli::resolve to own module 2023-01-25 22:08:35 -05:00
Martin Fischer
d9ead4e6df refactor: Rename CLI arg structs from Cli to Args
Technically the command-line interface (CLI) encompasses both input and
output, so naming the input structs 'Args' is more accurate than 'Cli'.
2023-01-25 22:08:35 -05:00
Charlie Marsh
b346f74915 Run cargo update (#2185) 2023-01-25 21:32:44 -05:00
Eric Roberts
708295f4c9 Move violations for pycodestyle rules to rules files (#2138) 2023-01-25 20:11:36 -05:00
Charlie Marsh
4190f1045e Remove manual newline from autofix helpers (#2184) 2023-01-25 19:53:26 -05:00
Florian Stasse
353857e2a5 Implement TRY400 (#2115) 2023-01-25 19:42:19 -05:00
Denis Gavrilyuk
55b43c8ea7 feat: implement TRY002 and TRY003 (#2135) 2023-01-25 19:22:43 -05:00
Anders Kaseorg
6036d1bbe2 flake8_executable: Only match shebang at beginning of line (#2183)
The Python implementation uses `re.match` for this, which only matches
at the beginning of a line.

https://github.com/xuhdev/flake8-executable/blob/v2.1.3/flake8_executable/__init__.py#L124

We could use `Regex::captures_read_at`, but that’s a more complicated
API; it’s easier to anchor the regex with `^`.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2023-01-25 18:57:09 -05:00
217 changed files with 5324 additions and 2244 deletions

View File

@@ -22,16 +22,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- run: cargo build --all
- run: ./target/debug/ruff_dev generate-all
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo +nightly dev generate-all'."
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo +nightly dev generate-all'."
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo dev generate-all'."
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo dev generate-all'."
- run: git diff --exit-code -- README.md ruff.schema.json
cargo-fmt:
@@ -39,12 +36,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
components: rustfmt
- name: "Install Rust toolchain"
run: rustup component add rustfmt
- run: cargo fmt --all --check
cargo_clippy:
@@ -52,13 +45,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
components: clippy
target: wasm32-unknown-unknown
- name: "Install Rust toolchain"
run: |
rustup component add clippy
rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v1
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic
- run: cargo clippy -p ruff --target wasm32-unknown-unknown --all-features -- -D warnings -W clippy::pedantic
@@ -71,20 +61,17 @@ jobs:
name: "cargo test | ${{ matrix.os }}"
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.65.0
override: true
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- run: cargo install cargo-insta
- run: pip install black[d]==22.12.0
- name: Run tests (Ubuntu)
- name: "Run tests (Ubuntu)"
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
cargo insta test --all --delete-unreferenced-snapshots
git diff --exit-code
- name: Run tests (Windows)
- name: "Run tests (Windows)"
if: ${{ matrix.os == 'windows-latest' }}
shell: bash
run: |
@@ -102,10 +89,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
override: true
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- run: ./scripts/add_rule.py --name DoTheThing --code PLC999 --linter pylint
- run: cargo check
@@ -114,61 +99,26 @@ jobs:
./scripts/add_rule.py --name FirstRule --code TST001 --linter test
- run: cargo check
# TODO(charlie): Re-enable the `wasm-pack` tests.
# See: https://github.com/charliermarsh/ruff/issues/1425
# wasm-pack-test:
# name: "wasm-pack test"
# runs-on: ubuntu-latest
# env:
# WASM_BINDGEN_TEST_TIMEOUT: 60
# steps:
# - uses: actions/checkout@v3
# - uses: actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: nightly-2022-11-01
# override: true
# - uses: actions/cache@v3
# env:
# cache-name: cache-cargo
# with:
# path: |
# ~/.cargo/registry
# ~/.cargo/git
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
# restore-keys: |
# ${{ runner.os }}-build-${{ env.cache-name }}-
# ${{ runner.os }}-build-
# ${{ runner.os }}-
# - uses: jetli/wasm-pack-action@v0.4.0
# - uses: jetli/wasm-bindgen-action@v0.2.0
# - run: wasm-pack test --node
maturin-build:
name: "maturin build"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.65.0
override: true
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- run: pip install maturin
- run: maturin build -b bin
- run: python scripts/transform_readme.py --target pypi
typos:
name: Spell Check with Typos
name: "spell check"
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v3
- name: Check spelling of file.txt
uses: crate-ci/typos@master
- uses: actions/checkout@v3
- uses: crate-ci/typos@master
with:
files: .

35
.github/workflows/docs.yaml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: mkdocs
on:
push:
paths:
- README.md
- mkdocs.template.yml
- .github/workflows/docs.yaml
branches: [ main ]
workflow_dispatch:
jobs:
mkdocs:
runs-on: ubuntu-latest
env:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- name: "Install dependencies"
run: |
pip install "mkdocs~=1.4.2" "mkdocs-material~=9.0.6"
- name: "Copy README File"
run: |
python scripts/transform_readme.py --target mkdocs
python scripts/generate_mkdocs.py
mkdocs build --strict
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@2.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages publish site --project-name=ruff-docs --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}

View File

@@ -24,21 +24,17 @@ jobs:
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 - x86_64
- name: "Install Rust toolchain"
run: rustup show
- name: "Build wheels - x86_64"
uses: messense/maturin-action@v1
with:
target: x86_64
args: --release --out dist --sdist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: Install built wheel - x86_64
- name: "Install built wheel - x86_64"
run: |
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -51,20 +47,16 @@ jobs:
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
- name: "Install Rust toolchain"
run: rustup show
- name: "Build wheels - universal2"
uses: messense/maturin-action@v1
with:
args: --release --universal2 --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: Install built wheel - universal2
- name: "Install built wheel - universal2"
run: |
pip install dist/${{ env.CRATE_NAME }}-*universal2.whl --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -81,22 +73,18 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.target }}
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
default: true
- name: Build wheels
- name: "Install Rust toolchain"
run: rustup show
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: Install built wheel
- name: "Install built wheel"
shell: bash
run: |
python -m pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -113,17 +101,17 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build wheels
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: Install built wheel
- name: "Install built wheel"
if: matrix.target == 'x86_64'
run: |
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -139,7 +127,7 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Build wheels
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
@@ -158,7 +146,7 @@ jobs:
pip3 install -U pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -177,13 +165,13 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build wheels
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: Install built wheel
- name: "Install built wheel"
if: matrix.target == 'x86_64-unknown-linux-musl'
uses: addnab/docker-run-action@v3
with:
@@ -192,7 +180,7 @@ jobs:
run: |
apk add py3-pip
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links /io/dist/ --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -212,7 +200,7 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Build wheels
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
@@ -228,7 +216,7 @@ jobs:
apk add py3-pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -252,17 +240,17 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: pypy${{ matrix.python-version }}
- name: Build wheels
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -i pypy${{ matrix.python-version }} -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: Install built wheel
- name: "Install built wheel"
if: matrix.target == 'x86_64'
run: |
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -285,7 +273,7 @@ jobs:
with:
name: wheels
- uses: actions/setup-python@v4
- name: Publish to PyPi
- name: "Publish to PyPi"
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.FLAKE8_TO_RUFF_TOKEN }}

View File

@@ -18,12 +18,8 @@ jobs:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
target: wasm32-unknown-unknown
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@v3
with:
node-version: 18

View File

@@ -26,21 +26,17 @@ jobs:
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 - x86_64
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - x86_64"
uses: messense/maturin-action@v1
with:
target: x86_64
args: --release --out dist --sdist
- name: Install built wheel - x86_64
- name: "Install built wheel - x86_64"
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -53,20 +49,16 @@ jobs:
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
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - universal2"
uses: messense/maturin-action@v1
with:
args: --release --universal2 --out dist
- name: Install built wheel - universal2
- name: "Install built wheel - universal2"
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*universal2.whl --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -83,22 +75,18 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.target }}
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
default: true
- name: Build wheels
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist
- name: Install built wheel
- name: "Install built wheel"
shell: bash
run: |
python -m pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -115,17 +103,19 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build wheels
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist
- name: Install built wheel
- name: "Install built wheel"
if: matrix.target == 'x86_64'
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -141,7 +131,9 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Build wheels
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
@@ -160,7 +152,7 @@ jobs:
pip3 install -U pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -179,13 +171,15 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build wheels
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
args: --release --out dist
- name: Install built wheel
- name: "Install built wheel"
if: matrix.target == 'x86_64-unknown-linux-musl'
uses: addnab/docker-run-action@v3
with:
@@ -194,7 +188,7 @@ jobs:
run: |
apk add py3-pip
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links /io/dist/ --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -214,7 +208,9 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Build wheels
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
@@ -230,7 +226,7 @@ jobs:
apk add py3-pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -254,17 +250,19 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: pypy${{ matrix.python-version }}
- name: Build wheels
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -i pypy${{ matrix.python-version }}
- name: Install built wheel
- name: "Install built wheel"
if: matrix.target == 'x86_64'
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
- name: "Upload wheels"
uses: actions/upload-artifact@v3
with:
name: wheels
@@ -288,13 +286,13 @@ jobs:
with:
name: wheels
- uses: actions/setup-python@v4
- name: Publish to PyPi
- name: "Publish to PyPi"
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.RUFF_TOKEN }}
run: |
pip install --upgrade twine
twine upload --skip-existing *
- name: Update pre-commit mirror
- name: "Update pre-commit mirror"
run: |
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.RUFF_PRE_COMMIT_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/charliermarsh/ruff-pre-commit/dispatches --data '{"event_type": "pypi_release"}'

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
# Local cache
.ruff_cache
resources/test/cpython
docs/
mkdocs.yml
###
# Rust.gitignore

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.235
rev: v0.0.237
hooks:
- id: ruff
args: [--fix]
@@ -15,6 +15,17 @@ repos:
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo +nightly fmt --
entry: cargo fmt --
language: rust
types: [rust]
- id: clippy
name: clippy
entry: cargo clippy --workspace --all-targets --all-features
language: rust
pass_filenames: false
- id: dev-generate-all
name: dev-generate-all
entry: cargo dev generate-all
language: rust
pass_filenames: false
exclude: target

View File

@@ -1,5 +1,44 @@
# Breaking Changes
## 0.0.237
### `--explain`, `--clean`, and `--generate-shell-completion` are now subcommands ([#2190](https://github.com/charliermarsh/ruff/pull/2190))
`--explain`, `--clean`, and `--generate-shell-completion` are now implemented as subcommands:
ruff . # Still works! And will always work.
ruff check . # New! Also works.
ruff --explain E402 # Still works.
ruff rule E402 # New! Also works. (And preferred.)
# Oops! The command has to come first.
ruff --format json --explain E402 # No longer works.
ruff --explain E402 --format json # Still works!
ruff rule E402 --format json # Works! (And preferred.)
This change is largely backwards compatible -- most users should experience
no change in behavior. However, please note the following exceptions:
* Subcommands will now fail when invoked with unsupported arguments, instead
of silently ignoring them. For example, the following will now fail:
ruff --clean --respect-gitignore
(the `clean` command doesn't support `--respect-gitignore`.)
* The semantics of `ruff <arg>` have changed slightly when `<arg>` is a valid subcommand.
For example, prior to this release, running `ruff rule` would run `ruff` over a file or
directory called `rule`. Now, `ruff rule` would invoke the `rule` subcommand. This should
only impact projects with files or directories named `rule`, `check`, `explain`, `clean`,
or `generate-shell-completion`.
* Scripts that invoke ruff should supply `--` before any positional arguments.
(The semantics of `ruff -- <arg>` have not changed.)
* `--explain` previously treated `--format grouped` as a synonym for `--format text`.
This is no longer supported; instead, use `--format text`.
## 0.0.226
### `misplaced-comparison-constant` (`PLC2201`) was deprecated in favor of `SIM300` ([#1980](https://github.com/charliermarsh/ruff/pull/1980))

View File

@@ -39,20 +39,24 @@ 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.
For rustfmt and Clippy, we use [nightly Rust][nightly], as it is stricter than stable Rust.
(However, tests and builds use stable Rust.)
and that it passes both the lint and test validation checks:
```shell
cargo +nightly fmt --all # Auto-formatting...
cargo +nightly clippy --fix --workspace --all-targets --all-features # Linting...
cargo fmt --all # Auto-formatting...
cargo clippy --fix --workspace --all-targets --all-features # Linting...
cargo test --all # 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.
If you have `pre-commit` [installed](https://pre-commit.com/#installation) then you can use it to
assist with formatting and linting. The following command will run the `pre-commit` hooks:
```shell
pre-commit run --all-files
```
Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration
prior to merging.
@@ -79,7 +83,7 @@ To trigger the violation, you'll likely want to augment the logic in `src/checke
defines the Python AST visitor, responsible for iterating over the abstract syntax tree and
collecting diagnostics as it goes.
If you need to inspect the AST, you can run `cargo +nightly dev print-ast` with a Python file. Grep
If you need to inspect the AST, you can run `cargo dev print-ast` with a Python file. 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/[linter]`, named to match
@@ -87,7 +91,7 @@ the code you defined earlier (e.g., `resources/test/fixtures/pycodestyle/E402.py
contain a variety of violations and non-violations designed to evaluate and demonstrate the behavior
of your lint rule.
Run `cargo +nightly dev generate-all` to generate the code for your new fixture. Then run Ruff
Run `cargo dev generate-all` to generate the code for your new fixture. Then run Ruff
locally with (e.g.) `cargo run resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
@@ -95,7 +99,7 @@ Once you're satisfied with the output, codify the behavior as a snapshot test by
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.
Finally, regenerate the documentation and generated code with `cargo +nightly dev generate-all`.
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
### Example: Adding a new configuration option
@@ -121,7 +125,7 @@ You may also want to add the new configuration option to the `flake8-to-ruff` to
responsible for converting `flake8` configuration files to Ruff's TOML format. This logic
lives in `flake8_to_ruff/src/converter.rs`.
Finally, regenerate the documentation and generated code with `cargo +nightly dev generate-all`.
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
## Release process
@@ -131,5 +135,3 @@ 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).
[nightly]: https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust

253
Cargo.lock generated
View File

@@ -82,9 +82,9 @@ dependencies = [
[[package]]
name = "assert_cmd"
version = "2.0.7"
version = "2.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3"
checksum = "9834fcc22e0874394a010230586367d4a3e9f11b560f469262678547e1d2575e"
dependencies = [
"bstr 1.1.0",
"doc-comment",
@@ -181,9 +181,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.11.1"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "cachedir"
@@ -236,12 +236,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "chunked_transfer"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
[[package]]
name = "ciborium"
version = "0.2.0"
@@ -283,13 +277,13 @@ dependencies = [
[[package]]
name = "clap"
version = "4.0.32"
version = "4.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39"
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
dependencies = [
"bitflags",
"clap_derive",
"clap_lex 0.3.0",
"clap_lex 0.3.1",
"is-terminal",
"once_cell",
"strsim",
@@ -298,11 +292,11 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.0.7"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b"
checksum = "3d6540eedc41f8a5a76cf3d8d458057dcdf817be4158a55b5f861f7a5483de75"
dependencies = [
"clap 4.0.32",
"clap 4.1.4",
]
[[package]]
@@ -311,26 +305,26 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31"
dependencies = [
"clap 4.0.32",
"clap 4.1.4",
"clap_complete",
"clap_complete_fig",
]
[[package]]
name = "clap_complete_fig"
version = "4.0.2"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46b30e010e669cd021e5004f3be26cff6b7c08d2a8a0d65b48d43a8cc0efd6c3"
checksum = "cf0c76d8fcf782a1102ccfcd10ca8246e7fdd609c1cd6deddbb96cb638e9bb5c"
dependencies = [
"clap 4.0.32",
"clap 4.1.4",
"clap_complete",
]
[[package]]
name = "clap_derive"
version = "4.0.21"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
dependencies = [
"heck",
"proc-macro-error",
@@ -350,9 +344,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
dependencies = [
"os_str_bytes",
]
@@ -399,15 +393,14 @@ checksum = "5458d9d1a587efaf5091602c59d299696a3877a439c8f6d461a2d3cce11df87a"
[[package]]
name = "console"
version = "0.15.2"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c"
checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"terminal_size",
"winapi",
"windows-sys",
]
[[package]]
@@ -551,9 +544,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.85"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd"
checksum = "b61a7545f753a88bcbe0a70de1fcc0221e10bfc752f576754fa91e663db1622e"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -563,9 +556,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.85"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0"
checksum = "f464457d494b5ed6905c63b0c4704842aba319084a0a3561cdc1359536b53200"
dependencies = [
"cc",
"codespan-reporting",
@@ -578,15 +571,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.85"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59"
checksum = "43c7119ce3a3701ed81aca8410b9acf6fc399d2629d057b87e2efa4e63a3aaea"
[[package]]
name = "cxxbridge-macro"
version = "1.0.85"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
checksum = "65e07508b90551e610910fa648a1878991d367064997a596135b86df30daf07e"
dependencies = [
"proc-macro2",
"quote",
@@ -757,10 +750,10 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.235"
version = "0.0.237"
dependencies = [
"anyhow",
"clap 4.0.32",
"clap 4.1.4",
"colored",
"configparser",
"once_cell",
@@ -771,7 +764,7 @@ dependencies = [
"serde_json",
"strum",
"strum_macros",
"toml 0.6.0",
"toml",
]
[[package]]
@@ -833,18 +826,18 @@ dependencies = [
[[package]]
name = "glob"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
"aho-corasick",
"bstr 0.2.17",
"bstr 1.1.0",
"fnv",
"log",
"regex",
@@ -931,11 +924,10 @@ dependencies = [
[[package]]
name = "ignore"
version = "0.4.18"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
dependencies = [
"crossbeam-utils",
"globset",
"lazy_static",
"log",
@@ -989,9 +981,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.23.0"
version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48b08a091dfe5b09a6a9688c468fdd5b4396e92ce09e2eb932f0884b02788a4"
checksum = "f6f0f08b46e4379744de2ab67aa8f7de3ffd1da3e275adc41fcc82053ede46ff"
dependencies = [
"console",
"lazy_static",
@@ -1014,9 +1006,9 @@ dependencies = [
[[package]]
name = "io-lifetimes"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
dependencies = [
"libc",
"windows-sys",
@@ -1242,9 +1234,9 @@ dependencies = [
[[package]]
name = "matches"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "memchr"
@@ -1302,9 +1294,9 @@ checksum = "d906846a98739ed9d73d66e62c2641eef8321f1734b7a1156ab045a0248fb2b3"
[[package]]
name = "nix"
version = "0.26.1"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694"
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
dependencies = [
"bitflags",
"cfg-if",
@@ -1369,9 +1361,9 @@ dependencies = [
[[package]]
name = "num-complex"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits",
"serde",
@@ -1408,18 +1400,18 @@ dependencies = [
[[package]]
name = "num_enum"
version = "0.5.7"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.7"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -1457,9 +1449,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.5"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
dependencies = [
"cfg-if",
"libc",
@@ -1527,9 +1519,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pest"
version = "2.5.3"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4257b4a04d91f7e9e6290be5d3da4804dd5784fafde3a497d73eb2b4a158c30a"
checksum = "4ab62d2fa33726dbe6321cc97ef96d8cde531e3eeaf858a058de53a8a6d40d8f"
dependencies = [
"thiserror",
"ucd-trie",
@@ -1537,9 +1529,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.5.3"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241cda393b0cdd65e62e07e12454f1f25d57017dcc514b1514cd3c4645e3a0a6"
checksum = "8bf026e2d0581559db66d837fe5242320f525d85c76283c61f4d51a1238d65ea"
dependencies = [
"pest",
"pest_generator",
@@ -1547,9 +1539,9 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.5.3"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46b53634d8c8196302953c74d5352f33d0c512a9499bd2ce468fc9f4128fa27c"
checksum = "2b27bd18aa01d91c8ed2b61ea23406a676b42d82609c6e2581fba42f0c15f17f"
dependencies = [
"pest",
"pest_meta",
@@ -1560,9 +1552,9 @@ dependencies = [
[[package]]
name = "pest_meta"
version = "2.5.3"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef4f1332a8d4678b41966bb4cc1d0676880e84183a1ecc3f4b69f03e99c7a51"
checksum = "9f02b677c1859756359fc9983c2e56a0237f18624a3789528804406b7e915e5d"
dependencies = [
"once_cell",
"pest",
@@ -1703,9 +1695,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "predicates"
version = "2.1.4"
version = "2.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54fc5dc63ed3bbf19494623db4f3af16842c0d975818e469022d09e53f0aa05"
checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd"
dependencies = [
"difflib",
"itertools",
@@ -1730,13 +1722,12 @@ dependencies = [
[[package]]
name = "proc-macro-crate"
version = "1.2.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
dependencies = [
"once_cell",
"thiserror",
"toml 0.5.11",
"toml_edit",
]
[[package]]
@@ -1765,9 +1756,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.49"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
@@ -1852,9 +1843,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.10.1"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3"
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
@@ -1884,9 +1875,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.7.0"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
@@ -1931,13 +1922,13 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.235"
version = "0.0.237"
dependencies = [
"anyhow",
"bitflags",
"cfg-if",
"chrono",
"clap 4.0.32",
"clap 4.1.4",
"colored",
"console_error_panic_hook",
"console_log",
@@ -1979,14 +1970,14 @@ dependencies = [
"textwrap",
"thiserror",
"titlecase",
"toml 0.6.0",
"toml",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]]
name = "ruff_cli"
version = "0.0.235"
version = "0.0.237"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1995,7 +1986,7 @@ dependencies = [
"bincode",
"cachedir",
"chrono",
"clap 4.0.32",
"clap 4.1.4",
"clap_complete_command",
"clearscreen",
"colored",
@@ -2023,10 +2014,10 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.235"
version = "0.0.237"
dependencies = [
"anyhow",
"clap 4.0.32",
"clap 4.1.4",
"itertools",
"libcst",
"once_cell",
@@ -2044,7 +2035,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.235"
version = "0.0.237"
dependencies = [
"once_cell",
"proc-macro2",
@@ -2071,9 +2062,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.36.5"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
dependencies = [
"bitflags",
"errno",
@@ -2085,9 +2076,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.20.7"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
dependencies = [
"log",
"ring",
@@ -2253,9 +2244,9 @@ checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
[[package]]
name = "serde"
version = "1.0.151"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
@@ -2273,9 +2264,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.151"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
@@ -2448,23 +2439,13 @@ dependencies = [
[[package]]
name = "termcolor"
version = "1.1.3"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "terminfo"
version = "0.7.5"
@@ -2602,15 +2583,6 @@ dependencies = [
"regex",
]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "toml"
version = "0.6.0"
@@ -2733,9 +2705,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.8"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-ident"
@@ -2801,12 +2773,11 @@ dependencies = [
[[package]]
name = "ureq"
version = "2.5.0"
version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f"
checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d"
dependencies = [
"base64",
"chunked_transfer",
"flate2",
"log",
"once_cell",
@@ -3000,9 +2971,9 @@ dependencies = [
[[package]]
name = "which"
version = "4.3.0"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
dependencies = [
"either",
"libc",
@@ -3063,45 +3034,45 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "yaml-rust"

View File

@@ -8,7 +8,7 @@ default-members = [".", "ruff_cli"]
[package]
name = "ruff"
version = "0.0.235"
version = "0.0.237"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -46,7 +46,7 @@ num-traits = "0.2.15"
once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { version = "1.6.0" }
ruff_macros = { version = "0.0.235", path = "ruff_macros" }
ruff_macros = { version = "0.0.237", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }

205
LICENSE
View File

@@ -800,3 +800,208 @@ are:
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
- flake8-logging-format, licensed as follows:
"""
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

613
README.md
View File

@@ -1,3 +1,5 @@
<!-- Begin section: Overview -->
# Ruff
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json)](https://github.com/charliermarsh/ruff)
@@ -67,7 +69,9 @@ Ruff is extremely actively developed and used in major open-source projects like
- [Home Assistant](https://github.com/home-assistant/core)
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
- [cibuildwheel (PyPA)](https://github.com/pypa/cibuildwheel)
- [build (PyPA)](https://github.com/pypa/build)
- [Babel](https://github.com/python-babel/babel)
- [featuretools](https://github.com/alteryx/featuretools)
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
@@ -104,6 +108,8 @@ developer of [Zulip](https://github.com/zulip/zulip):
> This is just ridiculously fast... `ruff` is amazing.
<!-- End section: Overview -->
## Table of Contents
1. [Installation and Usage](#installation-and-usage)
@@ -113,9 +119,9 @@ developer of [Zulip](https://github.com/zulip/zulip):
1. [pycodestyle (E, W)](#pycodestyle-e-w)
1. [mccabe (C90)](#mccabe-c90)
1. [isort (I)](#isort-i)
1. [pep8-naming (N)](#pep8-naming-n)
1. [pydocstyle (D)](#pydocstyle-d)
1. [pyupgrade (UP)](#pyupgrade-up)
1. [pep8-naming (N)](#pep8-naming-n)
1. [flake8-2020 (YTT)](#flake8-2020-ytt)
1. [flake8-annotations (ANN)](#flake8-annotations-ann)
1. [flake8-bandit (S)](#flake8-bandit-s)
@@ -123,30 +129,31 @@ developer of [Zulip](https://github.com/zulip/zulip):
1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap-fbt)
1. [flake8-bugbear (B)](#flake8-bugbear-b)
1. [flake8-builtins (A)](#flake8-builtins-a)
1. [flake8-commas (COM)](#flake8-commas-com)
1. [flake8-comprehensions (C4)](#flake8-comprehensions-c4)
1. [flake8-datetimez (DTZ)](#flake8-datetimez-dtz)
1. [flake8-debugger (T10)](#flake8-debugger-t10)
1. [flake8-errmsg (EM)](#flake8-errmsg-em)
1. [flake8-executable (EXE)](#flake8-executable-exe)
1. [flake8-implicit-str-concat (ISC)](#flake8-implicit-str-concat-isc)
1. [flake8-import-conventions (ICN)](#flake8-import-conventions-icn)
1. [flake8-logging-format (G)](#flake8-logging-format-g)
1. [flake8-no-pep420 (INP)](#flake8-no-pep420-inp)
1. [flake8-pie (PIE)](#flake8-pie-pie)
1. [flake8-print (T20)](#flake8-print-t20)
1. [flake8-pytest-style (PT)](#flake8-pytest-style-pt)
1. [flake8-quotes (Q)](#flake8-quotes-q)
1. [flake8-return (RET)](#flake8-return-ret)
1. [flake8-simplify (SIM)](#flake8-simplify-sim)
1. [flake8-tidy-imports (TID)](#flake8-tidy-imports-tid)
1. [flake8-type-checking (TCH)](#flake8-type-checking-tch)
1. [flake8-unused-arguments (ARG)](#flake8-unused-arguments-arg)
1. [flake8-datetimez (DTZ)](#flake8-datetimez-dtz)
1. [flake8-use-pathlib (PTH)](#flake8-use-pathlib-pth)
1. [eradicate (ERA)](#eradicate-era)
1. [pandas-vet (PD)](#pandas-vet-pd)
1. [pygrep-hooks (PGH)](#pygrep-hooks-pgh)
1. [Pylint (PL)](#pylint-pl)
1. [flake8-pie (PIE)](#flake8-pie-pie)
1. [flake8-commas (COM)](#flake8-commas-com)
1. [flake8-no-pep420 (INP)](#flake8-no-pep420-inp)
1. [flake8-executable (EXE)](#flake8-executable-exe)
1. [flake8-type-checking (TCH)](#flake8-type-checking-tch)
1. [tryceratops (TRY)](#tryceratops-try)
1. [flake8-use-pathlib (PTH)](#flake8-use-pathlib-pth)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf)<!-- End auto-generated table of contents. -->
1. [Editor Integrations](#editor-integrations)
1. [FAQ](#faq)
@@ -158,6 +165,8 @@ developer of [Zulip](https://github.com/zulip/zulip):
## Installation and Usage
<!-- Begin section: Installation and Usage -->
### Installation
Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
@@ -213,13 +222,17 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.235'
rev: 'v0.0.237'
hooks:
- id: ruff
```
<!-- End section: Installation and Usage -->
## Configuration
<!-- Begin section: Configuration -->
Ruff is configurable both via `pyproject.toml` and the command line. For a full list of configurable
options, see the [API reference](#reference).
@@ -227,12 +240,14 @@ If left unspecified, the default configuration is equivalent to:
```toml
[tool.ruff]
line-length = 88
# Enable Pyflakes `E` and `F` codes by default.
select = ["E", "F"]
ignore = []
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["A", "B", "C", "D", "E", "F", "..."]
unfixable = []
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
@@ -257,6 +272,9 @@ exclude = [
]
per-file-ignores = {}
# Same as Black.
line-length = 88
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
@@ -268,20 +286,21 @@ target-version = "py310"
max-complexity = 10
```
As an example, the following would configure Ruff to: (1) avoid enforcing line-length violations
(`E501`); (2) never remove unused imports (`F401`); and (3) ignore import-at-top-of-file violations
(`E402`) in `__init__.py` files:
As an example, the following would configure Ruff to: (1) enforce flake8-bugbear rules, in addition
to the defaults; (2) avoid enforcing line-length violations (`E501`); (3) avoid attempting to fix
flake8-bugbear (`B`) violations; and (3) ignore import-at-top-of-file violations (`E402`) in
`__init__.py` files:
```toml
[tool.ruff]
# Enable Pyflakes and pycodestyle rules.
select = ["E", "F"]
# Enable flake8-bugbear (`B`) rules.
select = ["E", "F", "B"]
# Never enforce `E501` (line length violations).
ignore = ["E501"]
# Never try to fix `F401` (unused imports).
unfixable = ["F401"]
# Avoid trying to fix flake8-bugbear (`B`) violations.
unfixable = ["B"]
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
[tool.ruff.per-file-ignores]
@@ -311,6 +330,17 @@ As a special-case, Ruff also supports the `ALL` code, which enables all rules. N
formats. Enabling `ALL` without further configuration may result in suboptimal behavior, especially
for the `pydocstyle` plugin.
If you're wondering how to configure Ruff, here are some **recommended guidelines**:
- Prefer `select` and `ignore` over `extend-select` and `extend-ignore`, to make your rule set
explicit.
- Use `ALL` with discretion. Enabling `ALL` will implicitly enable new rules whenever you upgrade.
- Start with a small set of rules (`select = ["E", "F"]`) and add a category at-a-time. For example,
you might consider expanding to `select = ["E", "F", "B"]` to enable the popular `flake8-bugbear`
extension.
- By default, Ruff's autofix is aggressive. If you find that it's too aggressive for your liking,
consider turning off autofix for specific rules or categories (see: [FAQ](#ruff-tried-to-fix-something-but-it-broke-my-code-what-should-i-do)).
As an alternative to `pyproject.toml`, Ruff will also respect a `ruff.toml` file, which implements
an equivalent schema (though the `[tool.ruff]` hierarchy can be omitted). For example, the
`pyproject.toml` described above would be represented via the following `ruff.toml`:
@@ -346,86 +376,24 @@ See `ruff --help` for more:
```
Ruff: An extremely fast Python linter.
Usage: ruff [OPTIONS] [FILES]...
Usage: ruff [OPTIONS] <COMMAND>
Arguments:
[FILES]...
Commands:
check Run Ruff on the given files or directories (default)
rule Explain a rule
clean Clear any caches in the current directory and any subdirectories
help Print this message or the help of the given subcommand(s)
Options:
--config <CONFIG>
Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
-v, --verbose
Enable verbose logging
-q, --quiet
Print lint violations, but nothing else
-s, --silent
Disable all logging (but still exit with status code "1" upon detecting lint violations)
-e, --exit-zero
Exit with status code "0", even upon detecting lint violations
-w, --watch
Run in watch mode by re-running whenever files change
--fix
Attempt to automatically fix lint violations
--fix-only
Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`
--diff
Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
-n, --no-cache
Disable cache reads
--isolated
Ignore all configuration files
--select <RULE_CODE>
Comma-separated list of rule codes to enable (or ALL, to enable all rules)
--extend-select <RULE_CODE>
Like --select, but adds additional rule codes on top of the selected ones
--ignore <RULE_CODE>
Comma-separated list of rule codes to disable
--extend-ignore <RULE_CODE>
Like --ignore, but adds additional rule codes on top of the ignored ones
--exclude <FILE_PATTERN>
List of paths, used to omit files and/or directories from analysis
--extend-exclude <FILE_PATTERN>
Like --exclude, but adds additional files and directories on top of those already excluded
--fixable <RULE_CODE>
List of rule codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
--unfixable <RULE_CODE>
List of rule codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
--per-file-ignores <PER_FILE_IGNORES>
List of mappings from file pattern to code to exclude
--format <FORMAT>
Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint]
--stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin
--cache-dir <CACHE_DIR>
Path to the cache directory [env: RUFF_CACHE_DIR=]
--show-source
Show violations with source code
--respect-gitignore
Respect file exclusions via `.gitignore` and other standard ignore files
--force-exclude
Enforce exclusions, even for paths passed to Ruff directly on the command-line
--update-check
Enable or disable automatic update checks
--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
--line-length <LINE_LENGTH>
Set the line-length for length-associated rules and automatic formatting
--add-noqa
Enable automatic additions of `noqa` directives to failing lines
--clean
Clear any caches in the current directory or any subdirectories
--explain <EXPLAIN>
Explain a rule
--show-files
See the files Ruff will be run against with the current settings
--show-settings
See the settings Ruff will use to lint a given Python file
-h, --help
Print help information
-V, --version
Print version information
-h, --help Print help
-V, --version Print version
Log levels:
-v, --verbose Enable verbose logging
-q, --quiet Print lint violations, but nothing else
-s, --silent Disable all logging (but still exit with status code "1" upon detecting lint violations)
For help with a specific command, see: `ruff help <command>`.
```
<!-- End auto-generated cli help. -->
@@ -446,10 +414,9 @@ There are a few exceptions to these rules:
resolved relative to the _current working directory_.
3. If no `pyproject.toml` file is found in the filesystem hierarchy, Ruff will fall back to using
a default configuration. If a user-specific configuration file exists
at `${config_dir}/ruff/pyproject.toml`,
that file will be used instead of the default configuration, with `${config_dir}` being
determined via the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate, and all
relative paths being again resolved relative to the _current working directory_.
at `${config_dir}/ruff/pyproject.toml`, that file will be used instead of the default
configuration, with `${config_dir}` being determined via the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html)
crate, and all relative paths being again resolved relative to the _current working directory_.
4. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via
`--select`) will override the settings in _every_ resolved configuration file.
@@ -544,11 +511,17 @@ Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This
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 rule codes.
<!-- End section: Configuration -->
## Supported Rules
<!-- Begin section: Rules -->
Regardless of the rule's origin, Ruff re-implements every rule in Rust as a first-party feature.
By default, Ruff enables all `E` and `F` rule codes, which correspond to those built-in to Flake8.
By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category,
and a [subset](#error-e) of the `E` category, omitting those stylistic rules made obsolete by the
use of an autoformatter, like [Black](https://github.com/psf/black).
The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option.
@@ -567,20 +540,20 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/) on PyPI.
| F405 | import-star-usage | `{name}` may be undefined, or defined from star imports: {sources} | |
| F406 | import-star-not-permitted | `from {name} import *` only allowed at module level | |
| F407 | future-feature-not-defined | Future feature `{name}` is not defined | |
| F501 | percent-format-invalid-format | '...' % ... has invalid format string: {message} | |
| F502 | percent-format-expected-mapping | '...' % ... expected mapping but got sequence | |
| F503 | percent-format-expected-sequence | '...' % ... expected sequence but got mapping | |
| F504 | percent-format-extra-named-arguments | '...' % ... has unused named argument(s): {message} | 🛠 |
| F505 | percent-format-missing-argument | '...' % ... is missing argument(s) for placeholder(s): {message} | |
| F506 | percent-format-mixed-positional-and-named | '...' % ... has mixed positional and named placeholders | |
| F507 | percent-format-positional-count-mismatch | '...' % ... has {wanted} placeholder(s) but {got} substitution(s) | |
| F508 | percent-format-star-requires-sequence | '...' % ... `*` specifier requires sequence | |
| F509 | percent-format-unsupported-format-character | '...' % ... has unsupported format character '{char}' | |
| F521 | string-dot-format-invalid-format | '...'.format(...) has invalid format string: {message} | |
| F522 | string-dot-format-extra-named-arguments | '...'.format(...) has unused named argument(s): {message} | 🛠 |
| F523 | string-dot-format-extra-positional-arguments | '...'.format(...) has unused arguments at position(s): {message} | |
| F524 | string-dot-format-missing-arguments | '...'.format(...) is missing argument(s) for placeholder(s): {message} | |
| F525 | string-dot-format-mixing-automatic | '...'.format(...) mixes automatic and manual numbering | |
| F501 | percent-format-invalid-format | `%`-format string has invalid format string: {message} | |
| F502 | percent-format-expected-mapping | `%`-format string expected mapping but got sequence | |
| F503 | percent-format-expected-sequence | `%`-format string expected sequence but got mapping | |
| F504 | percent-format-extra-named-arguments | `%`-format string has unused named argument(s): {message} | 🛠 |
| F505 | percent-format-missing-argument | `%`-format string is missing argument(s) for placeholder(s): {message} | |
| F506 | percent-format-mixed-positional-and-named | `%`-format string has mixed positional and named placeholders | |
| F507 | percent-format-positional-count-mismatch | `%`-format string has {wanted} placeholder(s) but {got} substitution(s) | |
| F508 | percent-format-star-requires-sequence | `%`-format string `*` specifier requires sequence | |
| F509 | percent-format-unsupported-format-character | `%`-format string has unsupported format character '{char}' | |
| F521 | string-dot-format-invalid-format | `.format` call has invalid format string: {message} | |
| F522 | string-dot-format-extra-named-arguments | `.format` call has unused named argument(s): {message} | 🛠 |
| F523 | string-dot-format-extra-positional-arguments | `.format` call has unused arguments at position(s): {message} | |
| F524 | string-dot-format-missing-arguments | `.format` call is missing argument(s) for placeholder(s): {message} | |
| F525 | string-dot-format-mixing-automatic | `.format` string mixes automatic and manual numbering | |
| F541 | f-string-missing-placeholders | f-string without any placeholders | 🛠 |
| F601 | multi-value-repeated-key-literal | Dictionary key literal `{name}` repeated | 🛠 |
| F602 | multi-value-repeated-key-variable | Dictionary key `{name}` repeated | 🛠 |
@@ -652,6 +625,28 @@ For more, see [isort](https://pypi.org/project/isort/) on PyPI.
| I001 | unsorted-imports | Import block is un-sorted or un-formatted | 🛠 |
| I002 | missing-required-import | Missing required import: `{name}` | 🛠 |
### pep8-naming (N)
For more, see [pep8-naming](https://pypi.org/project/pep8-naming/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| N801 | invalid-class-name | Class name `{name}` should use CapWords convention | |
| N802 | invalid-function-name | Function name `{name}` should be lowercase | |
| N803 | invalid-argument-name | Argument name `{name}` should be lowercase | |
| N804 | invalid-first-argument-name-for-class-method | First argument of a class method should be named `cls` | |
| N805 | invalid-first-argument-name-for-method | First argument of a method should be named `self` | |
| N806 | non-lowercase-variable-in-function | Variable `{name}` in function should be lowercase | |
| N807 | dunder-function-name | Function name should not start and end with `__` | |
| N811 | constant-imported-as-non-constant | Constant `{name}` imported as non-constant `{asname}` | |
| N812 | lowercase-imported-as-non-lowercase | Lowercase `{name}` imported as non-lowercase `{asname}` | |
| N813 | camelcase-imported-as-lowercase | Camelcase `{name}` imported as lowercase `{asname}` | |
| N814 | camelcase-imported-as-constant | Camelcase `{name}` imported as constant `{asname}` | |
| N815 | mixed-case-variable-in-class-scope | Variable `{name}` in class scope should not be mixedCase | |
| N816 | mixed-case-variable-in-global-scope | Variable `{name}` in global scope should not be mixedCase | |
| N817 | camelcase-imported-as-acronym | Camelcase `{name}` imported as acronym `{asname}` | |
| N818 | error-suffix-on-exception-name | Exception name `{name}` should be named with an Error suffix | |
### pydocstyle (D)
For more, see [pydocstyle](https://pypi.org/project/pydocstyle/) on PyPI.
@@ -745,28 +740,6 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/) on PyPI.
| UP033 | functools-cache | Use `@functools.cache` instead of `@functools.lru_cache(maxsize=None)` | 🛠 |
| UP034 | extraneous-parentheses | Avoid extraneous parentheses | 🛠 |
### pep8-naming (N)
For more, see [pep8-naming](https://pypi.org/project/pep8-naming/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| N801 | invalid-class-name | Class name `{name}` should use CapWords convention | |
| N802 | invalid-function-name | Function name `{name}` should be lowercase | |
| N803 | invalid-argument-name | Argument name `{name}` should be lowercase | |
| N804 | invalid-first-argument-name-for-class-method | First argument of a class method should be named `cls` | |
| N805 | invalid-first-argument-name-for-method | First argument of a method should be named `self` | |
| N806 | non-lowercase-variable-in-function | Variable `{name}` in function should be lowercase | |
| N807 | dunder-function-name | Function name should not start and end with `__` | |
| N811 | constant-imported-as-non-constant | Constant `{name}` imported as non-constant `{asname}` | |
| N812 | lowercase-imported-as-non-lowercase | Lowercase `{name}` imported as non-lowercase `{asname}` | |
| N813 | camelcase-imported-as-lowercase | Camelcase `{name}` imported as lowercase `{asname}` | |
| N814 | camelcase-imported-as-constant | Camelcase `{name}` imported as constant `{asname}` | |
| N815 | mixed-case-variable-in-class-scope | Variable `{name}` in class scope should not be mixedCase | |
| N816 | mixed-case-variable-in-global-scope | Variable `{name}` in global scope should not be mixedCase | |
| N817 | camelcase-imported-as-acronym | Camelcase `{name}` imported as acronym `{asname}` | |
| N818 | error-suffix-on-exception-name | Exception name `{name}` should be named with an Error suffix | |
### flake8-2020 (YTT)
For more, see [flake8-2020](https://pypi.org/project/flake8-2020/) on PyPI.
@@ -816,6 +789,7 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/) on PyPI.
| S106 | hardcoded-password-func-arg | Possible hardcoded password: "{}" | |
| S107 | hardcoded-password-default | Possible hardcoded password: "{}" | |
| S108 | hardcoded-temp-file | Probable insecure usage of temporary file or directory: "{}" | |
| S110 | try-except-pass | `try`-`except`-`pass` detected, consider logging the exception | |
| S113 | request-without-timeout | Probable use of requests call with timeout set to `{value}` | |
| S324 | hashlib-insecure-hash-function | Probable use of insecure hash functions in `hashlib`: "{}" | |
| S501 | request-with-no-cert-validation | Probable use of `{string}` call with `verify=False` disabling SSL certificate checks | |
@@ -854,12 +828,12 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/) on PyPI
| B004 | unreliable-callable-check | Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results. | |
| B005 | strip-with-multi-characters | Using `.strip()` with multi-character strings is misleading the reader | |
| B006 | mutable-argument-default | Do not use mutable data structures for argument defaults | |
| B007 | unused-loop-control-variable | Loop control variable `{name}` not used within loop body | |
| B007 | unused-loop-control-variable | Loop control variable `{name}` not used within loop body | 🛠 |
| B008 | function-call-argument-default | Do not perform function call `{name}` in argument defaults | |
| B009 | get-attr-with-constant | Do not call `getattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
| B010 | set-attr-with-constant | Do not call `setattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
| B011 | do-not-assert-false | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
| B012 | jump-statement-in-finally | `{name}` inside finally blocks cause exceptions to be silenced | |
| B012 | jump-statement-in-finally | `{name}` inside `finally` blocks cause exceptions to be silenced | |
| B013 | redundant-tuple-in-exception-handler | A length-one tuple literal is redundant. Write `except {name}` instead of `except ({name},)`. | 🛠 |
| B014 | duplicate-handler-exception | Exception handler with duplicate exception: `{name}` | 🛠 |
| B015 | useless-comparison | Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it. | |
@@ -888,6 +862,16 @@ For more, see [flake8-builtins](https://pypi.org/project/flake8-builtins/) on Py
| A002 | builtin-argument-shadowing | Argument `{name}` is shadowing a python builtin | |
| A003 | builtin-attribute-shadowing | Class attribute `{name}` is shadowing a python builtin | |
### flake8-commas (COM)
For more, see [flake8-commas](https://pypi.org/project/flake8-commas/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| COM812 | trailing-comma-missing | Trailing comma missing | 🛠 |
| COM818 | trailing-comma-on-bare-tuple-prohibited | Trailing comma on bare tuple prohibited | |
| COM819 | trailing-comma-prohibited | Trailing comma prohibited | 🛠 |
### flake8-comprehensions (C4)
For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/) on PyPI.
@@ -911,6 +895,22 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens
| C416 | unnecessary-comprehension | Unnecessary `{obj_type}` comprehension (rewrite using `{obj_type}()`) | 🛠 |
| C417 | unnecessary-map | Unnecessary `map` usage (rewrite using a generator expression) | |
### flake8-datetimez (DTZ)
For more, see [flake8-datetimez](https://pypi.org/project/flake8-datetimez/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| DTZ001 | call-datetime-without-tzinfo | The use of `datetime.datetime()` without `tzinfo` argument is not allowed | |
| DTZ002 | call-datetime-today | The use of `datetime.datetime.today()` is not allowed, use `datetime.datetime.now(tz=)` instead | |
| DTZ003 | call-datetime-utcnow | The use of `datetime.datetime.utcnow()` is not allowed, use `datetime.datetime.now(tz=)` instead | |
| DTZ004 | call-datetime-utcfromtimestamp | The use of `datetime.datetime.utcfromtimestamp()` is not allowed, use `datetime.datetime.fromtimestamp(ts, tz=)` instead | |
| DTZ005 | call-datetime-now-without-tzinfo | The use of `datetime.datetime.now()` without `tz` argument is not allowed | |
| DTZ006 | call-datetime-fromtimestamp | The use of `datetime.datetime.fromtimestamp()` without `tz` argument is not allowed | |
| DTZ007 | call-datetime-strptime-without-zone | The use of `datetime.datetime.strptime()` without %z must be followed by `.replace(tzinfo=)` or `.astimezone()` | |
| DTZ011 | call-date-today | The use of `datetime.date.today()` is not allowed, use `datetime.datetime.now(tz=).date()` instead | |
| DTZ012 | call-date-fromtimestamp | The use of `datetime.date.fromtimestamp()` is not allowed, use `datetime.datetime.fromtimestamp(ts, tz=).date()` instead | |
### flake8-debugger (T10)
For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/) on PyPI.
@@ -929,6 +929,18 @@ For more, see [flake8-errmsg](https://pypi.org/project/flake8-errmsg/) on PyPI.
| EM102 | f-string-in-exception | Exception must not use an f-string literal, assign to variable first | |
| EM103 | dot-format-in-exception | Exception must not use a `.format()` string directly, assign to variable first | |
### flake8-executable (EXE)
For more, see [flake8-executable](https://pypi.org/project/flake8-executable/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| EXE001 | shebang-not-executable | Shebang is present but file is not executable | |
| EXE002 | shebang-missing-executable-file | The file is executable but no shebang is present | |
| EXE003 | shebang-python | Shebang should contain "python" | |
| EXE004 | shebang-whitespace | Avoid whitespace before shebang | 🛠 |
| EXE005 | shebang-newline | Shebang should be at the beginning of the file | |
### flake8-implicit-str-concat (ISC)
For more, see [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/) on PyPI.
@@ -947,6 +959,42 @@ For more, see [flake8-import-conventions](https://github.com/joaopalmeiro/flake8
| ---- | ---- | ------- | --- |
| ICN001 | import-alias-is-not-conventional | `{name}` should be imported as `{asname}` | |
### flake8-logging-format (G)
For more, see [flake8-logging-format](https://pypi.org/project/flake8-logging-format/0.9.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| G001 | logging-string-format | Logging statement uses `string.format()` | |
| G002 | logging-percent-format | Logging statement uses `%` | |
| G003 | logging-string-concat | Logging statement uses `+` | |
| G004 | logging-f-string | Logging statement uses f-string | |
| G010 | logging-warn | Logging statement uses `warn` instead of `warning` | 🛠 |
| G101 | logging-extra-attr-clash | Logging statement uses an extra field that clashes with a LogRecord field: `{key}` | |
| G201 | logging-exc-info | Logging `.exception(...)` should be used instead of `.error(..., exc_info=True)` | |
| G202 | logging-redundant-exc-info | Logging statement has redundant `exc_info` | |
### flake8-no-pep420 (INP)
For more, see [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| INP001 | implicit-namespace-package | File `{filename}` is part of an implicit namespace package. Add an `__init__.py`. | |
### flake8-pie (PIE)
For more, see [flake8-pie](https://pypi.org/project/flake8-pie/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PIE790 | no-unnecessary-pass | Unnecessary `pass` statement | 🛠 |
| PIE794 | dupe-class-field-definitions | Class field `{name}` is defined multiple times | 🛠 |
| PIE796 | prefer-unique-enums | Enum contains duplicate value: `{value}` | |
| PIE800 | no-unnecessary-spread | Unnecessary spread `**` | |
| PIE804 | no-unnecessary-dict-kwargs | Unnecessary `dict` kwargs | |
| PIE807 | prefer-list-builtin | Prefer `list` over useless lambda | 🛠 |
### flake8-print (T20)
For more, see [flake8-print](https://pypi.org/project/flake8-print/) on PyPI.
@@ -1055,6 +1103,18 @@ For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports
| TID251 | banned-api | `{name}` is banned: {message} | |
| TID252 | relative-imports | Relative imports from parent modules are banned | |
### flake8-type-checking (TCH)
For more, see [flake8-type-checking](https://pypi.org/project/flake8-type-checking/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| TCH001 | typing-only-first-party-import | Move application import `{}` into a type-checking block | |
| TCH002 | typing-only-third-party-import | Move third-party import `{}` into a type-checking block | |
| TCH003 | typing-only-standard-library-import | Move standard library import `{}` into a type-checking block | |
| TCH004 | runtime-import-in-type-checking-block | Move import `{}` out of type-checking block. Import is used for more than type hinting. | |
| TCH005 | empty-type-checking-block | Found empty type-checking block | |
### flake8-unused-arguments (ARG)
For more, see [flake8-unused-arguments](https://pypi.org/project/flake8-unused-arguments/) on PyPI.
@@ -1067,21 +1127,37 @@ For more, see [flake8-unused-arguments](https://pypi.org/project/flake8-unused-a
| ARG004 | unused-static-method-argument | Unused static method argument: `{name}` | |
| ARG005 | unused-lambda-argument | Unused lambda argument: `{name}` | |
### flake8-datetimez (DTZ)
### flake8-use-pathlib (PTH)
For more, see [flake8-datetimez](https://pypi.org/project/flake8-datetimez/) on PyPI.
For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| DTZ001 | call-datetime-without-tzinfo | The use of `datetime.datetime()` without `tzinfo` argument is not allowed | |
| DTZ002 | call-datetime-today | The use of `datetime.datetime.today()` is not allowed | |
| DTZ003 | call-datetime-utcnow | The use of `datetime.datetime.utcnow()` is not allowed | |
| DTZ004 | call-datetime-utcfromtimestamp | The use of `datetime.datetime.utcfromtimestamp()` is not allowed | |
| DTZ005 | call-datetime-now-without-tzinfo | The use of `datetime.datetime.now()` without `tz` argument is not allowed | |
| DTZ006 | call-datetime-fromtimestamp | The use of `datetime.datetime.fromtimestamp()` without `tz` argument is not allowed | |
| DTZ007 | call-datetime-strptime-without-zone | The use of `datetime.datetime.strptime()` without %z must be followed by `.replace(tzinfo=)` or `.astimezone()` | |
| DTZ011 | call-date-today | The use of `datetime.date.today()` is not allowed. | |
| DTZ012 | call-date-fromtimestamp | The use of `datetime.date.fromtimestamp()` is not allowed | |
| PTH100 | pathlib-abspath | `os.path.abspath` should be replaced by `.resolve()` | |
| PTH101 | pathlib-chmod | `os.chmod` should be replaced by `.chmod()` | |
| PTH102 | pathlib-mkdir | `os.mkdir` should be replaced by `.mkdir()` | |
| PTH103 | pathlib-makedirs | `os.makedirs` should be replaced by `.mkdir(parents=True)` | |
| PTH104 | pathlib-rename | `os.rename` should be replaced by `.rename()` | |
| PTH105 | pathlib-replace | `os.replace`should be replaced by `.replace()` | |
| PTH106 | pathlib-rmdir | `os.rmdir` should be replaced by `.rmdir()` | |
| PTH107 | pathlib-remove | `os.remove` should be replaced by `.unlink()` | |
| PTH108 | pathlib-unlink | `os.unlink` should be replaced by `.unlink()` | |
| PTH109 | pathlib-getcwd | `os.getcwd()` should be replaced by `Path.cwd()` | |
| PTH110 | pathlib-exists | `os.path.exists` should be replaced by `.exists()` | |
| PTH111 | pathlib-expanduser | `os.path.expanduser` should be replaced by `.expanduser()` | |
| PTH112 | pathlib-is-dir | `os.path.isdir` should be replaced by `.is_dir()` | |
| PTH113 | pathlib-is-file | `os.path.isfile` should be replaced by `.is_file()` | |
| PTH114 | pathlib-is-link | `os.path.islink` should be replaced by `.is_symlink()` | |
| PTH115 | pathlib-readlink | `os.readlink(` should be replaced by `.readlink()` | |
| PTH116 | pathlib-stat | `os.stat` should be replaced by `.stat()` or `.owner()` or `.group()` | |
| PTH117 | pathlib-is-abs | `os.path.isabs` should be replaced by `.is_absolute()` | |
| PTH118 | pathlib-join | `os.path.join` should be replaced by foo_path / "bar" | |
| PTH119 | pathlib-basename | `os.path.basename` should be replaced by `.name` | |
| PTH120 | pathlib-dirname | `os.path.dirname` should be replaced by `.parent` | |
| PTH121 | pathlib-samefile | `os.path.samefile` should be replaced by `.samefile()` | |
| PTH122 | pathlib-splitext | `os.path.splitext` should be replaced by `.suffix` | |
| PTH123 | pathlib-open | `open("foo")` should be replaced by`Path("foo").open()` | |
| PTH124 | pathlib-py-path | `py.path` is in maintenance mode, use `pathlib` instead | |
### eradicate (ERA)
@@ -1136,6 +1212,8 @@ For more, see [Pylint](https://pypi.org/project/pylint/) on PyPI.
| ---- | ---- | ------- | --- |
| PLE0117 | nonlocal-without-binding | Nonlocal name `{name}` found without binding | |
| PLE0118 | used-prior-global-declaration | Name `{name}` is used prior to global declaration on line {line} | |
| PLE0604 | invalid-all-object | Invalid object in `__all__`, must contain only strings | |
| PLE0605 | invalid-all-format | Invalid format for `__all__`, must be `tuple` or `list` | |
| PLE1142 | await-outside-async | `await` should be used within an async function | |
#### Refactor (PLR)
@@ -1154,104 +1232,20 @@ For more, see [Pylint](https://pypi.org/project/pylint/) on PyPI.
| PLW0120 | useless-else-on-loop | Else clause on loop without a break statement, remove the else and de-indent all the code inside it | |
| PLW0602 | global-variable-not-assigned | Using global for `{name}` but no assignment is done | |
### flake8-pie (PIE)
For more, see [flake8-pie](https://pypi.org/project/flake8-pie/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PIE790 | no-unnecessary-pass | Unnecessary `pass` statement | 🛠 |
| PIE794 | dupe-class-field-definitions | Class field `{name}` is defined multiple times | 🛠 |
| PIE796 | prefer-unique-enums | Enum contains duplicate value: `{value}` | |
| PIE800 | no-unnecessary-spread | Unnecessary spread `**` | |
| PIE804 | no-unnecessary-dict-kwargs | Unnecessary `dict` kwargs | |
| PIE807 | prefer-list-builtin | Prefer `list` over useless lambda | 🛠 |
### flake8-commas (COM)
For more, see [flake8-commas](https://pypi.org/project/flake8-commas/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| COM812 | trailing-comma-missing | Trailing comma missing | 🛠 |
| COM818 | trailing-comma-on-bare-tuple-prohibited | Trailing comma on bare tuple prohibited | |
| COM819 | trailing-comma-prohibited | Trailing comma prohibited | 🛠 |
### flake8-no-pep420 (INP)
For more, see [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| INP001 | implicit-namespace-package | File `{filename}` is part of an implicit namespace package. Add an `__init__.py`. | |
### flake8-executable (EXE)
For more, see [flake8-executable](https://pypi.org/project/flake8-executable/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| EXE001 | shebang-not-executable | Shebang is present but file is not executable | |
| EXE002 | shebang-missing-executable-file | The file is executable but no shebang is present | |
| EXE003 | shebang-python | Shebang should contain "python" | |
| EXE004 | shebang-whitespace | Avoid whitespace before shebang | 🛠 |
| EXE005 | shebang-newline | Shebang should be at the beginning of the file | |
### flake8-type-checking (TCH)
For more, see [flake8-type-checking](https://pypi.org/project/flake8-type-checking/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| TCH001 | typing-only-first-party-import | Move application import `{}` into a type-checking block | |
| TCH002 | typing-only-third-party-import | Move third-party import `{}` into a type-checking block | |
| TCH003 | typing-only-standard-library-import | Move standard library import `{}` into a type-checking block | |
| TCH004 | runtime-import-in-type-checking-block | Move import `{}` out of type-checking block. Import is used for more than type hinting. | |
| TCH005 | empty-type-checking-block | Found empty type-checking block | |
### tryceratops (TRY)
For more, see [tryceratops](https://pypi.org/project/tryceratops/1.1.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| TRY002 | raise-vanilla-class | Create your own exception | |
| TRY003 | raise-vanilla-args | Avoid specifying long messages outside the exception class | |
| TRY004 | prefer-type-error | Prefer `TypeError` exception for invalid type | 🛠 |
| TRY200 | reraise-no-cause | Use `raise from` to specify exception cause | |
| TRY201 | verbose-raise | Use `raise` without specifying exception name | |
| TRY300 | try-consider-else | Consider `else` block | |
| TRY300 | try-consider-else | Consider moving this statement to an `else` block | |
| TRY301 | raise-within-try | Abstract `raise` to an inner function | |
### flake8-use-pathlib (PTH)
For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PTH100 | pathlib-abspath | `os.path.abspath` should be replaced by `.resolve()` | |
| PTH101 | pathlib-chmod | `os.chmod` should be replaced by `.chmod()` | |
| PTH102 | pathlib-mkdir | `os.mkdir` should be replaced by `.mkdir()` | |
| PTH103 | pathlib-makedirs | `os.makedirs` should be replaced by `.mkdir(parents=True)` | |
| PTH104 | pathlib-rename | `os.rename` should be replaced by `.rename()` | |
| PTH105 | pathlib-replace | `os.replace`should be replaced by `.replace()` | |
| PTH106 | pathlib-rmdir | `os.rmdir` should be replaced by `.rmdir()` | |
| PTH107 | pathlib-remove | `os.remove` should be replaced by `.unlink()` | |
| PTH108 | pathlib-unlink | `os.unlink` should be replaced by `.unlink()` | |
| PTH109 | pathlib-getcwd | `os.getcwd()` should be replaced by `Path.cwd()` | |
| PTH110 | pathlib-exists | `os.path.exists` should be replaced by `.exists()` | |
| PTH111 | pathlib-expanduser | `os.path.expanduser` should be replaced by `.expanduser()` | |
| PTH112 | pathlib-is-dir | `os.path.isdir` should be replaced by `.is_dir()` | |
| PTH113 | pathlib-is-file | `os.path.isfile` should be replaced by `.is_file()` | |
| PTH114 | pathlib-is-link | `os.path.islink` should be replaced by `.is_symlink()` | |
| PTH115 | pathlib-readlink | `os.readlink(` should be replaced by `.readlink()` | |
| PTH116 | pathlib-stat | `os.stat` should be replaced by `.stat()` or `.owner()` or `.group()` | |
| PTH117 | pathlib-is-abs | `os.path.isabs` should be replaced by `.is_absolute()` | |
| PTH118 | pathlib-join | `os.path.join` should be replaced by foo_path / "bar" | |
| PTH119 | pathlib-basename | `os.path.basename` should be replaced by `.name` | |
| PTH120 | pathlib-dirname | `os.path.dirname` should be replaced by `.parent` | |
| PTH121 | pathlib-samefile | `os.path.samefile` should be replaced by `.samefile()` | |
| PTH122 | pathlib-splitext | `os.path.splitext` should be replaced by `.suffix` | |
| PTH123 | pathlib-open | `open("foo")` should be replaced by`Path("foo").open()` | |
| PTH124 | pathlib-py-path | `py.path` is in maintenance mode, use `pathlib` instead | |
| TRY400 | error-instead-of-exception | Use `logging.exception` instead of `logging.error` | |
### Ruff-specific rules (RUF)
@@ -1266,8 +1260,12 @@ For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
<!-- End auto-generated sections. -->
<!-- End section: Rules -->
## Editor Integrations
<!-- Begin section: Editor Integrations -->
### VS Code (Official)
Download the [Ruff VS Code extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff),
@@ -1506,8 +1504,12 @@ jobs:
run: ruff --format=github .
```
<!-- End section: Editor Integrations -->
## FAQ
<!-- Begin section: FAQ -->
### Is Ruff compatible with Black?
Yes. Ruff is compatible with [Black](https://github.com/psf/black) out-of-the-box, as long as
@@ -1524,7 +1526,9 @@ automatically convert your existing configuration.)
Ruff can be used as a drop-in replacement for Flake8 when used (1) without or with a small number of
plugins, (2) alongside Black, and (3) on Python 3 code.
Under those conditions, Ruff implements every rule in Flake8.
Under those conditions, Ruff implements every rule in Flake8. In practice, that means Ruff
implements all of the `F` rules (which originate from Pyflakes), along with a subset of the `E` and
`W` rules (which originate from pycodestyle).
Ruff also re-implements some of the most popular Flake8 plugins and related code quality tools
natively, including:
@@ -1548,8 +1552,9 @@ natively, including:
- [`flake8-executable`](https://pypi.org/project/flake8-executable/)
- [`flake8-implicit-str-concat`](https://pypi.org/project/flake8-implicit-str-concat/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-logging-format`](https://pypi.org/project/flake8-logging-format/)
- [`flake8-no-pep420`](https://pypi.org/project/flake8-no-pep420)
- [`flake8-pie`](https://pypi.org/project/flake8-pie/) ([#1543](https://github.com/charliermarsh/ruff/issues/1543))
- [`flake8-pie`](https://pypi.org/project/flake8-pie/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-pytest-style`](https://pypi.org/project/flake8-pytest-style/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
@@ -1557,6 +1562,8 @@ natively, including:
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) ([#998](https://github.com/charliermarsh/ruff/issues/998))
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/)
- [`flake8-type-checking`](https://pypi.org/project/flake8-type-checking/)
- [`flake8-use-pathlib`](https://pypi.org/project/flake8-use-pathlib/)
- [`isort`](https://pypi.org/project/isort/)
- [`mccabe`](https://pypi.org/project/mccabe/)
- [`pandas-vet`](https://pypi.org/project/pandas-vet/)
@@ -1597,6 +1604,21 @@ Unlike Pylint, Ruff is capable of automatically fixing its own lint violations.
Pylint parity is being tracked in [#970](https://github.com/charliermarsh/ruff/issues/970).
### How does Ruff compare to Mypy, or Pyright, or Pyre?
Ruff is a linter, not a type checker. It can detect some of the same problems that a type checker
can, but a type checker will catch certain errors that Ruff would miss. The opposite is also true:
Ruff will catch certain errors that a type checker would typically ignore.
For example, unlike a type checker, Ruff will notify you if an import is unused, by looking for
references to that import in the source code; on the other hand, a type checker could flag that you
passed an integer argument to a function that expects a string, which Ruff would miss. The
tools are complementary.
It's recommended that you use Ruff in conjunction with a type checker, like Mypy, Pyright, or Pyre,
with Ruff providing faster feedback on lint violations and the type checker providing more detailed
feedback on type errors.
### Which tools does Ruff replace?
Today, Ruff can be used to replace Flake8 when used with any of the following plugins:
@@ -1618,8 +1640,9 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-executable`](https://pypi.org/project/flake8-executable/)
- [`flake8-implicit-str-concat`](https://pypi.org/project/flake8-implicit-str-concat/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-logging-format`](https://pypi.org/project/flake8-logging-format/)
- [`flake8-no-pep420`](https://pypi.org/project/flake8-no-pep420)
- [`flake8-pie`](https://pypi.org/project/flake8-pie/) ([#1543](https://github.com/charliermarsh/ruff/issues/1543))
- [`flake8-pie`](https://pypi.org/project/flake8-pie/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-pytest-style`](https://pypi.org/project/flake8-pytest-style/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
@@ -1627,6 +1650,8 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) ([#998](https://github.com/charliermarsh/ruff/issues/998))
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/)
- [`flake8-type-checking`](https://pypi.org/project/flake8-type-checking/)
- [`flake8-use-pathlib`](https://pypi.org/project/flake8-use-pathlib/)
- [`mccabe`](https://pypi.org/project/mccabe/)
- [`pandas-vet`](https://pypi.org/project/pandas-vet/)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
@@ -1733,6 +1758,46 @@ matter how they're provided, which avoids accidental incompatibilities and simpl
Run `ruff /path/to/code.py --show-settings` to view the resolved settings for a given file.
### I want to use Ruff, but I don't want to use `pyproject.toml`. Is that possible?
Yes! In lieu of a `pyproject.toml` file, you can use a `ruff.toml` file for configuration. The two
files are functionally equivalent and have an identical schema, with the exception that a `ruff.toml`
file can omit the `[tool.ruff]` section header.
For example, given this `pyproject.toml`:
```toml
[tool.ruff]
line-length = 88
[tool.ruff.pydocstyle]
convention = "google"
```
You could instead use a `ruff.toml` file like so:
```toml
line-length = 88
[pydocstyle]
convention = "google"
```
Ruff doesn't currently support INI files, like `setup.cfg` or `tox.ini`.
### How can I change Ruff's default configuration?
When no configuration file is found, Ruff will look for a user-specific `pyproject.toml` or
`ruff.toml` file as a last resort. This behavior is similar to Flake8's `~/.config/flake8`.
On macOS, Ruff expects that file to be located at `/Users/Alice/Library/Application Support/ruff/ruff.toml`.
On Linux, Ruff expects that file to be located at `/home/alice/.config/ruff/ruff.toml`.
On Windows, Ruff expects that file to be located at `C:\Users\Alice\AppData\Roaming\ruff\ruff.toml`.
For more, see the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate.
### Ruff tried to fix something, but it broke my code. What should I do?
Ruff's autofix is a best-effort mechanism. Given the dynamic nature of Python, it's difficult to
@@ -1751,6 +1816,7 @@ unfixable = ["B", "SIM", "TRY", "RUF"]
If you find a case where Ruff's autofix breaks your code, please file an Issue!
<!-- End section: FAQ -->
## Contributing
@@ -1888,7 +1954,9 @@ Benchmark 1: find . -type f -name "*.py" | xargs -P 0 pyupgrade --py311-plus
## Reference
### Options
<!-- Begin section: Settings -->
### Top-level
<!-- Sections automatically generated by `cargo dev generate-options`. -->
<!-- Begin auto-generated options sections. -->
@@ -2374,7 +2442,7 @@ Enabled by default.
```toml
[tool.ruff]
respect_gitignore = false
respect-gitignore = false
```
---
@@ -2439,7 +2507,7 @@ my_package/
bar.py
```
The `src` directory should be included in `source` (e.g., `source =
The `src` directory should be included in the `src` option (e.g., `src =
["src"]`), such that when resolving imports, `my_package.foo` is
considered a first-party import.
@@ -2645,6 +2713,24 @@ suppress-none-returning = true
### `flake8-bandit`
#### [`check-typed-exception`](#check-typed-exception)
Whether to disallow `try`-`except`-`pass` (`S110`) for specific exception types. By default,
`try`-`except`-`pass` is only disallowed for `Exception` and `BaseException`.
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-bandit]
check-typed-exception = true
```
---
#### [`hardcoded-tmp-directory`](#hardcoded-tmp-directory)
A list of directories to consider temporary.
@@ -3076,6 +3162,45 @@ and can be circumvented via `eval` or `importlib`.
---
### `flake8-type-checking`
#### [`exempt-modules`](#exempt-modules)
Exempt certain modules from needing to be moved into type-checking
blocks.
**Default value**: `["typing"]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.flake8-type-checking]
exempt-modules = ["typing", "typing_extensions"]
```
---
#### [`strict`](#strict)
Enforce TC001, TC002, and TC003 rules even when valid runtime imports
are present for the same module.
See: https://github.com/snok/flake8-type-checking#strict.
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-type-checking]
strict = true
```
---
### `flake8-unused-arguments`
#### [`ignore-variadic-names`](#ignore-variadic-names)
@@ -3592,6 +3717,8 @@ keep-runtime-typing = true
<!-- End auto-generated options sections. -->
<!-- End section: Settings -->
## License
MIT

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.235"
version = "0.0.237"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.235"
version = "0.0.237"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.235"
version = "0.0.237"
edition = "2021"
[dependencies]

View File

@@ -26,7 +26,7 @@ use ruff::flake8_to_ruff::{self, ExternalConfig};
about = "Convert existing Flake8 configuration to Ruff.",
long_about = None
)]
struct Cli {
struct Args {
/// Path to the Flake8 configuration file (e.g., `setup.cfg`, `tox.ini`, or
/// `.flake8`).
#[arg(required = true)]
@@ -41,15 +41,15 @@ struct Cli {
}
fn main() -> Result<()> {
let cli = Cli::parse();
let args = Args::parse();
// Read the INI file.
let mut ini = Ini::new_cs();
ini.set_multiline(true);
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
let config = ini.load(args.file).map_err(|msg| anyhow::anyhow!(msg))?;
// Read the pyproject.toml file.
let pyproject = cli.pyproject.map(flake8_to_ruff::parse).transpose()?;
let pyproject = args.pyproject.map(flake8_to_ruff::parse).transpose()?;
let external_config = pyproject
.as_ref()
.and_then(|pyproject| pyproject.tool.as_ref())
@@ -60,7 +60,7 @@ fn main() -> Result<()> {
.unwrap_or_default();
// Create Ruff's pyproject.toml section.
let pyproject = flake8_to_ruff::convert(&config, &external_config, cli.plugin)?;
let pyproject = flake8_to_ruff::convert(&config, &external_config, args.plugin)?;
println!("{}", toml::to_string_pretty(&pyproject)?);
Ok(())

49
mkdocs.template.yml Normal file
View File

@@ -0,0 +1,49 @@
site_name: Ruff
theme:
name: material
features:
- navigation.instant
- navigation.tracking
- content.code.annotate
- toc.integrate
- toc.follow
- navigation.path
- navigation.top
- content.code.copy
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
primary: red
toggle:
icon: material/weather-sunny
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: red
toggle:
icon: material/weather-night
name: Switch to light mode
repo_url: https://github.com/charliermarsh/ruff
repo_name: ruff
site_author: charliermarsh
site_url: https://beta.ruff.rs/docs/
site_dir: site/docs
markdown_extensions:
- toc:
permalink: "#"
- pymdownx.snippets:
- pymdownx.magiclink:
- attr_list:
- md_in_html:
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.inlinehilite:
- pymdownx.superfences:
- markdown.extensions.attr_list:
- pymdownx.keys:
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.highlight:
anchor_linenums: true
plugins:
- search

View File

@@ -7,7 +7,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.235"
version = "0.0.237"
description = "An extremely fast Python linter, written in Rust."
authors = [
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
@@ -15,6 +15,7 @@ authors = [
maintainers = [
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
]
readme = "README.md"
requires-python = ">=3.7"
license = { file = "LICENSE" }
keywords = ["automation", "flake8", "pycodestyle", "pyflakes", "pylint", "clippy"]

View File

@@ -39,3 +39,10 @@ class Foo:
# Error
def __init__(self, foo: int):
...
# Error used to be ok for a moment since the mere presence
# of a vararg falsely indicated that the function has a typed argument.
class Foo:
def __init__(self, *arg):
...

View File

@@ -0,0 +1,14 @@
try:
pass
except Exception:
pass
try:
pass
except:
pass
try:
pass
except ValueError:
pass

View File

@@ -61,3 +61,34 @@ except Exception as e:
raise bad
except BaseException:
pass
import logging
try:
pass
except Exception:
logging.error("...")
try:
pass
except Exception:
logging.error("...", exc_info=False)
try:
pass
except Exception:
logging.error("...", exc_info=None)
try:
pass
except Exception:
logging.exception("...")
try:
pass
except Exception:
logging.error("...", exc_info=True)

View File

@@ -1,7 +1,7 @@
"""
Should emit:
B009 - Line 18, 19, 20, 21, 22
B010 - Line 33, 34, 35, 36
B009 - Line 19, 20, 21, 22, 23, 24
B010 - Line 40, 41, 42, 43, 44, 45
"""
# Valid getattr usage
@@ -13,10 +13,12 @@ getattr(foo, bar, None)
getattr(foo, "123abc")
getattr(foo, r"123\abc")
getattr(foo, "except")
getattr(foo, "__123abc")
# Invalid usage
getattr(foo, "bar")
getattr(foo, "_123abc")
getattr(foo, "__123abc__")
getattr(foo, "abc123")
getattr(foo, r"abc123")
_ = lambda x: getattr(x, "bar")
@@ -27,6 +29,7 @@ if getattr(x, "bar"):
setattr(foo, bar, None)
setattr(foo, "bar{foo}".format(foo="a"), None)
setattr(foo, "123abc", None)
setattr(foo, "__123abc", None)
setattr(foo, r"123\abc", None)
setattr(foo, "except", None)
_ = lambda x: setattr(x, "bar", 1)
@@ -36,6 +39,7 @@ if setattr(x, "bar", 1):
# Invalid usage
setattr(foo, "bar", None)
setattr(foo, "_123abc", None)
setattr(foo, "__123abc__", None)
setattr(foo, "abc123", None)
setattr(foo, r"abc123", None)
setattr(foo.bar, r"baz", None)

View File

@@ -0,0 +1,3 @@
import logging
logging.info("Hello {}".format("World!"))

View File

@@ -0,0 +1,3 @@
import logging
logging.info("Hello %s" % "World!")

View File

@@ -0,0 +1,3 @@
import logging
logging.info("Hello" + " " + "World!")

View File

@@ -0,0 +1,4 @@
import logging
name = "world"
logging.info(f"Hello {name}")

View File

@@ -0,0 +1,3 @@
import logging
logging.warn("Hello World!")

View File

@@ -0,0 +1,8 @@
import logging
logging.info(
"Hello world!",
extra={
"name": "foobar",
},
)

View File

@@ -0,0 +1,8 @@
import logging
logging.info(
"Hello world!",
extra=dict(
name="foobar",
),
)

View File

@@ -0,0 +1,3 @@
import logging
logging.error('Hello World', exc_info=True)

View File

@@ -0,0 +1,3 @@
import logging
logging.exception('Hello World', exc_info=True)

View File

@@ -0,0 +1,7 @@
import argparse
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument("target_dir", type=Path)
args = parser.parse_args()
parser.error(f"Target directory {args.target_dir} does not exist")

View File

@@ -0,0 +1,8 @@
import logging
logging.info(
"Hello {world}!",
extra=dict(
world="World",
),
)

View File

@@ -0,0 +1,8 @@
import logging
logging.info(
"Hello {world}!",
extra=dict(
world="{}".format("World"),
),
)

View File

@@ -0,0 +1,3 @@
import logging
logging.info("Hello World!")

View File

@@ -0,0 +1,3 @@
import warnings
warnings.warn("Hello World!")

View File

@@ -1,12 +1,12 @@
class Foo:
"""buzz"""
pass # PIE790
pass
if foo:
"""foo"""
pass # PIE790
pass
def multi_statement() -> None:
@@ -18,28 +18,28 @@ if foo:
pass
else:
"""bar"""
pass # PIE790
pass
while True:
pass
else:
"""bar"""
pass # PIE790
pass
for _ in range(10):
pass
else:
"""bar"""
pass # PIE790
pass
async for _ in range(10):
pass
else:
"""bar"""
pass # PIE790
pass
def foo() -> None:
@@ -47,7 +47,7 @@ def foo() -> None:
buzz
"""
pass # PIE790
pass
async def foo():
@@ -55,14 +55,14 @@ async def foo():
buzz
"""
pass # PIE790
pass
try:
"""
buzz
"""
pass # PIE790
pass
except ValueError:
pass
@@ -71,29 +71,34 @@ try:
bar()
except ValueError:
"""bar"""
pass # PIE790
pass
for _ in range(10):
"""buzz"""
pass # PIE790
pass
async for _ in range(10):
"""buzz"""
pass # PIE790
pass
while cond:
"""buzz"""
pass # PIE790
pass
with bar:
"""buzz"""
pass # PIE790
pass
async with bar:
"""buzz"""
pass # PIE790
pass
def foo() -> None:
"""buzz"""
pass # bar
class Foo:

View File

@@ -1,15 +1,26 @@
# Errors
"yoda" == compare # SIM300
'yoda' == compare # SIM300
"yoda" == compare # SIM300
42 == age # SIM300
("a", "b") == compare # SIM300
"yoda" <= compare # SIM300
'yoda' < compare # SIM300
"yoda" < compare # SIM300
42 > age # SIM300
YODA == age # SIM300
YODA > age # SIM300
YODA >= age # SIM300
JediOrder.YODA == age # SIM300
# OK
compare == "yoda"
age == 42
compare == ("a", "b")
x == y
"yoda" == compare == 1
"yoda" == compare == someothervar
"yoda" == "yoda"
age == YODA
age < YODA
age <= YODA
YODA == YODA
age == JediOrder.YODA

View File

@@ -0,0 +1,16 @@
def f():
import pandas as pd
x: pd.DataFrame
def f():
import pandas.core.frame as pd
x: pd.DataFrame
def f():
import flask
x: flask

View File

@@ -0,0 +1,54 @@
def f():
# Even in strict mode, this shouldn't rase an error, since `pkg` is used at runtime,
# and implicitly imports `pkg.bar`.
import pkg
import pkg.bar
def test(value: pkg.bar.A):
return pkg.B()
def f():
# Even in strict mode, this shouldn't rase an error, since `pkg.bar` is used at
# runtime, and implicitly imports `pkg`.
import pkg
import pkg.bar
def test(value: pkg.A):
return pkg.bar.B()
def f():
# In un-strict mode, this shouldn't rase an error, since `pkg` is used at runtime.
import pkg
from pkg import A
def test(value: A):
return pkg.B()
def f():
# In un-strict mode, this shouldn't rase an error, since `pkg` is used at runtime.
from pkg import A, B
def test(value: A):
return B()
def f():
# Even in strict mode, this shouldn't rase an error, since `pkg.baz` is used at
# runtime, and implicitly imports `pkg.bar`.
import pkg.bar
import pkg.baz
def test(value: pkg.bar.A):
return pkg.baz.B()
def f():
# In un-strict mode, this _should_ rase an error, since `pkg` is used at runtime.
import pkg
from pkg.bar import A
def test(value: A):
return pkg.B()

View File

@@ -29,3 +29,4 @@ os.path.splitext(p)
with open(p) as fp:
fp.read()
open(p).close()
os.getcwdb(p)

View File

@@ -6,7 +6,7 @@ from typing import NewType
GLOBAL: str = "foo"
def f():
def assign():
global GLOBAL
GLOBAL = "bar"
lower = 0
@@ -18,4 +18,18 @@ def f():
MyObj2 = namedtuple("MyObj12", ["a", "b"])
T = TypeVar("T")
UserId = NewType('UserId', int)
UserId = NewType("UserId", int)
def aug_assign(rank, world_size):
global CURRENT_PORT
CURRENT_PORT += 1
if CURRENT_PORT > MAX_PORT:
CURRENT_PORT = START_PORT
def loop_assign():
global CURRENT_PORT
for CURRENT_PORT in range(5):
pass

View File

@@ -0,0 +1,6 @@
"""Test: explicit re-export of shadowed builtins."""
from concurrent.futures import (
CancelledError as CancelledError,
TimeoutError as TimeoutError,
)

View File

@@ -0,0 +1,11 @@
__all__ = (
None, # [invalid-all-object]
Fruit,
Worm,
)
class Fruit:
pass
class Worm:
pass

View File

@@ -0,0 +1,7 @@
__all__ = ("CONST") # [invalid-all-format]
__all__ = ["Hello"] + {"world"} # [invalid-all-format]
__all__ += {"world"} # [invalid-all-format]
__all__ = {"world"} + ["Hello"] # [invalid-all-format]

View File

@@ -2,31 +2,37 @@
# Errors.
###
def f():
global x
global X
def f():
global x
global X
print(x)
print(X)
###
# Non-errors.
###
def f():
global x
global X
x = 1
X = 1
def f():
global x
global X
(x, y) = (1, 2)
(X, y) = (1, 2)
def f():
global x
global X
del x
del X
def f():
global X
X += 1

View File

@@ -0,0 +1,31 @@
"""
Violation:
Raising vanilla exception with custom message means it should be customized.
"""
from somewhere import exceptions
def func():
a = 1
if a == 1:
raise Exception("Custom message")
b = 1
if b == 1:
raise Exception
def ignore():
try:
a = 1
except Exception as ex:
# This is another violation, but this specific analyzer shouldn't care
raise ex
def anotherfunc():
a = 1
if a == 1:
raise exceptions.Exception("Another except") # That's fine

View File

@@ -0,0 +1,54 @@
class CustomException(Exception):
pass
def func():
a = 1
if a == 1:
raise CustomException("Long message")
elif a == 2:
raise CustomException("Short") # This is acceptable
elif a == 3:
raise CustomException("its_code_not_message") # This is acceptable
def ignore():
try:
a = 1
except Exception as ex:
# This is another violation, but this specific analyzer shouldn't care
raise ex
class BadArgCantBeEven(Exception):
pass
class GoodArgCantBeEven(Exception):
def __init__(self, arg):
super().__init__(f"The argument '{arg}' should be even")
def bad(a):
if a % 2 == 0:
raise BadArgCantBeEven(f"The argument '{a}' should be even")
def another_bad(a):
if a % 2 == 0:
raise BadArgCantBeEven(f"The argument {a} should not be odd.")
def and_another_bad(a):
if a % 2 == 0:
raise BadArgCantBeEven("The argument `a` should not be odd.")
def good(a: int):
if a % 2 == 0:
raise GoodArgCantBeEven(a)
def another_good(a):
if a % 2 == 0:
raise GoodArgCantBeEven(a)

View File

@@ -0,0 +1,62 @@
"""
Violation:
Use '.exception' over '.error' inside except blocks
"""
import logging
logger = logging.getLogger(__name__)
def bad():
try:
a = 1
except Exception:
logging.error("Context message here")
if True:
logging.error("Context message here")
def bad():
try:
a = 1
except Exception:
logger.error("Context message here")
if True:
logger.error("Context message here")
def bad():
try:
a = 1
except Exception:
log.error("Context message here")
if True:
log.error("Context message here")
def bad():
try:
a = 1
except Exception:
self.logger.error("Context message here")
if True:
self.logger.error("Context message here")
def good():
try:
a = 1
except Exception:
logger.exception("Context message here")
def good():
try:
a = 1
except Exception:
foo.exception("Context message here")

View File

@@ -230,6 +230,17 @@
}
]
},
"flake8-type-checking": {
"description": "Options for the `flake8-type-checking` plugin.",
"anyOf": [
{
"$ref": "#/definitions/Flake8TypeCheckingOptions"
},
{
"type": "null"
}
]
},
"flake8-unused-arguments": {
"description": "Options for the `flake8-unused-arguments` plugin.",
"anyOf": [
@@ -421,7 +432,7 @@
]
},
"src": {
"description": "The source code paths to consider, e.g., when resolving first- vs. third-party imports.\n\nAs an example: given a Python package structure like:\n\n```text my_package/ pyproject.toml src/ my_package/ __init__.py foo.py bar.py ```\n\nThe `src` directory should be included in `source` (e.g., `source = [\"src\"]`), such that when resolving imports, `my_package.foo` is considered a first-party import.\n\nThis field supports globs. For example, if you have a series of Python packages in a `python_modules` directory, `src = [\"python_modules/*\"]` would expand to incorporate all of the packages in that directory. User home directory and environment variables will also be expanded.",
"description": "The source code paths to consider, e.g., when resolving first- vs. third-party imports.\n\nAs an example: given a Python package structure like:\n\n```text my_package/ pyproject.toml src/ my_package/ __init__.py foo.py bar.py ```\n\nThe `src` directory should be included in the `src` option (e.g., `src = [\"src\"]`), such that when resolving imports, `my_package.foo` is considered a first-party import.\n\nThis field supports globs. For example, if you have a series of Python packages in a `python_modules` directory, `src = [\"python_modules/*\"]` would expand to incorporate all of the packages in that directory. User home directory and environment variables will also be expanded.",
"type": [
"array",
"null"
@@ -567,6 +578,13 @@
"Flake8BanditOptions": {
"type": "object",
"properties": {
"check-typed-exception": {
"description": "Whether to disallow `try`-`except`-`pass` (`S110`) for specific exception types. By default, `try`-`except`-`pass` is only disallowed for `Exception` and `BaseException`.",
"type": [
"boolean",
"null"
]
},
"hardcoded-tmp-directory": {
"description": "A list of directories to consider temporary.",
"type": [
@@ -822,6 +840,29 @@
},
"additionalProperties": false
},
"Flake8TypeCheckingOptions": {
"type": "object",
"properties": {
"exempt-modules": {
"description": "Exempt certain modules from needing to be moved into type-checking blocks.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"strict": {
"description": "Enforce TC001, TC002, and TC003 rules even when valid runtime imports are present for the same module. See: https://github.com/snok/flake8-type-checking#strict.",
"type": [
"boolean",
"null"
]
}
},
"additionalProperties": false
},
"Flake8UnusedArgumentsOptions": {
"type": "object",
"properties": {
@@ -947,7 +988,7 @@
"description": "Whether to place \"closer\" imports (fewer `.` characters, most local) before \"further\" imports (more `.` characters, least local), or vice versa.\n\nThe default (\"furthest-to-closest\") is equivalent to isort's `reverse-relative` default (`reverse-relative = false`); setting this to \"closest-to-furthest\" is equivalent to isort's `reverse-relative = true`.",
"anyOf": [
{
"$ref": "#/definitions/RelatveImportsOrder"
"$ref": "#/definitions/RelativeImportsOrder"
},
{
"type": "null"
@@ -1163,7 +1204,7 @@
}
]
},
"RelatveImportsOrder": {
"RelativeImportsOrder": {
"oneOf": [
{
"description": "Place \"closer\" imports (fewer `.` characters, most local) before \"further\" imports (more `.` characters, least local).",
@@ -1475,6 +1516,22 @@
"FBT001",
"FBT002",
"FBT003",
"G",
"G0",
"G00",
"G001",
"G002",
"G003",
"G004",
"G01",
"G010",
"G1",
"G10",
"G101",
"G2",
"G20",
"G201",
"G202",
"I",
"I0",
"I00",
@@ -1565,6 +1622,10 @@
"PLE011",
"PLE0117",
"PLE0118",
"PLE06",
"PLE060",
"PLE0604",
"PLE0605",
"PLE1",
"PLE11",
"PLE114",
@@ -1699,6 +1760,7 @@
"S107",
"S108",
"S11",
"S110",
"S113",
"S3",
"S32",
@@ -1776,6 +1838,8 @@
"TRY",
"TRY0",
"TRY00",
"TRY002",
"TRY003",
"TRY004",
"TRY2",
"TRY20",
@@ -1785,6 +1849,9 @@
"TRY30",
"TRY300",
"TRY301",
"TRY4",
"TRY40",
"TRY400",
"UP",
"UP0",
"UP00",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.0.235"
version = "0.0.237"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"

View File

@@ -15,135 +15,206 @@ use rustc_hash::FxHashMap;
#[command(
author,
name = "ruff",
about = "Ruff: An extremely fast Python linter."
about = "Ruff: An extremely fast Python linter.",
after_help = "For help with a specific command, see: `ruff help <command>`."
)]
#[command(version)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cli {
#[arg(required_unless_present_any = ["clean", "explain", "generate_shell_completion"])]
pub struct Args {
#[command(subcommand)]
pub command: Command,
#[clap(flatten)]
pub log_level_args: LogLevelArgs,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, clap::Subcommand)]
pub enum Command {
/// Run Ruff on the given files or directories (default).
Check(CheckArgs),
/// Explain a rule.
#[clap(alias = "--explain")]
Rule {
#[arg(value_parser=Rule::from_code)]
rule: &'static Rule,
/// Output format
#[arg(long, value_enum, default_value = "text")]
format: HelpFormat,
},
/// Clear any caches in the current directory and any subdirectories.
#[clap(alias = "--clean")]
Clean,
/// Generate shell completion.
#[clap(alias = "--generate-shell-completion", hide = true)]
GenerateShellCompletion { shell: clap_complete_command::Shell },
}
#[derive(Debug, clap::Args)]
#[allow(clippy::struct_excessive_bools, clippy::module_name_repetitions)]
pub struct CheckArgs {
/// List of files or directories to check.
pub files: Vec<PathBuf>,
/// Path to the `pyproject.toml` or `ruff.toml` file to use for
/// configuration.
#[arg(long, conflicts_with = "isolated")]
pub config: Option<PathBuf>,
/// Enable verbose logging.
#[arg(short, long, group = "verbosity")]
pub verbose: bool,
/// Print lint violations, but nothing else.
#[arg(short, long, group = "verbosity")]
pub quiet: bool,
/// Disable all logging (but still exit with status code "1" upon detecting
/// lint violations).
#[arg(short, long, group = "verbosity")]
pub silent: bool,
/// Exit with status code "0", even upon detecting lint violations.
#[arg(short, long)]
pub exit_zero: bool,
/// Run in watch mode by re-running whenever files change.
#[arg(short, long)]
pub watch: bool,
/// Attempt to automatically fix lint violations.
#[arg(long, overrides_with("no_fix"))]
fix: bool,
#[clap(long, overrides_with("fix"), hide = true)]
no_fix: bool,
/// Show violations with source code.
#[arg(long, overrides_with("no_show_source"))]
show_source: bool,
#[clap(long, overrides_with("show_source"), hide = true)]
no_show_source: bool,
/// Avoid writing any fixed files back; instead, output a diff for each
/// changed file to stdout.
#[arg(long)]
pub diff: bool,
/// Run in watch mode by re-running whenever files change.
#[arg(short, long)]
pub watch: bool,
/// Fix any fixable lint violations, but don't report on leftover
/// violations. Implies `--fix`.
#[arg(long, overrides_with("no_fix_only"))]
fix_only: bool,
#[clap(long, overrides_with("fix_only"), hide = true)]
no_fix_only: bool,
/// Avoid writing any fixed files back; instead, output a diff for each
/// changed file to stdout.
#[arg(long)]
pub diff: bool,
/// Disable cache reads.
#[arg(short, long)]
pub no_cache: bool,
/// Ignore all configuration files.
#[arg(long, conflicts_with = "config")]
pub isolated: bool,
/// Comma-separated list of rule codes to enable (or ALL, to enable all
/// rules).
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub select: Option<Vec<RuleSelector>>,
/// Like --select, but adds additional rule codes on top of the selected
/// ones.
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub extend_select: Option<Vec<RuleSelector>>,
/// Comma-separated list of rule codes to disable.
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub ignore: Option<Vec<RuleSelector>>,
/// Like --ignore, but adds additional rule codes on top of the ignored
/// ones.
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub extend_ignore: Option<Vec<RuleSelector>>,
/// List of paths, used to omit files and/or directories from analysis.
#[arg(long, value_delimiter = ',', value_name = "FILE_PATTERN")]
pub exclude: Option<Vec<FilePattern>>,
/// Like --exclude, but adds additional files and directories on top of
/// those already excluded.
#[arg(long, value_delimiter = ',', value_name = "FILE_PATTERN")]
pub extend_exclude: Option<Vec<FilePattern>>,
/// List of rule codes to treat as eligible for autofix. Only applicable
/// when autofix itself is enabled (e.g., via `--fix`).
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub fixable: Option<Vec<RuleSelector>>,
/// List of rule codes to treat as ineligible for autofix. Only applicable
/// when autofix itself is enabled (e.g., via `--fix`).
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub unfixable: Option<Vec<RuleSelector>>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')]
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
/// Output serialization format for violations.
#[arg(long, value_enum, env = "RUFF_FORMAT")]
pub format: Option<SerializationFormat>,
/// The name of the file when passing it through stdin.
#[arg(long)]
pub stdin_filename: Option<PathBuf>,
/// Path to the cache directory.
#[arg(long, env = "RUFF_CACHE_DIR")]
pub cache_dir: Option<PathBuf>,
/// Show violations with source code.
#[arg(long, overrides_with("no_show_source"))]
show_source: bool,
#[clap(long, overrides_with("show_source"), hide = true)]
no_show_source: bool,
/// Path to the `pyproject.toml` or `ruff.toml` file to use for
/// configuration.
#[arg(long, conflicts_with = "isolated")]
pub config: Option<PathBuf>,
/// Comma-separated list of rule codes to enable (or ALL, to enable all
/// rules).
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
help_heading = "Rule selection"
)]
pub select: Option<Vec<RuleSelector>>,
/// Comma-separated list of rule codes to disable.
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
help_heading = "Rule selection"
)]
pub ignore: Option<Vec<RuleSelector>>,
/// Like --select, but adds additional rule codes on top of the selected
/// ones.
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
help_heading = "Rule selection"
)]
pub extend_select: Option<Vec<RuleSelector>>,
/// Like --ignore, but adds additional rule codes on top of the ignored
/// ones.
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
help_heading = "Rule selection"
)]
pub extend_ignore: Option<Vec<RuleSelector>>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',', help_heading = "Rule selection")]
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
/// List of paths, used to omit files and/or directories from analysis.
#[arg(
long,
value_delimiter = ',',
value_name = "FILE_PATTERN",
help_heading = "File selection"
)]
pub exclude: Option<Vec<FilePattern>>,
/// Like --exclude, but adds additional files and directories on top of
/// those already excluded.
#[arg(
long,
value_delimiter = ',',
value_name = "FILE_PATTERN",
help_heading = "File selection"
)]
pub extend_exclude: Option<Vec<FilePattern>>,
/// List of rule codes to treat as eligible for autofix. Only applicable
/// when autofix itself is enabled (e.g., via `--fix`).
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
help_heading = "Rule selection"
)]
pub fixable: Option<Vec<RuleSelector>>,
/// List of rule codes to treat as ineligible for autofix. Only applicable
/// when autofix itself is enabled (e.g., via `--fix`).
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
help_heading = "Rule selection"
)]
pub unfixable: Option<Vec<RuleSelector>>,
/// Respect file exclusions via `.gitignore` and other standard ignore
/// files.
#[arg(long, overrides_with("no_respect_gitignore"))]
#[arg(
long,
overrides_with("no_respect_gitignore"),
help_heading = "File selection"
)]
respect_gitignore: bool,
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
no_respect_gitignore: bool,
/// Enforce exclusions, even for paths passed to Ruff directly on the
/// command-line.
#[arg(long, overrides_with("no_force_exclude"))]
#[arg(
long,
overrides_with("no_force_exclude"),
help_heading = "File selection"
)]
force_exclude: bool,
#[clap(long, overrides_with("force_exclude"), hide = true)]
no_force_exclude: bool,
/// Enable or disable automatic update checks.
#[arg(long, overrides_with("no_update_check"))]
update_check: bool,
#[clap(long, overrides_with("update_check"), hide = true)]
no_update_check: bool,
/// Regular expression matching the name of dummy variables.
#[arg(long)]
pub dummy_variable_rgx: Option<Regex>,
/// The minimum Python version that should be supported.
#[arg(long)]
#[arg(long, help_heading = "Rule configuration")]
pub target_version: Option<PythonVersion>,
/// Set the line-length for length-associated rules and automatic
/// formatting.
#[arg(long)]
#[arg(long, help_heading = "Rule configuration")]
pub line_length: Option<usize>,
/// Regular expression matching the name of dummy variables.
#[arg(long, help_heading = "Rule configuration")]
pub dummy_variable_rgx: Option<Regex>,
/// Disable cache reads.
#[arg(short, long, help_heading = "Miscellaneous")]
pub no_cache: bool,
/// Ignore all configuration files.
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
pub isolated: bool,
/// Path to the cache directory.
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
pub cache_dir: Option<PathBuf>,
/// The name of the file when passing it through stdin.
#[arg(long, help_heading = "Miscellaneous")]
pub stdin_filename: Option<PathBuf>,
/// Exit with status code "0", even upon detecting lint violations.
#[arg(short, long, help_heading = "Miscellaneous")]
pub exit_zero: bool,
/// Enable or disable automatic update checks.
#[arg(
long,
overrides_with("no_update_check"),
help_heading = "Miscellaneous"
)]
update_check: bool,
#[clap(long, overrides_with("update_check"), hide = true)]
no_update_check: bool,
/// Enable automatic additions of `noqa` directives to failing lines.
#[arg(
long,
// conflicts_with = "add_noqa",
conflicts_with = "clean",
conflicts_with = "explain",
conflicts_with = "generate_shell_completion",
conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
@@ -151,62 +222,11 @@ pub struct Cli {
conflicts_with = "watch",
)]
pub add_noqa: bool,
/// Clear any caches in the current directory or any subdirectories.
#[arg(
long,
// Fake subcommands.
conflicts_with = "add_noqa",
// conflicts_with = "clean",
conflicts_with = "explain",
conflicts_with = "generate_shell_completion",
conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
pub clean: bool,
/// Explain a rule.
#[arg(
long,
value_parser=Rule::from_code,
// Fake subcommands.
conflicts_with = "add_noqa",
conflicts_with = "clean",
// conflicts_with = "explain",
conflicts_with = "generate_shell_completion",
conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
pub explain: Option<&'static Rule>,
/// Generate shell completion
#[arg(
long,
hide = true,
value_name = "SHELL",
// Fake subcommands.
conflicts_with = "add_noqa",
conflicts_with = "clean",
conflicts_with = "explain",
// conflicts_with = "generate_shell_completion",
conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
pub generate_shell_completion: Option<clap_complete_command::Shell>,
/// See the files Ruff will be run against with the current settings.
#[arg(
long,
// Fake subcommands.
conflicts_with = "add_noqa",
conflicts_with = "clean",
conflicts_with = "explain",
conflicts_with = "generate_shell_completion",
// conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
@@ -219,9 +239,6 @@ pub struct Cli {
long,
// Fake subcommands.
conflicts_with = "add_noqa",
conflicts_with = "clean",
conflicts_with = "explain",
conflicts_with = "generate_shell_completion",
conflicts_with = "show_files",
// conflicts_with = "show_settings",
// Unsupported default-command arguments.
@@ -231,28 +248,75 @@ pub struct Cli {
pub show_settings: bool,
}
impl Cli {
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum HelpFormat {
Text,
Json,
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, clap::Args)]
pub struct LogLevelArgs {
/// Enable verbose logging.
#[arg(
short,
long,
global = true,
group = "verbosity",
help_heading = "Log levels"
)]
pub verbose: bool,
/// Print lint violations, but nothing else.
#[arg(
short,
long,
global = true,
group = "verbosity",
help_heading = "Log levels"
)]
pub quiet: bool,
/// Disable all logging (but still exit with status code "1" upon detecting
/// lint violations).
#[arg(
short,
long,
global = true,
group = "verbosity",
help_heading = "Log levels"
)]
pub silent: bool,
}
impl From<&LogLevelArgs> for LogLevel {
fn from(args: &LogLevelArgs) -> Self {
if args.silent {
LogLevel::Silent
} else if args.quiet {
LogLevel::Quiet
} else if args.verbose {
LogLevel::Verbose
} else {
LogLevel::Default
}
}
}
impl CheckArgs {
/// Partition the CLI into command-line arguments and configuration
/// overrides.
pub fn partition(self) -> (Arguments, Overrides) {
(
Arguments {
add_noqa: self.add_noqa,
clean: self.clean,
config: self.config,
diff: self.diff,
exit_zero: self.exit_zero,
explain: self.explain,
files: self.files,
generate_shell_completion: self.generate_shell_completion,
isolated: self.isolated,
no_cache: self.no_cache,
quiet: self.quiet,
show_files: self.show_files,
show_settings: self.show_settings,
silent: self.silent,
stdin_filename: self.stdin_filename,
verbose: self.verbose,
watch: self.watch,
},
Overrides {
@@ -299,21 +363,15 @@ fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
#[allow(clippy::struct_excessive_bools)]
pub struct Arguments {
pub add_noqa: bool,
pub clean: bool,
pub config: Option<PathBuf>,
pub diff: bool,
pub exit_zero: bool,
pub explain: Option<&'static Rule>,
pub files: Vec<PathBuf>,
pub generate_shell_completion: Option<clap_complete_command::Shell>,
pub isolated: bool,
pub no_cache: bool,
pub quiet: bool,
pub show_files: bool,
pub show_settings: bool,
pub silent: bool,
pub stdin_filename: Option<PathBuf>,
pub verbose: bool,
pub watch: bool,
}
@@ -420,19 +478,6 @@ impl ConfigProcessor for &Overrides {
}
}
/// Map the CLI settings to a `LogLevel`.
pub fn extract_log_level(cli: &Arguments) -> LogLevel {
if cli.silent {
LogLevel::Silent
} else if cli.quiet {
LogLevel::Quiet
} else if cli.verbose {
LogLevel::Verbose
} else {
LogLevel::Default
}
}
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
pub fn collect_per_file_ignores(pairs: Vec<PatternPrefixPair>) -> Vec<PerFileIgnore> {
let mut per_file_ignores: FxHashMap<String, Vec<RuleSelector>> = FxHashMap::default();

View File

@@ -16,15 +16,14 @@ use ruff::linter::add_noqa_to_path;
use ruff::logging::LogLevel;
use ruff::message::{Location, Message};
use ruff::registry::{Linter, Rule, RuleNamespace};
use ruff::resolver::{FileDiscovery, PyprojectDiscovery};
use ruff::resolver::PyprojectDiscovery;
use ruff::settings::flags;
use ruff::settings::types::SerializationFormat;
use ruff::{fix, fs, packaging, resolver, warn_user_once, AutofixAvailability, IOError};
use serde::Serialize;
use walkdir::WalkDir;
use crate::args::{HelpFormat, Overrides};
use crate::cache;
use crate::cli::Overrides;
use crate::diagnostics::{lint_path, lint_stdin, Diagnostics};
use crate::iterators::par_iter;
@@ -32,15 +31,13 @@ use crate::iterators::par_iter;
pub fn run(
files: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery,
file_strategy: &FileDiscovery,
overrides: &Overrides,
cache: flags::Cache,
autofix: fix::FixMode,
) -> Result<Diagnostics> {
// Collect all the Python files to check.
let start = Instant::now();
let (paths, resolver) =
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?;
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);
@@ -49,9 +46,6 @@ pub fn run(
return Ok(Diagnostics::default());
}
// Validate the `Settings` and return any errors.
resolver.validate(pyproject_strategy)?;
// Initialize the cache.
if matches!(cache, flags::Cache::Enabled) {
match &pyproject_strategy {
@@ -156,12 +150,11 @@ fn read_from_stdin() -> Result<String> {
pub fn run_stdin(
filename: Option<&Path>,
pyproject_strategy: &PyprojectDiscovery,
file_strategy: &FileDiscovery,
overrides: &Overrides,
autofix: fix::FixMode,
) -> Result<Diagnostics> {
if let Some(filename) = filename {
if !resolver::python_file_at_path(filename, pyproject_strategy, file_strategy, overrides)? {
if !resolver::python_file_at_path(filename, pyproject_strategy, overrides)? {
return Ok(Diagnostics::default());
}
}
@@ -182,13 +175,11 @@ pub fn run_stdin(
pub fn add_noqa(
files: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery,
file_strategy: &FileDiscovery,
overrides: &Overrides,
) -> Result<usize> {
// Collect all the files to check.
let start = Instant::now();
let (paths, resolver) =
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?;
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);
@@ -197,9 +188,6 @@ pub fn add_noqa(
return Ok(0);
}
// Validate the `Settings` and return any errors.
resolver.validate(pyproject_strategy)?;
let start = Instant::now();
let modifications: usize = par_iter(&paths)
.flatten()
@@ -226,15 +214,10 @@ pub fn add_noqa(
pub fn show_settings(
files: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery,
file_strategy: &FileDiscovery,
overrides: &Overrides,
) -> Result<()> {
// Collect all files in the hierarchy.
let (paths, resolver) =
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
// Validate the `Settings` and return any errors.
resolver.validate(pyproject_strategy)?;
let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?;
// Print the list of files.
let Some(entry) = paths
@@ -255,21 +238,16 @@ pub fn show_settings(
pub fn show_files(
files: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery,
file_strategy: &FileDiscovery,
overrides: &Overrides,
) -> Result<()> {
// Collect all files in the hierarchy.
let (paths, resolver) =
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
let (paths, _resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?;
if paths.is_empty() {
warn_user_once!("No Python files found under the given path(s)");
return Ok(());
}
// Validate the `Settings` and return any errors.
resolver.validate(pyproject_strategy)?;
// Print the list of files.
for entry in paths
.iter()
@@ -290,10 +268,10 @@ struct Explanation<'a> {
}
/// Explain a `Rule` to the user.
pub fn explain(rule: &Rule, format: SerializationFormat) -> Result<()> {
pub fn rule(rule: &Rule, format: HelpFormat) -> Result<()> {
let (linter, _) = Linter::parse_code(rule.code()).unwrap();
match format {
SerializationFormat::Text | SerializationFormat::Grouped => {
HelpFormat::Text => {
println!("{}\n", rule.as_ref());
println!("Code: {} ({})\n", rule.code(), linter.name());
@@ -311,7 +289,7 @@ pub fn explain(rule: &Rule, format: SerializationFormat) -> Result<()> {
println!("* {format}");
}
}
SerializationFormat::Json => {
HelpFormat::Json => {
println!(
"{}",
serde_json::to_string_pretty(&Explanation {
@@ -321,24 +299,12 @@ pub fn explain(rule: &Rule, format: SerializationFormat) -> Result<()> {
})?
);
}
SerializationFormat::Junit => {
bail!("`--explain` does not support junit format")
}
SerializationFormat::Github => {
bail!("`--explain` does not support GitHub format")
}
SerializationFormat::Gitlab => {
bail!("`--explain` does not support GitLab format")
}
SerializationFormat::Pylint => {
bail!("`--explain` does not support pylint format")
}
};
Ok(())
}
/// Clear any caches in the current directory or any subdirectories.
pub fn clean(level: &LogLevel) -> Result<()> {
pub fn clean(level: LogLevel) -> Result<()> {
for entry in WalkDir::new(&*path_dedot::CWD)
.into_iter()
.filter_map(Result::ok)
@@ -346,7 +312,7 @@ pub fn clean(level: &LogLevel) -> Result<()> {
{
let cache = entry.path().join(CACHE_DIR_NAME);
if cache.is_dir() {
if level >= &LogLevel::Default {
if level >= LogLevel::Default {
eprintln!("Removing cache at: {}", fs::relativize_path(&cache).bold());
}
remove_dir_all(&cache)?;

View File

@@ -42,9 +42,6 @@ pub fn lint_path(
cache: flags::Cache,
autofix: fix::FixMode,
) -> Result<Diagnostics> {
// Validate the `Settings` and return any errors.
settings.lib.validate()?;
// Check the cache.
// TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have
// side-effects that aren't captured in the cache. (In practice, it's fine
@@ -116,9 +113,6 @@ pub fn lint_stdin(
settings: &Settings,
autofix: fix::FixMode,
) -> Result<Diagnostics> {
// Validate the `Settings` and return any errors.
settings.validate()?;
// Lint the inputs.
let (messages, fixed) = if matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff) {
let (transformed, fixed, messages) = lint_fix(

View File

@@ -6,11 +6,11 @@
#![warn(clippy::pedantic)]
#![allow(clippy::must_use_candidate, dead_code)]
mod cli;
mod args;
use clap::CommandFactory;
/// Returns the output of `ruff --help`.
pub fn help() -> String {
cli::Cli::command().render_help().to_string()
args::Args::command().render_help().to_string()
}

View File

@@ -8,90 +8,55 @@
)]
use std::io::{self};
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::process::ExitCode;
use std::sync::mpsc::channel;
use ::ruff::logging::{set_up_logging, LogLevel};
use ::ruff::resolver::{
resolve_settings_with_processor, ConfigProcessor, FileDiscovery, PyprojectDiscovery, Relativity,
};
use ::ruff::settings::configuration::Configuration;
use ::ruff::settings::pyproject;
use ::ruff::resolver::PyprojectDiscovery;
use ::ruff::settings::types::SerializationFormat;
use ::ruff::{fix, fs, warn_user_once};
use anyhow::Result;
use clap::{CommandFactory, Parser};
use cli::{extract_log_level, Cli, Overrides};
use args::{Args, CheckArgs, Command};
use clap::{CommandFactory, Parser, Subcommand};
use colored::Colorize;
use notify::{recommended_watcher, RecursiveMode, Watcher};
use path_absolutize::path_dedot;
use printer::{Printer, Violations};
use ruff::settings::{AllSettings, CliSettings};
use ruff::settings::CliSettings;
pub(crate) mod args;
mod cache;
mod cli;
mod commands;
mod diagnostics;
mod iterators;
mod printer;
mod resolve;
#[cfg(all(feature = "update-informer"))]
pub mod updates;
/// Resolve the relevant settings strategy and defaults for the current
/// invocation.
fn resolve(
isolated: bool,
config: Option<&Path>,
overrides: &Overrides,
stdin_filename: Option<&Path>,
) -> Result<PyprojectDiscovery> {
if isolated {
// First priority: if we're running in isolated mode, use the default settings.
let mut config = Configuration::default();
overrides.process_config(&mut config);
let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?;
Ok(PyprojectDiscovery::Fixed(settings))
} else if let Some(pyproject) = config {
// Second priority: the user specified a `pyproject.toml` file. Use that
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
// current working directory. (This matches ESLint's behavior.)
let settings = resolve_settings_with_processor(pyproject, &Relativity::Cwd, overrides)?;
Ok(PyprojectDiscovery::Fixed(settings))
} else if let Some(pyproject) = pyproject::find_settings_toml(
stdin_filename
.as_ref()
.unwrap_or(&path_dedot::CWD.as_path()),
)? {
// Third priority: find a `pyproject.toml` file in either an ancestor of
// `stdin_filename` (if set) or the current working path all paths relative to
// that directory. (With `Strategy::Hierarchical`, we'll end up finding
// the "closest" `pyproject.toml` file for every Python file later on,
// so these act as the "default" settings.)
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Parent, overrides)?;
Ok(PyprojectDiscovery::Hierarchical(settings))
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
// Fourth priority: find a user-specific `pyproject.toml`, but resolve all paths
// relative the current working directory. (With `Strategy::Hierarchical`, we'll
// end up the "closest" `pyproject.toml` file for every Python file later on, so
// these act as the "default" settings.)
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Cwd, overrides)?;
Ok(PyprojectDiscovery::Hierarchical(settings))
} else {
// Fallback: load Ruff's default settings, and resolve all paths relative to the
// current working directory. (With `Strategy::Hierarchical`, we'll end up the
// "closest" `pyproject.toml` file for every Python file later on, so these act
// as the "default" settings.)
let mut config = Configuration::default();
overrides.process_config(&mut config);
let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?;
Ok(PyprojectDiscovery::Hierarchical(settings))
}
}
fn inner_main() -> Result<ExitCode> {
let mut args: Vec<_> = std::env::args_os().collect();
// Clap doesn't support default subcommands but we want to run `check` by
// default for convenience and backwards-compatibility, so we just
// preprocess the arguments accordingly before passing them to Clap.
if let Some(arg) = args.get(1).and_then(|s| s.to_str()) {
if !Command::has_subcommand(rewrite_legacy_subcommand(arg))
&& arg != "-h"
&& arg != "--help"
&& arg != "-V"
&& arg != "--version"
&& arg != "help"
{
args.insert(1, "check".into());
}
}
// Extract command-line arguments.
let (cli, overrides) = Cli::parse().partition();
let Args {
command,
log_level_args,
} = Args::parse_from(args);
let default_panic_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
@@ -108,45 +73,44 @@ quoting the executed command, along with the relevant file contents and `pyproje
default_panic_hook(info);
}));
let log_level = extract_log_level(&cli);
let log_level: LogLevel = (&log_level_args).into();
set_up_logging(&log_level)?;
if let Some(shell) = cli.generate_shell_completion {
shell.generate(&mut Cli::command(), &mut io::stdout());
return Ok(ExitCode::SUCCESS);
}
if cli.clean {
commands::clean(&log_level)?;
return Ok(ExitCode::SUCCESS);
match command {
Command::Rule { rule, format } => commands::rule(rule, format)?,
Command::Clean => commands::clean(log_level)?,
Command::GenerateShellCompletion { shell } => {
shell.generate(&mut Args::command(), &mut io::stdout());
}
Command::Check(args) => return check(args, log_level),
}
Ok(ExitCode::SUCCESS)
}
fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitCode> {
let (cli, overrides) = args.partition();
// Construct the "default" settings. These are used when no `pyproject.toml`
// files are present, or files are injected from outside of the hierarchy.
let pyproject_strategy = resolve(
let pyproject_strategy = resolve::resolve(
cli.isolated,
cli.config.as_deref(),
&overrides,
cli.stdin_filename.as_deref(),
)?;
// Validate the `Settings` and return any errors.
match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings.lib.validate()?,
PyprojectDiscovery::Hierarchical(settings) => settings.lib.validate()?,
};
if cli.show_settings {
commands::show_settings(&cli.files, &pyproject_strategy, &overrides)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_files {
commands::show_files(&cli.files, &pyproject_strategy, &overrides)?;
}
// Extract options that are included in `Settings`, but only apply at the top
// level.
let file_strategy = FileDiscovery {
force_exclude: match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings.lib.force_exclude,
PyprojectDiscovery::Hierarchical(settings) => settings.lib.force_exclude,
},
respect_gitignore: match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings.lib.respect_gitignore,
PyprojectDiscovery::Hierarchical(settings) => settings.lib.respect_gitignore,
},
};
let CliSettings {
fix,
fix_only,
@@ -158,19 +122,6 @@ quoting the executed command, along with the relevant file contents and `pyproje
PyprojectDiscovery::Hierarchical(settings) => settings.cli.clone(),
};
if let Some(rule) = cli.explain {
commands::explain(rule, format)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_settings {
commands::show_settings(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_files {
commands::show_files(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
return Ok(ExitCode::SUCCESS);
}
// Autofix rules are as follows:
// - If `--fix` or `--fix-only` is set, always apply fixes to the filesystem (or
// print them to stdout, if we're reading from stdin).
@@ -204,7 +155,16 @@ quoting the executed command, along with the relevant file contents and `pyproje
warn_user_once!("Detected debug build without --no-cache.");
}
if cli.add_noqa {
let modifications = commands::add_noqa(&cli.files, &pyproject_strategy, &overrides)?;
if modifications > 0 && log_level >= LogLevel::Default {
println!("Added {modifications} noqa directives.");
}
return Ok(ExitCode::SUCCESS);
}
let printer = Printer::new(&format, &log_level, &autofix, &violations);
if cli.watch {
if !matches!(autofix, fix::FixMode::None) {
warn_user_once!("--fix is not enabled in watch mode.");
@@ -220,7 +180,6 @@ quoting the executed command, along with the relevant file contents and `pyproje
let messages = commands::run(
&cli.files,
&pyproject_strategy,
&file_strategy,
&overrides,
cache.into(),
fix::FixMode::None,
@@ -250,7 +209,6 @@ quoting the executed command, along with the relevant file contents and `pyproje
let messages = commands::run(
&cli.files,
&pyproject_strategy,
&file_strategy,
&overrides,
cache.into(),
fix::FixMode::None,
@@ -261,12 +219,6 @@ quoting the executed command, along with the relevant file contents and `pyproje
Err(err) => return Err(err.into()),
}
}
} else if cli.add_noqa {
let modifications =
commands::add_noqa(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
if modifications > 0 && log_level >= LogLevel::Default {
println!("Added {modifications} noqa directives.");
}
} else {
let is_stdin = cli.files == vec![PathBuf::from("-")];
@@ -275,7 +227,6 @@ quoting the executed command, along with the relevant file contents and `pyproje
commands::run_stdin(
cli.stdin_filename.map(fs::normalize_path).as_deref(),
&pyproject_strategy,
&file_strategy,
&overrides,
autofix,
)?
@@ -283,7 +234,6 @@ quoting the executed command, along with the relevant file contents and `pyproje
commands::run(
&cli.files,
&pyproject_strategy,
&file_strategy,
&overrides,
cache.into(),
autofix,
@@ -317,10 +267,18 @@ quoting the executed command, along with the relevant file contents and `pyproje
}
}
}
Ok(ExitCode::SUCCESS)
}
fn rewrite_legacy_subcommand(cmd: &str) -> &str {
match cmd {
"--explain" => "rule",
"--clean" => "clean",
"--generate-shell-completion" => "generate-shell-completion",
cmd => cmd,
}
}
#[must_use]
pub fn main() -> ExitCode {
match inner_main() {

68
ruff_cli/src/resolve.rs Normal file
View File

@@ -0,0 +1,68 @@
use std::path::Path;
use anyhow::Result;
use path_absolutize::path_dedot;
use ruff::resolver::{
resolve_settings_with_processor, ConfigProcessor, PyprojectDiscovery, Relativity,
};
use ruff::settings::configuration::Configuration;
use ruff::settings::{pyproject, AllSettings};
use crate::args::Overrides;
/// Resolve the relevant settings strategy and defaults for the current
/// invocation.
pub fn resolve(
isolated: bool,
config: Option<&Path>,
overrides: &Overrides,
stdin_filename: Option<&Path>,
) -> Result<PyprojectDiscovery> {
// First priority: if we're running in isolated mode, use the default settings.
if isolated {
let mut config = Configuration::default();
overrides.process_config(&mut config);
let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?;
return Ok(PyprojectDiscovery::Fixed(settings));
}
// Second priority: the user specified a `pyproject.toml` file. Use that
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
// current working directory. (This matches ESLint's behavior.)
if let Some(pyproject) = config {
let settings = resolve_settings_with_processor(pyproject, &Relativity::Cwd, overrides)?;
return Ok(PyprojectDiscovery::Fixed(settings));
}
// Third priority: find a `pyproject.toml` file in either an ancestor of
// `stdin_filename` (if set) or the current working path all paths relative to
// that directory. (With `Strategy::Hierarchical`, we'll end up finding
// the "closest" `pyproject.toml` file for every Python file later on,
// so these act as the "default" settings.)
if let Some(pyproject) = pyproject::find_settings_toml(
stdin_filename
.as_ref()
.unwrap_or(&path_dedot::CWD.as_path()),
)? {
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Parent, overrides)?;
return Ok(PyprojectDiscovery::Hierarchical(settings));
}
// Fourth priority: find a user-specific `pyproject.toml`, but resolve all paths
// relative the current working directory. (With `Strategy::Hierarchical`, we'll
// end up the "closest" `pyproject.toml` file for every Python file later on, so
// these act as the "default" settings.)
if let Some(pyproject) = pyproject::find_user_settings_toml() {
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Cwd, overrides)?;
return Ok(PyprojectDiscovery::Hierarchical(settings));
}
// Fallback: load Ruff's default settings, and resolve all paths relative to the
// current working directory. (With `Strategy::Hierarchical`, we'll end up the
// "closest" `pyproject.toml` file for every Python file later on, so these act
// as the "default" settings.)
let mut config = Configuration::default();
overrides.process_config(&mut config);
let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?;
Ok(PyprojectDiscovery::Hierarchical(settings))
}

View File

@@ -163,8 +163,8 @@ fn test_show_source() -> Result<()> {
#[test]
fn explain_status_codes() -> Result<()> {
let mut cmd = Command::cargo_bin(BIN_NAME)?;
cmd.args(["-", "--explain", "F401"]).assert().success();
cmd.args(["--explain", "F401"]).assert().success();
let mut cmd = Command::cargo_bin(BIN_NAME)?;
cmd.args(["-", "--explain", "RUF404"]).assert().failure();
cmd.args(["--explain", "RUF404"]).assert().failure();
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.235"
version = "0.0.237"
edition = "2021"
[dependencies]

View File

@@ -1,29 +1,28 @@
//! Run all code and documentation generation steps.
use anyhow::Result;
use clap::Args;
use crate::{generate_cli_help, generate_json_schema, generate_options, generate_rules_table};
#[derive(Args)]
pub struct Cli {
#[derive(clap::Args)]
pub struct Args {
/// Write the generated artifacts to stdout (rather than to the filesystem).
#[arg(long)]
dry_run: bool,
}
pub fn main(cli: &Cli) -> Result<()> {
generate_json_schema::main(&generate_json_schema::Cli {
dry_run: cli.dry_run,
pub fn main(args: &Args) -> Result<()> {
generate_json_schema::main(&generate_json_schema::Args {
dry_run: args.dry_run,
})?;
generate_rules_table::main(&generate_rules_table::Cli {
dry_run: cli.dry_run,
generate_rules_table::main(&generate_rules_table::Args {
dry_run: args.dry_run,
})?;
generate_options::main(&generate_options::Cli {
dry_run: cli.dry_run,
generate_options::main(&generate_options::Args {
dry_run: args.dry_run,
})?;
generate_cli_help::main(&generate_cli_help::Cli {
dry_run: cli.dry_run,
generate_cli_help::main(&generate_cli_help::Args {
dry_run: args.dry_run,
})?;
Ok(())
}

View File

@@ -1,15 +1,14 @@
//! Generate CLI help.
use anyhow::Result;
use clap::Args;
use crate::utils::replace_readme_section;
const HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated cli help. -->";
const HELP_END_PRAGMA: &str = "<!-- End auto-generated cli help. -->";
#[derive(Args)]
pub struct Cli {
#[derive(clap::Args)]
pub struct Args {
/// Write the generated help to stdout (rather than to `README.md`).
#[arg(long)]
pub(crate) dry_run: bool,
@@ -19,10 +18,10 @@ fn trim_lines(s: &str) -> String {
s.lines().map(str::trim_end).collect::<Vec<_>>().join("\n")
}
pub fn main(cli: &Cli) -> Result<()> {
pub fn main(args: &Args) -> Result<()> {
let output = trim_lines(ruff_cli::help().trim());
if cli.dry_run {
if args.dry_run {
print!("{output}");
} else {
replace_readme_section(

View File

@@ -2,22 +2,21 @@ use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use ruff::settings::options::Options;
use schemars::schema_for;
#[derive(Args)]
pub struct Cli {
#[derive(clap::Args)]
pub struct Args {
/// Write the generated table to stdout (rather than to `ruff.schema.json`).
#[arg(long)]
pub(crate) dry_run: bool,
}
pub fn main(cli: &Cli) -> Result<()> {
pub fn main(args: &Args) -> Result<()> {
let schema = schema_for!(Options);
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
if cli.dry_run {
if args.dry_run {
println!("{schema_string}");
} else {
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))

View File

@@ -1,7 +1,6 @@
//! Generate a Markdown-compatible listing of configuration options.
use anyhow::Result;
use clap::Args;
use itertools::Itertools;
use ruff::settings::options::Options;
use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionField};
@@ -11,8 +10,8 @@ use crate::utils::replace_readme_section;
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated options sections. -->";
const END_PRAGMA: &str = "<!-- End auto-generated options sections. -->";
#[derive(Args)]
pub struct Cli {
#[derive(clap::Args)]
pub struct Args {
/// Write the generated table to stdout (rather than to `README.md`).
#[arg(long)]
pub(crate) dry_run: bool,
@@ -39,7 +38,7 @@ fn emit_field(output: &mut String, field: &OptionField, group_name: Option<&str>
output.push('\n');
}
pub fn main(cli: &Cli) -> Result<()> {
pub fn main(args: &Args) -> Result<()> {
let mut output = String::new();
// Generate all the top-level fields.
@@ -89,7 +88,7 @@ pub fn main(cli: &Cli) -> Result<()> {
}
}
if cli.dry_run {
if args.dry_run {
print!("{output}");
} else {
replace_readme_section(&output, BEGIN_PRAGMA, END_PRAGMA)?;

View File

@@ -1,7 +1,6 @@
//! Generate a Markdown-compatible table of supported lint rules.
use anyhow::Result;
use clap::Args;
use ruff::registry::{Linter, LinterCategory, Rule, RuleNamespace};
use strum::IntoEnumIterator;
@@ -13,8 +12,8 @@ const TABLE_END_PRAGMA: &str = "<!-- End auto-generated sections. -->";
const TOC_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated table of contents. -->";
const TOC_END_PRAGMA: &str = "<!-- End auto-generated table of contents. -->";
#[derive(Args)]
pub struct Cli {
#[derive(clap::Args)]
pub struct Args {
/// Write the generated table to stdout (rather than to `README.md`).
#[arg(long)]
pub(crate) dry_run: bool,
@@ -43,7 +42,7 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>)
table_out.push('\n');
}
pub fn main(cli: &Cli) -> Result<()> {
pub fn main(args: &Args) -> Result<()> {
// Generate the table string.
let mut table_out = String::new();
let mut toc_out = String::new();
@@ -96,7 +95,7 @@ pub fn main(cli: &Cli) -> Result<()> {
}
}
if cli.dry_run {
if args.dry_run {
print!("Table of Contents: {toc_out}\n Rules Tables: {table_out}");
} else {
// Extra newline in the markdown numbered list looks weird

View File

@@ -33,45 +33,45 @@ use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
struct Args {
#[command(subcommand)]
command: Commands,
command: Command,
}
#[derive(Subcommand)]
enum Commands {
enum Command {
/// Run all code and documentation generation steps.
GenerateAll(generate_all::Cli),
GenerateAll(generate_all::Args),
/// Generate JSON schema for the TOML configuration file.
GenerateJSONSchema(generate_json_schema::Cli),
GenerateJSONSchema(generate_json_schema::Args),
/// Generate a Markdown-compatible table of supported lint rules.
GenerateRulesTable(generate_rules_table::Cli),
GenerateRulesTable(generate_rules_table::Args),
/// Generate a Markdown-compatible listing of configuration options.
GenerateOptions(generate_options::Cli),
GenerateOptions(generate_options::Args),
/// Generate CLI help.
GenerateCliHelp(generate_cli_help::Cli),
GenerateCliHelp(generate_cli_help::Args),
/// Print the AST for a given Python file.
PrintAST(print_ast::Cli),
PrintAST(print_ast::Args),
/// Print the LibCST CST for a given Python file.
PrintCST(print_cst::Cli),
PrintCST(print_cst::Args),
/// Print the token stream for a given Python file.
PrintTokens(print_tokens::Cli),
PrintTokens(print_tokens::Args),
/// Run round-trip source code generation on a given Python file.
RoundTrip(round_trip::Cli),
RoundTrip(round_trip::Args),
}
fn main() -> Result<()> {
let cli = Cli::parse();
match &cli.command {
Commands::GenerateAll(args) => generate_all::main(args)?,
Commands::GenerateJSONSchema(args) => generate_json_schema::main(args)?,
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
Commands::GenerateOptions(args) => generate_options::main(args)?,
Commands::GenerateCliHelp(args) => generate_cli_help::main(args)?,
Commands::PrintAST(args) => print_ast::main(args)?,
Commands::PrintCST(args) => print_cst::main(args)?,
Commands::PrintTokens(args) => print_tokens::main(args)?,
Commands::RoundTrip(args) => round_trip::main(args)?,
let args = Args::parse();
match &args.command {
Command::GenerateAll(args) => generate_all::main(args)?,
Command::GenerateJSONSchema(args) => generate_json_schema::main(args)?,
Command::GenerateRulesTable(args) => generate_rules_table::main(args)?,
Command::GenerateOptions(args) => generate_options::main(args)?,
Command::GenerateCliHelp(args) => generate_cli_help::main(args)?,
Command::PrintAST(args) => print_ast::main(args)?,
Command::PrintCST(args) => print_cst::main(args)?,
Command::PrintTokens(args) => print_tokens::main(args)?,
Command::RoundTrip(args) => round_trip::main(args)?,
}
Ok(())
}

View File

@@ -4,19 +4,18 @@ use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use rustpython_parser::parser;
#[derive(Args)]
pub struct Cli {
#[derive(clap::Args)]
pub struct Args {
/// Python file for which to generate the AST.
#[arg(required = true)]
file: PathBuf,
}
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
pub fn main(args: &Args) -> Result<()> {
let contents = fs::read_to_string(&args.file)?;
let python_ast = parser::parse_program(&contents, &args.file.to_string_lossy())?;
println!("{python_ast:#?}");
Ok(())
}

View File

@@ -4,17 +4,16 @@ use std::fs;
use std::path::PathBuf;
use anyhow::{bail, Result};
use clap::Args;
#[derive(Args)]
pub struct Cli {
#[derive(clap::Args)]
pub struct Args {
/// Python file for which to generate the CST.
#[arg(required = true)]
file: PathBuf,
}
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
pub fn main(args: &Args) -> Result<()> {
let contents = fs::read_to_string(&args.file)?;
match libcst_native::parse_module(&contents, None) {
Ok(python_cst) => {
println!("{python_cst:#?}");

View File

@@ -4,18 +4,17 @@ use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use rustpython_parser::lexer;
#[derive(Args)]
pub struct Cli {
#[derive(clap::Args)]
pub struct Args {
/// Python file for which to generate the AST.
#[arg(required = true)]
file: PathBuf,
}
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
pub fn main(args: &Args) -> Result<()> {
let contents = fs::read_to_string(&args.file)?;
for (_, tok, _) in lexer::make_tokenizer(&contents).flatten() {
println!("{tok:#?}");
}

View File

@@ -4,18 +4,17 @@ use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use ruff::source_code::round_trip;
#[derive(Args)]
pub struct Cli {
#[derive(clap::Args)]
pub struct Args {
/// Python file to round-trip.
#[arg(required = true)]
file: PathBuf,
}
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
println!("{}", round_trip(&contents, &cli.file.to_string_lossy())?);
pub fn main(args: &Args) -> Result<()> {
let contents = fs::read_to_string(&args.file)?;
println!("{}", round_trip(&contents, &args.file.to_string_lossy())?);
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.235"
version = "0.0.237"
edition = "2021"
[lib]

View File

@@ -1,3 +1,4 @@
use std::cmp::Reverse;
use std::collections::HashSet;
use proc_macro2::{Ident, Span};
@@ -70,7 +71,7 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
});
}
parsed.sort_by_key(|(prefix, _)| prefix.len());
parsed.sort_by_key(|(prefix, _)| Reverse(prefix.len()));
let mut if_statements = quote!();
let mut into_iter_match_arms = quote!();

View File

@@ -1,15 +1,4 @@
condense_wildcard_suffixes = true
edition = "2021"
format_strings = true
group_imports = "StdExternalCrate"
hex_literal_case = "Lower"
imports_granularity = "Module"
max_width = 100
normalize_comments = true
normalize_doc_attributes = true
reorder_impl_items = true
reorder_imports = true
reorder_modules = true
unstable_features = true
use_field_init_shorthand = true
wrap_comments = true

View File

@@ -84,7 +84,7 @@ mod tests {
fp.write("\n")
elif line.strip() == "/// Ruff-specific rules":
fp.write(f"/// [{plugin}]({url})\n")
fp.write(f"{indent}/// [{plugin}]({url})\n")
fp.write(f'{indent}#[prefix = "{prefix_code}"]\n')
fp.write(f"{indent}{pascal_case(plugin)},")
fp.write("\n")

View File

@@ -0,0 +1,67 @@
"""Generate an MkDocs-compatible `docs` and `mkdocs.yml` from the README.md."""
import argparse
import shutil
from pathlib import Path
import yaml
SECTIONS: list[tuple[str, str]] = [
("Overview", "index.md"),
("Installation and Usage", "installation-and-usage.md"),
("Configuration", "configuration.md"),
("Rules", "rules.md"),
("Settings", "settings.md"),
("Editor Integrations", "editor-integrations.md"),
("FAQ", "faq.md"),
]
def main() -> None:
"""Generate an MkDocs-compatible `docs` and `mkdocs.yml`."""
with Path("README.md").open(encoding="utf8") as fp:
content = fp.read()
Path("docs").mkdir(parents=True, exist_ok=True)
# Split the README.md into sections.
for (title, filename) in SECTIONS:
with Path(f"docs/{filename}").open("w+") as f:
block = content.split(f"<!-- Begin section: {title} -->")
if len(block) != 2:
msg = f"Section {title} not found in README.md"
raise ValueError(msg)
block = block[1].split(f"<!-- End section: {title} -->")
if len(block) != 2:
msg = f"Section {title} not found in README.md"
raise ValueError(msg)
f.write(block[0])
# Copy the CONTRIBUTING.md.
shutil.copy("CONTRIBUTING.md", "docs/contributing.md")
# Add the nav section to mkdocs.yml.
with Path("mkdocs.template.yml").open(encoding="utf8") as fp:
config = yaml.safe_load(fp)
config["nav"] = [
{"Overview": "index.md"},
{"Installation and Usage": "installation-and-usage.md"},
{"Configuration": "configuration.md"},
{"Rules": "rules.md"},
{"Settings": "settings.md"},
{"Editor Integrations": "editor-integrations.md"},
{"FAQ": "faq.md"},
{"Contributing": "contributing.md"},
]
with Path("mkdocs.yml").open("w+") as fp:
yaml.safe_dump(config, fp)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate an MkDocs-compatible `docs` and `mkdocs.yml`.",
)
args = parser.parse_args()
main()

View File

@@ -5,6 +5,7 @@ ignore = [
"INP001", # implicit-namespace-package
"PLR2004", # magic-value-comparison
"S101", # assert-used
"EM"
]
[tool.ruff.pydocstyle]

View File

@@ -0,0 +1,77 @@
"""Transform the README.md to support a specific deployment target.
By default, we assume that our README.md will be rendered on GitHub. However, different
targets have different strategies for rendering light- and dark-mode images. This script
adjusts the images in the README.md to support the given target.
"""
import argparse
from pathlib import Path
# https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#specifying-the-theme-an-image-is-shown-to
GITHUB = """
<p align="center">
<picture align="center">
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1309177/212613422-7faaf278-706b-4294-ad92-236ffcab3430.svg">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1309177/212613257-5f4bca12-6d6b-4c79-9bac-51a4c6d08928.svg">
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/212613257-5f4bca12-6d6b-4c79-9bac-51a4c6d08928.svg">
</picture>
</p>
"""
# https://github.com/pypi/warehouse/issues/11251
PYPI = """
<p align="center">
<picture align="center">
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/212613257-5f4bca12-6d6b-4c79-9bac-51a4c6d08928.svg">
</picture>
</p>
"""
# https://squidfunk.github.io/mkdocs-material/reference/images/#light-and-dark-mode
MK_DOCS = """
<p align="center">
<picture align="center">
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/212613257-5f4bca12-6d6b-4c79-9bac-51a4c6d08928.svg#only-light">
</picture>
</p>
<p align="center">
<picture align="center">
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/212613422-7faaf278-706b-4294-ad92-236ffcab3430.svg#only-dark">
</picture>
</p>
"""
def main(target: str) -> None:
"""Modify the README.md to support the given target."""
with Path("README.md").open(encoding="utf8") as fp:
content = fp.read()
if GITHUB not in content:
msg = "README.md is not in the expected format."
raise ValueError(msg)
if target == "pypi":
with Path("README.md").open("w", encoding="utf8") as fp:
fp.write(content.replace(GITHUB, PYPI))
elif target == "mkdocs":
with Path("README.md").open("w", encoding="utf8") as fp:
fp.write(content.replace(GITHUB, MK_DOCS))
else:
msg = f"Unknown target: {target}"
raise ValueError(msg)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Modify the README.md to support a specific deployment target.",
)
parser.add_argument(
"--target",
type=str,
required=True,
choices=("pypi", "mkdocs"),
)
args = parser.parse_args()
main(target=args.target)

View File

@@ -489,6 +489,17 @@ pub fn is_const_none(expr: &Expr) -> bool {
)
}
/// Return `true` if an [`Expr`] is `True`.
pub fn is_const_true(expr: &Expr) -> bool {
matches!(
&expr.node,
ExprKind::Constant {
value: Constant::Bool(true),
kind: None
},
)
}
/// Return `true` if a keyword argument is present with a non-`None` value.
pub fn has_non_none_keyword(keywords: &[Keyword], keyword: &str) -> bool {
find_keyword(keywords, keyword).map_or(false, |keyword| {
@@ -665,6 +676,24 @@ pub fn match_trailing_content(stmt: &Stmt, locator: &Locator) -> bool {
false
}
/// If a `Stmt` has a trailing comment, return the index of the hash.
pub fn match_trailing_comment(stmt: &Stmt, locator: &Locator) -> Option<usize> {
let range = Range::new(
stmt.end_location.unwrap(),
Location::new(stmt.end_location.unwrap().row() + 1, 0),
);
let suffix = locator.slice_source_code_range(&range);
for (i, char) in suffix.chars().enumerate() {
if char == '#' {
return Some(i);
}
if !char.is_whitespace() {
return None;
}
}
None
}
/// Return the number of trailing empty lines following a statement.
pub fn count_trailing_lines(stmt: &Stmt, locator: &Locator) -> usize {
let suffix =
@@ -974,6 +1003,21 @@ impl<'a> SimpleCallArgs<'a> {
}
}
/// Return `true` if the given `Expr` is a potential logging call. Matches
/// `logging.error`, `logger.error`, `self.logger.error`, etc., but not
/// arbitrary `foo.error` calls.
pub fn is_logger_candidate(func: &Expr) -> bool {
if let ExprKind::Attribute { value, .. } = &func.node {
let call_path = collect_call_path(value);
if let Some(tail) = call_path.last() {
if tail.starts_with("log") || tail.ends_with("logger") || tail.ends_with("logging") {
return true;
}
}
}
false
}
#[cfg(test)]
mod tests {
use anyhow::Result;

View File

@@ -1,3 +1,4 @@
use bitflags::bitflags;
use rustc_hash::FxHashMap;
use rustpython_ast::{Cmpop, Located};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
@@ -9,9 +10,21 @@ use crate::ast::types::{Binding, BindingKind, Scope};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
bitflags! {
#[derive(Default)]
pub struct AllNamesFlags: u32 {
const INVALID_FORMAT = 0b0000_0001;
const INVALID_OBJECT = 0b0000_0010;
}
}
/// Extract the names bound to a given __all__ assignment.
pub fn extract_all_names(stmt: &Stmt, scope: &Scope, bindings: &[Binding]) -> Vec<String> {
fn add_to_names(names: &mut Vec<String>, elts: &[Expr]) {
pub fn extract_all_names(
stmt: &Stmt,
scope: &Scope,
bindings: &[Binding],
) -> (Vec<String>, AllNamesFlags) {
fn add_to_names(names: &mut Vec<String>, elts: &[Expr], flags: &mut AllNamesFlags) {
for elt in elts {
if let ExprKind::Constant {
value: Constant::Str(value),
@@ -19,11 +32,14 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope, bindings: &[Binding]) -> Ve
} = &elt.node
{
names.push(value.to_string());
} else {
*flags |= AllNamesFlags::INVALID_OBJECT;
}
}
}
let mut names: Vec<String> = vec![];
let mut flags = AllNamesFlags::empty();
// Grab the existing bound __all__ values.
if let StmtKind::AugAssign { .. } = &stmt.node {
@@ -42,7 +58,7 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope, bindings: &[Binding]) -> Ve
} {
match &value.node {
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
add_to_names(&mut names, elts);
add_to_names(&mut names, elts, &mut flags);
}
ExprKind::BinOp { left, right, .. } => {
let mut current_left = left;
@@ -50,27 +66,35 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope, bindings: &[Binding]) -> Ve
while let Some(elts) = match &current_right.node {
ExprKind::List { elts, .. } => Some(elts),
ExprKind::Tuple { elts, .. } => Some(elts),
_ => None,
_ => {
flags |= AllNamesFlags::INVALID_FORMAT;
None
}
} {
add_to_names(&mut names, elts);
add_to_names(&mut names, elts, &mut flags);
match &current_left.node {
ExprKind::BinOp { left, right, .. } => {
current_left = left;
current_right = right;
}
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
add_to_names(&mut names, elts);
add_to_names(&mut names, elts, &mut flags);
break;
}
_ => {
flags |= AllNamesFlags::INVALID_FORMAT;
break;
}
_ => break,
}
}
}
_ => {}
_ => {
flags |= AllNamesFlags::INVALID_FORMAT;
}
}
}
names
(names, flags)
}
#[derive(Default)]

View File

@@ -89,9 +89,6 @@ pub struct Scope<'a> {
pub uses_locals: bool,
/// A map from bound name to binding index.
pub values: FxHashMap<&'a str, usize>,
/// A list of (name, index) pairs for bindings that were overridden in the
/// scope.
pub overridden: Vec<(&'a str, usize)>,
}
impl<'a> Scope<'a> {
@@ -102,11 +99,27 @@ impl<'a> Scope<'a> {
import_starred: false,
uses_locals: false,
values: FxHashMap::default(),
overridden: Vec::new(),
}
}
}
// Pyflakes defines the following binding hierarchy (via inheritance):
// Binding
// ExportBinding
// Annotation
// Argument
// Assignment
// NamedExprAssignment
// Definition
// FunctionDefinition
// ClassDefinition
// Builtin
// Importation
// SubmoduleImportation
// ImportationFrom
// StarImportation
// FutureImportation
#[derive(Clone, Debug)]
pub enum BindingKind<'a> {
Annotation,
@@ -127,10 +140,12 @@ pub enum BindingKind<'a> {
SubmoduleImportation(&'a str, &'a str),
}
#[derive(Clone, Debug)]
#[derive(Debug, Clone)]
pub struct Binding<'a> {
pub kind: BindingKind<'a>,
pub range: Range,
/// The context in which the binding was created.
pub context: ExecutionContext,
/// The statement in which the [`Binding`] was defined.
pub source: Option<RefEquality<'a, Stmt>>,
/// Tuple of (scope index, range) indicating the scope and range at which
@@ -146,34 +161,17 @@ pub struct Binding<'a> {
pub synthetic_usage: Option<(usize, Range)>,
}
#[derive(Copy, Clone)]
pub enum UsageContext {
#[derive(Copy, Debug, Clone)]
pub enum ExecutionContext {
Runtime,
Typing,
}
// Pyflakes defines the following binding hierarchy (via inheritance):
// Binding
// ExportBinding
// Annotation
// Argument
// Assignment
// NamedExprAssignment
// Definition
// FunctionDefinition
// ClassDefinition
// Builtin
// Importation
// SubmoduleImportation
// ImportationFrom
// StarImportation
// FutureImportation
impl<'a> Binding<'a> {
pub fn mark_used(&mut self, scope: usize, range: Range, context: UsageContext) {
pub fn mark_used(&mut self, scope: usize, range: Range, context: ExecutionContext) {
match context {
UsageContext::Runtime => self.runtime_usage = Some((scope, range)),
UsageContext::Typing => self.typing_usage = Some((scope, range)),
ExecutionContext::Runtime => self.runtime_usage = Some((scope, range)),
ExecutionContext::Typing => self.typing_usage = Some((scope, range)),
}
}

View File

@@ -12,7 +12,7 @@ use crate::ast::whitespace::LinesWithTrailingNewline;
use crate::cst::helpers::compose_module_path;
use crate::cst::matchers::match_module;
use crate::fix::Fix;
use crate::source_code::{Indexer, Locator};
use crate::source_code::{Indexer, Locator, Stylist};
/// Determine if a body contains only a single statement, taking into account
/// deleted.
@@ -157,6 +157,7 @@ pub fn delete_stmt(
deleted: &[&Stmt],
locator: &Locator,
indexer: &Indexer,
stylist: &Stylist,
) -> Result<Fix> {
if parent
.map(|parent| is_lone_child(stmt, parent, deleted))
@@ -179,7 +180,11 @@ pub fn delete_stmt(
} else if helpers::preceded_by_continuation(stmt, indexer) {
if is_end_of_file(stmt, locator) && stmt.location.column() == 0 {
// Special-case: a file can't end in a continuation.
Fix::replacement("\n".to_string(), stmt.location, stmt.end_location.unwrap())
Fix::replacement(
stylist.line_ending().to_string(),
stmt.location,
stmt.end_location.unwrap(),
)
} else {
Fix::deletion(stmt.location, stmt.end_location.unwrap())
}
@@ -200,6 +205,7 @@ pub fn remove_unused_imports<'a>(
deleted: &[&Stmt],
locator: &Locator,
indexer: &Indexer,
stylist: &Stylist,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
let mut tree = match_module(module_text)?;
@@ -237,7 +243,7 @@ pub fn remove_unused_imports<'a>(
if !found_star {
bail!("Expected \'*\' for unused import");
}
return delete_stmt(stmt, parent, deleted, locator, indexer);
return delete_stmt(stmt, parent, deleted, locator, indexer, stylist);
} else {
bail!("Expected: ImportNames::Aliases | ImportNames::Star");
}
@@ -298,9 +304,13 @@ pub fn remove_unused_imports<'a>(
}
if aliases.is_empty() {
delete_stmt(stmt, parent, deleted, locator, indexer)
delete_stmt(stmt, parent, deleted, locator, indexer, stylist)
} else {
let mut state = CodegenState::default();
let mut state = CodegenState {
default_newline: stylist.line_ending(),
default_indent: stylist.indentation(),
..CodegenState::default()
};
tree.codegen(&mut state);
Ok(Fix::replacement(

View File

@@ -1,5 +1,6 @@
//! Lint rules based on AST traversal.
use std::iter;
use std::path::Path;
use itertools::Itertools;
@@ -16,11 +17,11 @@ use rustpython_parser::parser;
use smallvec::smallvec;
use crate::ast::helpers::{binding_range, collect_call_path, extract_handler_names};
use crate::ast::operations::extract_all_names;
use crate::ast::operations::{extract_all_names, AllNamesFlags};
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
Binding, BindingKind, CallPath, ClassDef, FunctionDef, Lambda, Node, Range, RefEquality, Scope,
ScopeKind, UsageContext,
Binding, BindingKind, CallPath, ClassDef, ExecutionContext, FunctionDef, Lambda, Node, Range,
RefEquality, Scope, ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{branch_detection, cast, helpers, operations, visitor};
@@ -34,10 +35,11 @@ use crate::registry::{Diagnostic, Rule};
use crate::rules::{
flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except, flake8_boolean_trap,
flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger,
flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_pie, flake8_print,
flake8_pytest_style, flake8_return, flake8_simplify, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle,
pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_logging_format,
flake8_pie, flake8_print, flake8_pytest_style, flake8_return, flake8_simplify,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, flake8_use_pathlib, mccabe,
pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade,
ruff, tryceratops,
};
use crate::settings::types::PythonVersion;
use crate::settings::{flags, Settings};
@@ -79,7 +81,7 @@ pub struct Checker<'a> {
pub(crate) exprs: Vec<RefEquality<'a, Expr>>,
pub(crate) scopes: Vec<Scope<'a>>,
pub(crate) scope_stack: Vec<usize>,
pub(crate) dead_scopes: Vec<usize>,
pub(crate) dead_scopes: Vec<(usize, Vec<usize>)>,
deferred_string_type_definitions: Vec<(Range, &'a str, bool, DeferralContext<'a>)>,
deferred_type_definitions: Vec<(&'a Expr, bool, DeferralContext<'a>)>,
deferred_functions: Vec<(&'a Stmt, DeferralContext<'a>, VisibleScope)>,
@@ -100,7 +102,6 @@ pub struct Checker<'a> {
except_handlers: Vec<Vec<Vec<&'a str>>>,
// Check-specific state.
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>,
pub(crate) type_checking_blocks: Vec<&'a Stmt>,
}
impl<'a> Checker<'a> {
@@ -161,7 +162,6 @@ impl<'a> Checker<'a> {
except_handlers: vec![],
// Check-specific state.
flake8_bugbear_seen: vec![],
type_checking_blocks: vec![],
}
}
@@ -330,6 +330,7 @@ where
let ranges = helpers::find_names(stmt, self.locator);
if scope_index != GLOBAL_SCOPE_INDEX {
// Add the binding to the current scope.
let context = self.execution_context();
let scope = &mut self.scopes[scope_index];
let usage = Some((scope.id, Range::from_located(stmt)));
for (name, range) in names.iter().zip(ranges.iter()) {
@@ -341,6 +342,7 @@ where
typing_usage: None,
range: *range,
source: Some(RefEquality(stmt)),
context,
});
scope.values.insert(name, index);
}
@@ -357,6 +359,7 @@ where
let scope_index = *self.scope_stack.last().expect("No current scope found");
let ranges = helpers::find_names(stmt, self.locator);
if scope_index != GLOBAL_SCOPE_INDEX {
let context = self.execution_context();
let scope = &mut self.scopes[scope_index];
let usage = Some((scope.id, Range::from_located(stmt)));
for (name, range) in names.iter().zip(ranges.iter()) {
@@ -369,6 +372,7 @@ where
typing_usage: None,
range: *range,
source: Some(RefEquality(stmt)),
context,
});
scope.values.insert(name, index);
}
@@ -674,6 +678,8 @@ where
for expr in &args.defaults {
self.visit_expr(expr);
}
let context = self.execution_context();
self.add_binding(
name,
Binding {
@@ -683,6 +689,7 @@ where
typing_usage: None,
range: Range::from_located(stmt),
source: Some(self.current_stmt().clone()),
context,
},
);
}
@@ -837,6 +844,7 @@ where
typing_usage: None,
range: Range::from_located(alias),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
} else {
@@ -876,6 +884,7 @@ where
typing_usage: None,
range: Range::from_located(alias),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
}
@@ -1118,6 +1127,7 @@ where
typing_usage: None,
range: Range::from_located(alias),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
@@ -1154,6 +1164,7 @@ where
typing_usage: None,
range: Range::from_located(stmt),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
@@ -1228,6 +1239,7 @@ where
typing_usage: None,
range,
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
}
@@ -1394,6 +1406,16 @@ where
pyupgrade::rules::os_error_alias(self, &item);
}
}
if self.settings.rules.enabled(&Rule::RaiseVanillaClass) {
if let Some(expr) = exc {
tryceratops::rules::raise_vanilla_class(self, expr);
}
}
if self.settings.rules.enabled(&Rule::RaiseVanillaArgs) {
if let Some(expr) = exc {
tryceratops::rules::raise_vanilla_args(self, expr);
}
}
}
StmtKind::AugAssign { target, .. } => {
self.handle_node_load(target);
@@ -1607,6 +1629,9 @@ where
if self.settings.rules.enabled(&Rule::RaiseWithinTry) {
tryceratops::rules::raise_within_try(self, body);
}
if self.settings.rules.enabled(&Rule::ErrorInsteadOfException) {
tryceratops::rules::error_instead_of_exception(self, handlers);
}
}
StmtKind::Assign { targets, value, .. } => {
if self.settings.rules.enabled(&Rule::DoNotAssignLambda) {
@@ -1736,6 +1761,7 @@ where
typing_usage: None,
range: Range::from_located(stmt),
source: Some(RefEquality(stmt)),
context: self.execution_context(),
});
self.scopes[GLOBAL_SCOPE_INDEX].values.insert(name, index);
}
@@ -1799,6 +1825,7 @@ where
typing_usage: None,
range: Range::from_located(stmt),
source: Some(RefEquality(stmt)),
context: self.execution_context(),
});
self.scopes[GLOBAL_SCOPE_INDEX].values.insert(name, index);
}
@@ -1862,7 +1889,6 @@ where
if self.settings.rules.enabled(&Rule::EmptyTypeCheckingBlock) {
flake8_type_checking::rules::empty_type_checking_block(self, test, body);
}
self.type_checking_blocks.push(stmt);
let prev_in_type_checking_block = self.in_type_checking_block;
self.in_type_checking_block = true;
@@ -1894,6 +1920,7 @@ where
typing_usage: None,
range: Range::from_located(stmt),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
}
@@ -2642,6 +2669,19 @@ where
{
flake8_use_pathlib::helpers::replaceable_by_pathlib(self, func);
}
// flake8-logging-format
if self.settings.rules.enabled(&Rule::LoggingStringFormat)
|| self.settings.rules.enabled(&Rule::LoggingPercentFormat)
|| self.settings.rules.enabled(&Rule::LoggingStringConcat)
|| self.settings.rules.enabled(&Rule::LoggingFString)
|| self.settings.rules.enabled(&Rule::LoggingWarn)
|| self.settings.rules.enabled(&Rule::LoggingExtraAttrClash)
|| self.settings.rules.enabled(&Rule::LoggingExcInfo)
|| self.settings.rules.enabled(&Rule::LoggingRedundantExcInfo)
{
flake8_logging_format::rules::logging_call(self, func, args, keywords);
}
}
ExprKind::Dict { keys, values } => {
if self
@@ -3407,6 +3447,15 @@ where
body,
);
}
if self.settings.rules.enabled(&Rule::TryExceptPass) {
flake8_bandit::rules::try_except_pass(
self,
type_.as_deref(),
name.as_deref(),
body,
self.settings.flake8_bandit.check_typed_exception,
);
}
if self.settings.rules.enabled(&Rule::ReraiseNoCause) {
tryceratops::rules::reraise_no_cause(self, body);
}
@@ -3584,6 +3633,7 @@ where
typing_usage: None,
range: Range::from_located(arg),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
@@ -3660,11 +3710,12 @@ impl<'a> Checker<'a> {
}
fn pop_scope(&mut self) {
self.dead_scopes.push(
self.dead_scopes.push((
self.scope_stack
.pop()
.expect("Attempted to pop without scope"),
);
self.scope_stack.clone(),
));
}
fn bind_builtins(&mut self) {
@@ -3681,9 +3732,10 @@ impl<'a> Checker<'a> {
kind: BindingKind::Builtin,
range: Range::default(),
runtime_usage: None,
synthetic_usage: None,
synthetic_usage: Some((0, Range::default())),
typing_usage: None,
source: None,
context: ExecutionContext::Runtime,
});
scope.values.insert(builtin, index);
}
@@ -3720,6 +3772,18 @@ impl<'a> Checker<'a> {
.map(|index| &self.scopes[*index])
}
pub fn execution_context(&self) -> ExecutionContext {
if self.in_type_checking_block
|| self.in_annotation
|| self.in_deferred_string_type_definition
|| self.in_deferred_type_definition
{
ExecutionContext::Typing
} else {
ExecutionContext::Runtime
}
}
fn add_binding<'b>(&mut self, name: &'b str, binding: Binding<'a>)
where
'b: 'a,
@@ -3795,16 +3859,41 @@ impl<'a> Checker<'a> {
}
}
// Assume the rebound name is used as a global or within a loop.
let scope = self.current_scope();
let binding = match scope.values.get(&name) {
None => binding,
Some(index) => Binding {
runtime_usage: self.bindings[*index].runtime_usage,
synthetic_usage: self.bindings[*index].synthetic_usage,
typing_usage: self.bindings[*index].typing_usage,
..binding
},
let binding = if let Some(index) = scope.values.get(&name) {
if matches!(self.bindings[*index].kind, BindingKind::Builtin) {
// Avoid overriding builtins.
binding
} else if matches!(self.bindings[*index].kind, BindingKind::Global) {
// If the original binding was a global, and the new binding conflicts within the
// current scope, then the new binding is also a global.
Binding {
runtime_usage: self.bindings[*index].runtime_usage,
synthetic_usage: self.bindings[*index].synthetic_usage,
typing_usage: self.bindings[*index].typing_usage,
kind: BindingKind::Global,
..binding
}
} else if matches!(self.bindings[*index].kind, BindingKind::Nonlocal) {
// If the original binding was a nonlocal, and the new binding conflicts within the
// current scope, then the new binding is also a nonlocal.
Binding {
runtime_usage: self.bindings[*index].runtime_usage,
synthetic_usage: self.bindings[*index].synthetic_usage,
typing_usage: self.bindings[*index].typing_usage,
kind: BindingKind::Nonlocal,
..binding
}
} else {
Binding {
runtime_usage: self.bindings[*index].runtime_usage,
synthetic_usage: self.bindings[*index].synthetic_usage,
typing_usage: self.bindings[*index].typing_usage,
..binding
}
}
} else {
binding
};
// Don't treat annotations as assignments if there is an existing value
@@ -3837,17 +3926,8 @@ impl<'a> Checker<'a> {
}
if let Some(index) = scope.values.get(&id.as_str()) {
let context = if self.in_type_checking_block
|| self.in_annotation
|| self.in_deferred_string_type_definition
|| self.in_deferred_type_definition
{
UsageContext::Typing
} else {
UsageContext::Runtime
};
// Mark the binding as used.
let context = self.execution_context();
self.bindings[*index].mark_used(scope_id, Range::from_located(expr), context);
if matches!(self.bindings[*index].kind, BindingKind::Annotation)
@@ -4030,6 +4110,7 @@ impl<'a> Checker<'a> {
typing_usage: None,
range: Range::from_located(expr),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
return;
@@ -4049,6 +4130,7 @@ impl<'a> Checker<'a> {
typing_usage: None,
range: Range::from_located(expr),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
return;
@@ -4064,6 +4146,7 @@ impl<'a> Checker<'a> {
typing_usage: None,
range: Range::from_located(expr),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
return;
@@ -4103,19 +4186,31 @@ impl<'a> Checker<'a> {
}
_ => false,
} {
let (all_names, all_names_flags) =
extract_all_names(parent, current, &self.bindings);
if self.settings.rules.enabled(&Rule::InvalidAllFormat)
&& matches!(all_names_flags, AllNamesFlags::INVALID_FORMAT)
{
pylint::rules::invalid_all_format(self, expr);
}
if self.settings.rules.enabled(&Rule::InvalidAllObject)
&& matches!(all_names_flags, AllNamesFlags::INVALID_OBJECT)
{
pylint::rules::invalid_all_object(self, expr);
}
self.add_binding(
id,
Binding {
kind: BindingKind::Export(extract_all_names(
parent,
current,
&self.bindings,
)),
kind: BindingKind::Export(all_names),
runtime_usage: None,
synthetic_usage: None,
typing_usage: None,
range: Range::from_located(expr),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
return;
@@ -4131,6 +4226,7 @@ impl<'a> Checker<'a> {
typing_usage: None,
range: Range::from_located(expr),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
);
}
@@ -4336,13 +4432,48 @@ impl<'a> Checker<'a> {
return;
}
let mut diagnostics: Vec<Diagnostic> = vec![];
for scope in self
.dead_scopes
.iter()
.rev()
.map(|index| &self.scopes[*index])
// Identify any valid runtime imports. If a module is imported at runtime, and
// used at runtime, then by default, we avoid flagging any other
// imports from that model as typing-only.
let runtime_imports: Vec<Vec<&Binding>> = if !self.settings.flake8_type_checking.strict
&& (self
.settings
.rules
.enabled(&Rule::RuntimeImportInTypeCheckingBlock)
|| self
.settings
.rules
.enabled(&Rule::TypingOnlyFirstPartyImport)
|| self
.settings
.rules
.enabled(&Rule::TypingOnlyThirdPartyImport)
|| self
.settings
.rules
.enabled(&Rule::TypingOnlyStandardLibraryImport))
{
self.scopes
.iter()
.map(|scope| {
scope
.values
.values()
.map(|index| &self.bindings[*index])
.filter(|binding| {
flake8_type_checking::helpers::is_valid_runtime_import(binding)
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
} else {
vec![]
};
let mut diagnostics: Vec<Diagnostic> = vec![];
for (index, stack) in self.dead_scopes.iter().rev() {
let scope = &self.scopes[*index];
// PLW0602
if self
.settings
@@ -4352,10 +4483,14 @@ impl<'a> Checker<'a> {
for (name, index) in &scope.values {
let binding = &self.bindings[*index];
if matches!(binding.kind, BindingKind::Global) {
diagnostics.push(Diagnostic::new(
violations::GlobalVariableNotAssigned((*name).to_string()),
binding.range,
));
if let Some(stmt) = &binding.source {
if matches!(stmt.node, StmtKind::Global { .. }) {
diagnostics.push(Diagnostic::new(
violations::GlobalVariableNotAssigned((*name).to_string()),
binding.range,
));
}
}
}
}
}
@@ -4481,25 +4616,28 @@ impl<'a> Checker<'a> {
.rules
.enabled(&Rule::TypingOnlyStandardLibraryImport)
{
for (.., index) in scope
.values
.iter()
.chain(scope.overridden.iter().map(|(a, b)| (a, b)))
{
let runtime_imports: Vec<&Binding> = if self.settings.flake8_type_checking.strict {
vec![]
} else {
stack
.iter()
.chain(iter::once(index))
.flat_map(|index| runtime_imports[*index].iter())
.copied()
.collect()
};
for (.., index) in &scope.values {
let binding = &self.bindings[*index];
if let Some(diagnostic) =
flake8_type_checking::rules::runtime_import_in_type_checking_block(
binding,
&self.type_checking_blocks,
)
flake8_type_checking::rules::runtime_import_in_type_checking_block(binding)
{
diagnostics.push(diagnostic);
}
if let Some(diagnostic) =
flake8_type_checking::rules::typing_only_runtime_import(
binding,
&self.type_checking_blocks,
&runtime_imports,
self.package,
self.settings,
)
@@ -4522,11 +4660,7 @@ impl<'a> Checker<'a> {
let mut ignored: FxHashMap<BindingContext, Vec<UnusedImport>> =
FxHashMap::default();
for (name, index) in scope
.values
.iter()
.chain(scope.overridden.iter().map(|(a, b)| (a, b)))
{
for (name, index) in &scope.values {
let binding = &self.bindings[*index];
let full_name = match &binding.kind {
@@ -4598,6 +4732,7 @@ impl<'a> Checker<'a> {
&deleted,
self.locator,
self.indexer,
self.stylist,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
@@ -4617,7 +4752,11 @@ impl<'a> Checker<'a> {
let multiple = unused_imports.len() > 1;
for (full_name, range) in unused_imports {
let mut diagnostic = Diagnostic::new(
violations::UnusedImport(full_name.to_string(), ignore_init, multiple),
violations::UnusedImport {
name: full_name.to_string(),
ignore_init,
multiple,
},
*range,
);
if matches!(child.node, StmtKind::ImportFrom { .. })
@@ -4639,7 +4778,11 @@ impl<'a> Checker<'a> {
let multiple = unused_imports.len() > 1;
for (full_name, range) in unused_imports {
let mut diagnostic = Diagnostic::new(
violations::UnusedImport(full_name.to_string(), ignore_init, multiple),
violations::UnusedImport {
name: full_name.to_string(),
ignore_init,
multiple,
},
*range,
);
if matches!(child.node, StmtKind::ImportFrom { .. })

View File

@@ -32,9 +32,6 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Diagnosti
// Load the relevant `Settings` for the given `Path`.
let settings = resolve(path)?;
// Validate the `Settings` and return any errors.
settings.validate()?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(contents);

View File

@@ -11,8 +11,8 @@ use crate::registry::Rule;
use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_errmsg,
flake8_implicit_str_concat, flake8_import_conventions, flake8_pytest_style, flake8_quotes,
flake8_tidy_imports, flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle,
pydocstyle, pylint, pyupgrade,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pylint, pyupgrade,
};
use crate::rustpython_helpers::tokenize;
use crate::settings::configuration::Configuration;
@@ -141,13 +141,14 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
flake8_errmsg: Some(flake8_errmsg::settings::Settings::default().into()),
flake8_pytest_style: Some(flake8_pytest_style::settings::Settings::default().into()),
flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()),
flake8_tidy_imports: Some(flake8_tidy_imports::Settings::default().into()),
flake8_implicit_str_concat: Some(
flake8_implicit_str_concat::settings::Settings::default().into(),
),
flake8_import_conventions: Some(
flake8_import_conventions::settings::Settings::default().into(),
),
flake8_tidy_imports: Some(flake8_tidy_imports::Settings::default().into()),
flake8_type_checking: Some(flake8_type_checking::settings::Settings::default().into()),
flake8_unused_arguments: Some(
flake8_unused_arguments::settings::Settings::default().into(),
),

View File

@@ -42,9 +42,6 @@ pub fn check_path(
autofix: flags::Autofix,
noqa: flags::Noqa,
) -> Result<Vec<Diagnostic>> {
// Validate the `Settings` and return any errors.
settings.validate()?;
// Aggregate all diagnostics.
let mut diagnostics: Vec<Diagnostic> = vec![];
@@ -187,9 +184,6 @@ const MAX_ITERATIONS: usize = 100;
/// Add any missing `#noqa` pragmas to the source code at the given `Path`.
pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
// Validate the `Settings` and return any errors.
settings.validate()?;
// Read the file from disk.
let contents = fs::read_file(path)?;

View File

@@ -46,7 +46,7 @@ macro_rules! notify_user {
}
}
#[derive(Debug, Default, PartialOrd, Ord, PartialEq, Eq)]
#[derive(Debug, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Clone)]
pub enum LogLevel {
// No output (+ `log::LevelFilter::Off`).
Silent,
@@ -60,6 +60,7 @@ pub enum LogLevel {
}
impl LogLevel {
#[allow(clippy::trivially_copy_pass_by_ref)]
fn level_filter(&self) -> log::LevelFilter {
match self {
LogLevel::Default => log::LevelFilter::Info,

View File

@@ -211,6 +211,7 @@ mod tests {
use crate::ast::types::Range;
use crate::noqa::{add_noqa_inner, NOQA_LINE_REGEX};
use crate::registry::Diagnostic;
use crate::rules::pycodestyle::rules::AmbiguousVariableName;
use crate::settings::hashable::HashableHashSet;
use crate::source_code::LineEnding;
use crate::violations;
@@ -264,7 +265,7 @@ mod tests {
let diagnostics = vec![
Diagnostic::new(
violations::AmbiguousVariableName("x".to_string()),
AmbiguousVariableName("x".to_string()),
Range::new(Location::new(1, 0), Location::new(1, 0)),
),
Diagnostic::new(
@@ -287,7 +288,7 @@ mod tests {
let diagnostics = vec![
Diagnostic::new(
violations::AmbiguousVariableName("x".to_string()),
AmbiguousVariableName("x".to_string()),
Range::new(Location::new(1, 0), Location::new(1, 0)),
),
Diagnostic::new(

View File

@@ -13,3 +13,12 @@ pub fn is_identifier(s: &str) -> bool {
// Are the rest of the characters letters, digits, or underscores?
s.chars().skip(1).all(|c| c.is_alphanumeric() || c == '_')
}
/// Returns `true` if a string is a private identifier, such that, when the
/// identifier is defined in a class definition, it will be mangled prior to
/// code generation.
///
/// See: <https://docs.python.org/3.5/reference/expressions.html?highlight=mangling#index-5>.
pub fn is_mangled_private(id: &str) -> bool {
id.starts_with("__") && !id.ends_with("__")
}

View File

@@ -49,7 +49,7 @@ pub static TYPING_EXTENSIONS: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
"assert_type",
"clear_overloads",
"final",
"get_Type_hints",
"get_type_hints",
"get_args",
"get_origin",
"get_overloads",

View File

@@ -13,26 +13,26 @@ use crate::{rules, violations};
ruff_macros::define_rule_mapping!(
// pycodestyle errors
E101 => violations::MixedSpacesAndTabs,
E101 => rules::pycodestyle::rules::MixedSpacesAndTabs,
E401 => violations::MultipleImportsOnOneLine,
E402 => violations::ModuleImportNotAtTopOfFile,
E501 => violations::LineTooLong,
E711 => violations::NoneComparison,
E712 => violations::TrueFalseComparison,
E713 => violations::NotInTest,
E714 => violations::NotIsTest,
E721 => violations::TypeComparison,
E722 => violations::DoNotUseBareExcept,
E731 => violations::DoNotAssignLambda,
E741 => violations::AmbiguousVariableName,
E742 => violations::AmbiguousClassName,
E743 => violations::AmbiguousFunctionName,
E501 => rules::pycodestyle::rules::LineTooLong,
E711 => rules::pycodestyle::rules::NoneComparison,
E712 => rules::pycodestyle::rules::TrueFalseComparison,
E713 => rules::pycodestyle::rules::NotInTest,
E714 => rules::pycodestyle::rules::NotIsTest,
E721 => rules::pycodestyle::rules::TypeComparison,
E722 => rules::pycodestyle::rules::DoNotUseBareExcept,
E731 => rules::pycodestyle::rules::DoNotAssignLambda,
E741 => rules::pycodestyle::rules::AmbiguousVariableName,
E742 => rules::pycodestyle::rules::AmbiguousClassName,
E743 => rules::pycodestyle::rules::AmbiguousFunctionName,
E902 => violations::IOError,
E999 => violations::SyntaxError,
// pycodestyle warnings
W292 => violations::NoNewLineAtEndOfFile,
W505 => violations::DocLineTooLong,
W605 => violations::InvalidEscapeSequence,
W292 => rules::pycodestyle::rules::NoNewLineAtEndOfFile,
W505 => rules::pycodestyle::rules::DocLineTooLong,
W605 => rules::pycodestyle::rules::InvalidEscapeSequence,
// pyflakes
F401 => violations::UnusedImport,
F402 => violations::ImportShadowedByLoopVar,
@@ -78,6 +78,8 @@ ruff_macros::define_rule_mapping!(
F842 => violations::UnusedAnnotation,
F901 => violations::RaiseNotImplemented,
// pylint
PLE0604 => rules::pylint::rules::InvalidAllObject,
PLE0605 => rules::pylint::rules::InvalidAllFormat,
PLC0414 => violations::UselessImportAlias,
PLC3002 => violations::UnnecessaryDirectLambdaCall,
PLE0117 => violations::NonlocalWithoutBinding,
@@ -318,10 +320,10 @@ ruff_macros::define_rule_mapping!(
N817 => violations::CamelcaseImportedAsAcronym,
N818 => violations::ErrorSuffixOnExceptionName,
// isort
I001 => violations::UnsortedImports,
I002 => violations::MissingRequiredImport,
I001 => rules::isort::rules::UnsortedImports,
I002 => rules::isort::rules::MissingRequiredImport,
// eradicate
ERA001 => violations::CommentedOutCode,
ERA001 => rules::eradicate::rules::CommentedOutCode,
// flake8-bandit
S101 => violations::AssertUsed,
S102 => violations::ExecUsed,
@@ -331,6 +333,7 @@ ruff_macros::define_rule_mapping!(
S106 => violations::HardcodedPasswordFuncArg,
S107 => violations::HardcodedPasswordDefault,
S108 => violations::HardcodedTempFile,
S110 => rules::flake8_bandit::rules::TryExceptPass,
S113 => violations::RequestWithoutTimeout,
S324 => violations::HashlibInsecureHashFunction,
S501 => violations::RequestWithNoCertValidation,
@@ -340,9 +343,9 @@ ruff_macros::define_rule_mapping!(
S612 => rules::flake8_bandit::rules::LoggingConfigInsecureListen,
S701 => violations::Jinja2AutoescapeFalse,
// flake8-boolean-trap
FBT001 => violations::BooleanPositionalArgInFunctionDefinition,
FBT002 => violations::BooleanDefaultValueInFunctionDefinition,
FBT003 => violations::BooleanPositionalValueInFunctionCall,
FBT001 => rules::flake8_boolean_trap::rules::BooleanPositionalArgInFunctionDefinition,
FBT002 => rules::flake8_boolean_trap::rules::BooleanDefaultValueInFunctionDefinition,
FBT003 => rules::flake8_boolean_trap::rules::BooleanPositionalValueInFunctionCall,
// flake8-unused-arguments
ARG001 => violations::UnusedFunctionArgument,
ARG002 => violations::UnusedMethodArgument,
@@ -350,7 +353,7 @@ ruff_macros::define_rule_mapping!(
ARG004 => violations::UnusedStaticMethodArgument,
ARG005 => violations::UnusedLambdaArgument,
// flake8-import-conventions
ICN001 => violations::ImportAliasIsNotConventional,
ICN001 => rules::flake8_import_conventions::rules::ImportAliasIsNotConventional,
// flake8-datetimez
DTZ001 => violations::CallDatetimeWithoutTzinfo,
DTZ002 => violations::CallDatetimeToday,
@@ -410,18 +413,18 @@ ruff_macros::define_rule_mapping!(
PT025 => violations::ErroneousUseFixturesOnFixture,
PT026 => violations::UseFixturesWithoutParameters,
// flake8-pie
PIE790 => violations::NoUnnecessaryPass,
PIE794 => violations::DupeClassFieldDefinitions,
PIE796 => violations::PreferUniqueEnums,
PIE800 => violations::NoUnnecessarySpread,
PIE804 => violations::NoUnnecessaryDictKwargs,
PIE807 => violations::PreferListBuiltin,
PIE790 => rules::flake8_pie::rules::NoUnnecessaryPass,
PIE794 => rules::flake8_pie::rules::DupeClassFieldDefinitions,
PIE796 => rules::flake8_pie::rules::PreferUniqueEnums,
PIE800 => rules::flake8_pie::rules::NoUnnecessarySpread,
PIE804 => rules::flake8_pie::rules::NoUnnecessaryDictKwargs,
PIE807 => rules::flake8_pie::rules::PreferListBuiltin,
// flake8-commas
COM812 => violations::TrailingCommaMissing,
COM818 => violations::TrailingCommaOnBareTupleProhibited,
COM819 => violations::TrailingCommaProhibited,
COM812 => rules::flake8_commas::rules::TrailingCommaMissing,
COM818 => rules::flake8_commas::rules::TrailingCommaOnBareTupleProhibited,
COM819 => rules::flake8_commas::rules::TrailingCommaProhibited,
// flake8-no-pep420
INP001 => violations::ImplicitNamespacePackage,
INP001 => rules::flake8_no_pep420::rules::ImplicitNamespacePackage,
// flake8-executable
EXE001 => rules::flake8_executable::rules::ShebangNotExecutable,
EXE002 => rules::flake8_executable::rules::ShebangMissingExecutableFile,
@@ -435,11 +438,14 @@ ruff_macros::define_rule_mapping!(
TCH004 => rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock,
TCH005 => rules::flake8_type_checking::rules::EmptyTypeCheckingBlock,
// tryceratops
TRY002 => rules::tryceratops::rules::RaiseVanillaClass,
TRY003 => rules::tryceratops::rules::RaiseVanillaArgs,
TRY004 => rules::tryceratops::rules::PreferTypeError,
TRY200 => rules::tryceratops::rules::ReraiseNoCause,
TRY201 => rules::tryceratops::rules::VerboseRaise,
TRY300 => rules::tryceratops::rules::TryConsiderElse,
TRY301 => rules::tryceratops::rules::RaiseWithinTry,
TRY400 => rules::tryceratops::rules::ErrorInsteadOfException,
// flake8-use-pathlib
PTH100 => rules::flake8_use_pathlib::violations::PathlibAbspath,
PTH101 => rules::flake8_use_pathlib::violations::PathlibChmod,
@@ -466,6 +472,15 @@ ruff_macros::define_rule_mapping!(
PTH122 => rules::flake8_use_pathlib::violations::PathlibSplitext,
PTH123 => rules::flake8_use_pathlib::violations::PathlibOpen,
PTH124 => rules::flake8_use_pathlib::violations::PathlibPyPath,
// flake8-logging-format
G001 => rules::flake8_logging_format::violations::LoggingStringFormat,
G002 => rules::flake8_logging_format::violations::LoggingPercentFormat,
G003 => rules::flake8_logging_format::violations::LoggingStringConcat,
G004 => rules::flake8_logging_format::violations::LoggingFString,
G010 => rules::flake8_logging_format::violations::LoggingWarn,
G101 => rules::flake8_logging_format::violations::LoggingExtraAttrClash,
G201 => rules::flake8_logging_format::violations::LoggingExcInfo,
G202 => rules::flake8_logging_format::violations::LoggingRedundantExcInfo,
// ruff
RUF001 => violations::AmbiguousUnicodeCharacterString,
RUF002 => violations::AmbiguousUnicodeCharacterDocstring,
@@ -490,15 +505,15 @@ pub enum Linter {
/// [isort](https://pypi.org/project/isort/)
#[prefix = "I"]
Isort,
/// [pep8-naming](https://pypi.org/project/pep8-naming/)
#[prefix = "N"]
PEP8Naming,
/// [pydocstyle](https://pypi.org/project/pydocstyle/)
#[prefix = "D"]
Pydocstyle,
/// [pyupgrade](https://pypi.org/project/pyupgrade/)
#[prefix = "UP"]
Pyupgrade,
/// [pep8-naming](https://pypi.org/project/pep8-naming/)
#[prefix = "N"]
PEP8Naming,
/// [flake8-2020](https://pypi.org/project/flake8-2020/)
#[prefix = "YTT"]
Flake82020,
@@ -520,21 +535,39 @@ pub enum Linter {
/// [flake8-builtins](https://pypi.org/project/flake8-builtins/)
#[prefix = "A"]
Flake8Builtins,
/// [flake8-commas](https://pypi.org/project/flake8-commas/)
#[prefix = "COM"]
Flake8Commas,
/// [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
#[prefix = "C4"]
Flake8Comprehensions,
/// [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
#[prefix = "DTZ"]
Flake8Datetimez,
/// [flake8-debugger](https://pypi.org/project/flake8-debugger/)
#[prefix = "T10"]
Flake8Debugger,
/// [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
#[prefix = "EM"]
Flake8ErrMsg,
/// [flake8-executable](https://pypi.org/project/flake8-executable/)
#[prefix = "EXE"]
Flake8Executable,
/// [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
#[prefix = "ISC"]
Flake8ImplicitStrConcat,
/// [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
#[prefix = "ICN"]
Flake8ImportConventions,
/// [flake8-logging-format](https://pypi.org/project/flake8-logging-format/0.9.0/)
#[prefix = "G"]
Flake8LoggingFormat,
/// [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420/)
#[prefix = "INP"]
Flake8NoPep420,
/// [flake8-pie](https://pypi.org/project/flake8-pie/)
#[prefix = "PIE"]
Flake8Pie,
/// [flake8-print](https://pypi.org/project/flake8-print/)
#[prefix = "T20"]
Flake8Print,
@@ -553,12 +586,15 @@ pub enum Linter {
/// [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
#[prefix = "TID"]
Flake8TidyImports,
/// [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
#[prefix = "TCH"]
Flake8TypeChecking,
/// [flake8-unused-arguments](https://pypi.org/project/flake8-unused-arguments/)
#[prefix = "ARG"]
Flake8UnusedArguments,
/// [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
#[prefix = "DTZ"]
Flake8Datetimez,
/// [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
#[prefix = "PTH"]
Flake8UsePathlib,
/// [eradicate](https://pypi.org/project/eradicate/)
#[prefix = "ERA"]
Eradicate,
@@ -571,27 +607,9 @@ pub enum Linter {
/// [Pylint](https://pypi.org/project/pylint/)
#[prefix = "PL"]
Pylint,
/// [flake8-pie](https://pypi.org/project/flake8-pie/)
#[prefix = "PIE"]
Flake8Pie,
/// [flake8-commas](https://pypi.org/project/flake8-commas/)
#[prefix = "COM"]
Flake8Commas,
/// [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420/)
#[prefix = "INP"]
Flake8NoPep420,
/// [flake8-executable](https://pypi.org/project/flake8-executable/)
#[prefix = "EXE"]
Flake8Executable,
/// [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
#[prefix = "TCH"]
Flake8TypeChecking,
/// [tryceratops](https://pypi.org/project/tryceratops/1.1.0/)
#[prefix = "TRY"]
Tryceratops,
/// [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
#[prefix = "PTH"]
Flake8UsePathlib,
/// Ruff-specific rules
#[prefix = "RUF"]
Ruff,
@@ -717,12 +735,20 @@ impl Diagnostic {
}
/// Pairs of checks that shouldn't be enabled together.
pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str)] = &[(
Rule::OneBlankLineBeforeClass,
Rule::NoBlankLineBeforeClass,
"`D203` (OneBlankLineBeforeClass) and `D211` (NoBlankLinesBeforeClass) are incompatible. \
Consider adding `D203` to `ignore`.",
)];
pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str)] = &[
(
Rule::OneBlankLineBeforeClass,
Rule::NoBlankLineBeforeClass,
"`one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are \
incompatible. Consider ignoring `one-blank-line-before-class`.",
),
(
Rule::MultiLineSummaryFirstLine,
Rule::MultiLineSummarySecondLine,
"`multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are \
incompatible. Consider ignoring one.",
),
];
#[cfg(test)]
mod tests {

View File

@@ -16,13 +16,6 @@ use crate::settings::configuration::Configuration;
use crate::settings::pyproject::settings_toml;
use crate::settings::{pyproject, AllSettings, Settings};
/// The strategy used to discover Python files in the filesystem..
#[derive(Debug)]
pub struct FileDiscovery {
pub force_exclude: bool,
pub respect_gitignore: bool,
}
/// The strategy used to discover the relevant `pyproject.toml` file for each
/// Python file.
#[derive(Debug)]
@@ -35,6 +28,15 @@ pub enum PyprojectDiscovery {
Hierarchical(AllSettings),
}
impl PyprojectDiscovery {
fn top_level_settings(&self) -> &AllSettings {
match self {
PyprojectDiscovery::Fixed(settings) => settings,
PyprojectDiscovery::Hierarchical(settings) => settings,
}
}
}
/// The strategy for resolving file paths in a `pyproject.toml`.
pub enum Relativity {
/// Resolve file paths relative to the current working directory.
@@ -98,26 +100,6 @@ impl Resolver {
pub fn iter(&self) -> impl Iterator<Item = &AllSettings> {
self.settings.values()
}
/// Validate all resolved `Settings` in this `Resolver`.
pub fn validate(&self, strategy: &PyprojectDiscovery) -> Result<()> {
// TODO(charlie): This risks false positives (but not false negatives), since
// some of the `Settings` in the path may ultimately be unused (or, e.g., they
// could have their `required_version` overridden by other `Settings` in
// the path). It'd be preferable to validate once we've determined the
// `Settings` for each path, but that's more expensive.
match &strategy {
PyprojectDiscovery::Fixed(settings) => {
settings.lib.validate()?;
}
PyprojectDiscovery::Hierarchical(default) => {
for settings in std::iter::once(default).chain(self.iter()) {
settings.lib.validate()?;
}
}
}
Ok(())
}
}
pub trait ConfigProcessor: Copy + Send + Sync {
@@ -232,7 +214,6 @@ pub fn is_python_entry(entry: &DirEntry) -> bool {
pub fn python_files_in_path(
paths: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery,
file_strategy: &FileDiscovery,
processor: impl ConfigProcessor,
) -> Result<(Vec<Result<DirEntry, ignore::Error>>, Resolver)> {
// Normalize every path (e.g., convert from relative to absolute).
@@ -256,7 +237,7 @@ pub fn python_files_in_path(
}
// Check if the paths themselves are excluded.
if file_strategy.force_exclude {
if pyproject_strategy.top_level_settings().lib.force_exclude {
paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_strategy));
if paths.is_empty() {
return Ok((vec![], resolver));
@@ -272,7 +253,12 @@ pub fn python_files_in_path(
for path in &paths[1..] {
builder.add(path);
}
builder.standard_filters(file_strategy.respect_gitignore);
builder.standard_filters(
pyproject_strategy
.top_level_settings()
.lib
.respect_gitignore,
);
builder.hidden(false);
let walker = builder.build_parallel();
@@ -369,10 +355,9 @@ pub fn python_files_in_path(
pub fn python_file_at_path(
path: &Path,
pyproject_strategy: &PyprojectDiscovery,
file_strategy: &FileDiscovery,
processor: impl ConfigProcessor,
) -> Result<bool> {
if !file_strategy.force_exclude {
if !pyproject_strategy.top_level_settings().lib.force_exclude {
return Ok(true);
}

View File

@@ -1,12 +1,28 @@
use ruff_macros::derive_message_formats;
use rustpython_ast::Location;
use super::detection::comment_contains_code;
use crate::ast::types::Range;
use crate::define_violation;
use crate::fix::Fix;
use crate::registry::{Diagnostic, Rule};
use crate::settings::{flags, Settings};
use crate::source_code::Locator;
use crate::violations;
use crate::violation::AlwaysAutofixableViolation;
define_violation!(
pub struct CommentedOutCode;
);
impl AlwaysAutofixableViolation for CommentedOutCode {
#[derive_message_formats]
fn message(&self) -> String {
format!("Found commented-out code")
}
fn autofix_title(&self) -> String {
"Remove commented-out code".to_string()
}
}
fn is_standalone_comment(line: &str) -> bool {
for char in line.chars() {
@@ -33,7 +49,7 @@ pub fn commented_out_code(
// Verify that the comment is on its own line, and that it contains code.
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
let mut diagnostic = Diagnostic::new(violations::CommentedOutCode, Range::new(start, end));
let mut diagnostic = Diagnostic::new(CommentedOutCode, Range::new(start, end));
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::CommentedOutCode)
{

View File

@@ -125,8 +125,8 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
// ANN002, ANN401
if let Some(arg) = &args.vararg {
has_any_typed_arg = true;
if let Some(expr) = &arg.node.annotation {
has_any_typed_arg = true;
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker
.settings
@@ -153,8 +153,8 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
// ANN003, ANN401
if let Some(arg) = &args.kwarg {
has_any_typed_arg = true;
if let Some(expr) = &arg.node.annotation {
has_any_typed_arg = true;
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker
.settings

View File

@@ -48,4 +48,22 @@ expression: diagnostics
column: 12
fix: ~
parent: ~
- kind:
MissingReturnTypeSpecialMethod: __init__
location:
row: 47
column: 8
end_location:
row: 47
column: 16
fix:
content:
- " -> None"
location:
row: 47
column: 28
end_location:
row: 47
column: 28
parent: ~

View File

@@ -31,6 +31,7 @@ mod tests {
#[test_case(Rule::SnmpWeakCryptography, Path::new("S509.py"); "S509")]
#[test_case(Rule::LoggingConfigInsecureListen, Path::new("S612.py"); "S612")]
#[test_case(Rule::Jinja2AutoescapeFalse, Path::new("S701.py"); "S701")]
#[test_case(Rule::TryExceptPass, Path::new("S110.py"); "S110")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let diagnostics = test_path(
@@ -55,6 +56,7 @@ mod tests {
"/dev/shm".to_string(),
"/foo".to_string(),
],
check_typed_exception: false,
},
..Settings::for_rule(Rule::HardcodedTempFile)
},
@@ -62,4 +64,20 @@ mod tests {
assert_yaml_snapshot!("S108_extend", diagnostics);
Ok(())
}
#[test]
fn check_typed_exception() -> Result<()> {
let diagnostics = test_path(
Path::new("./resources/test/fixtures/flake8_bandit/S110.py"),
&Settings {
flake8_bandit: super::settings::Settings {
check_typed_exception: true,
..Default::default()
},
..Settings::for_rule(Rule::TryExceptPass)
},
)?;
assert_yaml_snapshot!("S110_typed", diagnostics);
Ok(())
}
}

View File

@@ -17,6 +17,7 @@ pub use request_with_no_cert_validation::request_with_no_cert_validation;
pub use request_without_timeout::request_without_timeout;
pub use snmp_insecure_version::snmp_insecure_version;
pub use snmp_weak_cryptography::snmp_weak_cryptography;
pub use try_except_pass::{try_except_pass, TryExceptPass};
pub use unsafe_yaml_load::unsafe_yaml_load;
mod assert_used;
@@ -34,4 +35,5 @@ mod request_with_no_cert_validation;
mod request_without_timeout;
mod snmp_insecure_version;
mod snmp_weak_cryptography;
mod try_except_pass;
mod unsafe_yaml_load;

View File

@@ -0,0 +1,43 @@
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct TryExceptPass;
);
impl Violation for TryExceptPass {
#[derive_message_formats]
fn message(&self) -> String {
format!("`try`-`except`-`pass` detected, consider logging the exception")
}
}
/// S110
pub fn try_except_pass(
checker: &mut Checker,
type_: Option<&Expr>,
_name: Option<&str>,
body: &[Stmt],
check_typed_exception: bool,
) {
if body.len() == 1
&& body[0].node == StmtKind::Pass
&& (check_typed_exception
|| type_.map_or(true, |type_| {
checker.resolve_call_path(type_).map_or(true, |call_path| {
call_path.as_slice() == ["", "Exception"]
|| call_path.as_slice() == ["", "BaseException"]
})
}))
{
checker.diagnostics.push(Diagnostic::new(
TryExceptPass,
Range::from_located(&body[0]),
));
}
}

View File

@@ -34,11 +34,20 @@ pub struct Options {
/// A list of directories to consider temporary, in addition to those
/// specified by `hardcoded-tmp-directory`.
pub hardcoded_tmp_directory_extend: Option<Vec<String>>,
#[option(
default = "false",
value_type = "bool",
example = "check-typed-exception = true"
)]
/// Whether to disallow `try`-`except`-`pass` (`S110`) for specific exception types. By default,
/// `try`-`except`-`pass` is only disallowed for `Exception` and `BaseException`.
pub check_typed_exception: Option<bool>,
}
#[derive(Debug, Hash)]
pub struct Settings {
pub hardcoded_tmp_directory: Vec<String>,
pub check_typed_exception: bool,
}
impl From<Options> for Settings {
@@ -55,6 +64,7 @@ impl From<Options> for Settings {
.into_iter(),
)
.collect(),
check_typed_exception: options.check_typed_exception.unwrap_or(false),
}
}
}
@@ -64,6 +74,7 @@ impl From<Settings> for Options {
Self {
hardcoded_tmp_directory: Some(settings.hardcoded_tmp_directory),
hardcoded_tmp_directory_extend: None,
check_typed_exception: Some(settings.check_typed_exception),
}
}
}
@@ -72,6 +83,7 @@ impl Default for Settings {
fn default() -> Self {
Self {
hardcoded_tmp_directory: default_tmp_dirs(),
check_typed_exception: false,
}
}
}

View File

@@ -0,0 +1,25 @@
---
source: src/rules/flake8_bandit/mod.rs
expression: diagnostics
---
- kind:
TryExceptPass: ~
location:
row: 4
column: 4
end_location:
row: 4
column: 8
fix: ~
parent: ~
- kind:
TryExceptPass: ~
location:
row: 9
column: 4
end_location:
row: 9
column: 8
fix: ~
parent: ~

View File

@@ -0,0 +1,35 @@
---
source: src/rules/flake8_bandit/mod.rs
expression: diagnostics
---
- kind:
TryExceptPass: ~
location:
row: 4
column: 4
end_location:
row: 4
column: 8
fix: ~
parent: ~
- kind:
TryExceptPass: ~
location:
row: 9
column: 4
end_location:
row: 9
column: 8
fix: ~
parent: ~
- kind:
TryExceptPass: ~
location:
row: 14
column: 4
end_location:
row: 14
column: 8
fix: ~
parent: ~

View File

@@ -1,5 +1,7 @@
use rustpython_ast::{Expr, ExprKind, Stmt, StmtKind};
use crate::ast::helpers;
use crate::ast::helpers::{find_keyword, is_const_true};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
@@ -21,7 +23,7 @@ pub fn blind_except(
for exception in ["BaseException", "Exception"] {
if id == exception && checker.is_builtin(exception) {
// If the exception is re-raised, don't flag an error.
if !body.iter().any(|stmt| {
if body.iter().any(|stmt| {
if let StmtKind::Raise { exc, .. } = &stmt.node {
if let Some(exc) = exc {
if let ExprKind::Name { id, .. } = &exc.node {
@@ -36,11 +38,38 @@ pub fn blind_except(
false
}
}) {
checker.diagnostics.push(Diagnostic::new(
violations::BlindExcept(id.to_string()),
Range::from_located(type_),
));
continue;
}
// If the exception is logged, don't flag an error.
if body.iter().any(|stmt| {
if let StmtKind::Expr { value } = &stmt.node {
if let ExprKind::Call { func, keywords, .. } = &value.node {
if helpers::is_logger_candidate(func) {
if let ExprKind::Attribute { attr, .. } = &func.node {
if attr == "exception" {
return true;
}
if attr == "error" {
if let Some(keyword) = find_keyword(keywords, "exc_info") {
if is_const_true(&keyword.node.value) {
return true;
}
}
}
}
}
}
}
false
}) {
continue;
}
checker.diagnostics.push(Diagnostic::new(
violations::BlindExcept(id.to_string()),
Range::from_located(type_),
));
}
}
}

Some files were not shown because too many files have changed in this diff Show More