Compare commits
97 Commits
v0.3.0
...
pythonplus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f97547b5f | ||
|
|
e944c16c46 | ||
|
|
5f40371ffc | ||
|
|
f7802ad5de | ||
|
|
e832327a56 | ||
|
|
324390607c | ||
|
|
4db5c29f19 | ||
|
|
e9d3f71c90 | ||
|
|
7b3ee2daff | ||
|
|
c2e15f38ee | ||
|
|
d59433b12e | ||
|
|
2bf1882398 | ||
|
|
c269c1a706 | ||
|
|
32d6f84e3d | ||
|
|
93d582d734 | ||
|
|
05b406080a | ||
|
|
3ed707f245 | ||
|
|
c56fb6e15a | ||
|
|
dbf82233b8 | ||
|
|
87afe36c87 | ||
|
|
704fefc7ab | ||
|
|
dacec7377c | ||
|
|
b669306c87 | ||
|
|
b117f33075 | ||
|
|
c746912b9e | ||
|
|
fc7139d9a5 | ||
|
|
f8f56186b3 | ||
|
|
02fc521369 | ||
|
|
4b0666919b | ||
|
|
06284c3700 | ||
|
|
8d73866f70 | ||
|
|
bc693ea13a | ||
|
|
ad84eedc18 | ||
|
|
96a4f95a44 | ||
|
|
bae26b49a6 | ||
|
|
3d7adbc0ed | ||
|
|
c6456b882c | ||
|
|
49eb97879a | ||
|
|
0c84fbb6db | ||
|
|
a892fc755d | ||
|
|
a067d87ccc | ||
|
|
b64f2ea401 | ||
|
|
4bce801065 | ||
|
|
a56d42f183 | ||
|
|
1d97f27335 | ||
|
|
965adbed4b | ||
|
|
c504d7ab11 | ||
|
|
72c9f7e4c9 | ||
|
|
57be3fce90 | ||
|
|
7a675cd822 | ||
|
|
7b4a73d421 | ||
|
|
91af5a4b74 | ||
|
|
461cdad53a | ||
|
|
b9264a5a11 | ||
|
|
ea79f616bc | ||
|
|
f999b1b617 | ||
|
|
fe6afbe406 | ||
|
|
cbd927f346 | ||
|
|
6159a8e532 | ||
|
|
8ea5b08700 | ||
|
|
4c05c258de | ||
|
|
af6ea2f5e4 | ||
|
|
46ab9dec18 | ||
|
|
d441338358 | ||
|
|
72599dafb6 | ||
|
|
7eaec300dd | ||
|
|
184241f99a | ||
|
|
8b749e1d4d | ||
|
|
8dde81a905 | ||
|
|
00300c0d9d | ||
|
|
a6d892b1f4 | ||
|
|
ba4328226d | ||
|
|
64f66cd8fe | ||
|
|
4eac9baf43 | ||
|
|
c27e048ff2 | ||
|
|
737fcfd79e | ||
|
|
84bf333031 | ||
|
|
db25a563f7 | ||
|
|
e725b6fdaf | ||
|
|
fb05d218c3 | ||
|
|
ba7f6783e9 | ||
|
|
7515196245 | ||
|
|
c7431828a7 | ||
|
|
39a3031898 | ||
|
|
c007b175ba | ||
|
|
0cd3b07efa | ||
|
|
c59d82a22e | ||
|
|
8b5daaec7d | ||
|
|
0373b51823 | ||
|
|
b82e87790e | ||
|
|
56d445add9 | ||
|
|
8ecdf5369a | ||
|
|
c9931a548f | ||
|
|
8e0a70cfa3 | ||
|
|
cbafae022d | ||
|
|
cea59b4425 | ||
|
|
40186a26ef |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
crates/ruff_linter/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
|
crates/ruff_linter/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
|
||||||
crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
|
crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
|
||||||
|
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_2.py text eol=crlf
|
||||||
|
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py text eol=crlf
|
||||||
|
|
||||||
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
|
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
|
||||||
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf
|
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -3,6 +3,8 @@ Thank you for taking the time to report an issue! We're glad to have you involve
|
|||||||
|
|
||||||
If you're filing a bug report, please consider including the following information:
|
If you're filing a bug report, please consider including the following information:
|
||||||
|
|
||||||
|
* List of keywords you searched for before creating this issue. Write them down here so that others can find this issue more easily and help provide feedback.
|
||||||
|
e.g. "RUF001", "unused variable", "Jupyter notebook"
|
||||||
* A minimal code snippet that reproduces the bug.
|
* A minimal code snippet that reproduces the bug.
|
||||||
* The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
|
* The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
|
||||||
* The current Ruff settings (any relevant sections from your `pyproject.toml`).
|
* The current Ruff settings (any relevant sections from your `pyproject.toml`).
|
||||||
|
|||||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -472,7 +472,7 @@ jobs:
|
|||||||
- determine_changes
|
- determine_changes
|
||||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: extractions/setup-just@v1
|
- uses: extractions/setup-just@v2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -517,7 +517,7 @@ jobs:
|
|||||||
path: binaries
|
path: binaries
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- name: "Publish to GitHub"
|
- name: "Publish to GitHub"
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
files: binaries/*
|
files: binaries/*
|
||||||
|
|||||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -1,5 +1,60 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.3.2
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- Improve single-`with` item formatting for Python 3.8 or older ([#10276](https://github.com/astral-sh/ruff/pull/10276))
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- \[`pyupgrade`\] Allow fixes for f-string rule regardless of line length (`UP032`) ([#10263](https://github.com/astral-sh/ruff/pull/10263))
|
||||||
|
- \[`pycodestyle`\] Include actual conditions in E712 diagnostics ([#10254](https://github.com/astral-sh/ruff/pull/10254))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fix trailing kwargs end of line comment after slash ([#10297](https://github.com/astral-sh/ruff/pull/10297))
|
||||||
|
- Fix unstable `with` items formatting ([#10274](https://github.com/astral-sh/ruff/pull/10274))
|
||||||
|
- Avoid repeating function calls in f-string conversions ([#10265](https://github.com/astral-sh/ruff/pull/10265))
|
||||||
|
- Fix E203 false positive for slices in format strings ([#10280](https://github.com/astral-sh/ruff/pull/10280))
|
||||||
|
- Fix incorrect `Parameter` range for `*args` and `**kwargs` ([#10283](https://github.com/astral-sh/ruff/pull/10283))
|
||||||
|
- Treat `typing.Annotated` subscripts as type definitions ([#10285](https://github.com/astral-sh/ruff/pull/10285))
|
||||||
|
|
||||||
|
## 0.3.1
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`pycodestyle`\] Fix E301 not triggering on decorated methods. ([#10117](https://github.com/astral-sh/ruff/pull/10117))
|
||||||
|
- \[`pycodestyle`\] Respect `isort` settings in blank line rules (`E3*`) ([#10096](https://github.com/astral-sh/ruff/pull/10096))
|
||||||
|
- \[`pycodestyle`\] Make blank lines in typing stub files optional (`E3*`) ([#10098](https://github.com/astral-sh/ruff/pull/10098))
|
||||||
|
- \[`pylint`\] Implement `singledispatch-method` (`E1519`) ([#10140](https://github.com/astral-sh/ruff/pull/10140))
|
||||||
|
- \[`pylint`\] Implement `useless-exception-statement` (`W0133`) ([#10176](https://github.com/astral-sh/ruff/pull/10176))
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- \[`flake8-debugger`\] Check for use of `debugpy` and `ptvsd` debug modules (#10177) ([#10194](https://github.com/astral-sh/ruff/pull/10194))
|
||||||
|
- \[`pyupgrade`\] Generate diagnostic for all valid f-string conversions regardless of line length (`UP032`) ([#10238](https://github.com/astral-sh/ruff/pull/10238))
|
||||||
|
- \[`pep8_naming`\] Add fixes for `N804` and `N805` ([#10215](https://github.com/astral-sh/ruff/pull/10215))
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- Colorize the output of `ruff format --diff` ([#10110](https://github.com/astral-sh/ruff/pull/10110))
|
||||||
|
- Make `--config` and `--isolated` global flags ([#10150](https://github.com/astral-sh/ruff/pull/10150))
|
||||||
|
- Correctly expand tildes and environment variables in paths passed to `--config` ([#10219](https://github.com/astral-sh/ruff/pull/10219))
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- Accept a PEP 440 version specifier for `required-version` ([#10216](https://github.com/astral-sh/ruff/pull/10216))
|
||||||
|
- Implement isort's `default-section` setting ([#10149](https://github.com/astral-sh/ruff/pull/10149))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Remove trailing space from `CapWords` message ([#10220](https://github.com/astral-sh/ruff/pull/10220))
|
||||||
|
- Respect external codes in file-level exemptions ([#10203](https://github.com/astral-sh/ruff/pull/10203))
|
||||||
|
- \[`flake8-raise`\] Avoid false-positives for parens-on-raise with `future.exception()` (`RSE102`) ([#10206](https://github.com/astral-sh/ruff/pull/10206))
|
||||||
|
- \[`pylint`\] Add fix for unary expressions in `PLC2801` ([#9587](https://github.com/astral-sh/ruff/pull/9587))
|
||||||
|
- \[`ruff`\] Fix RUF028 not allowing `# fmt: skip` on match cases ([#10178](https://github.com/astral-sh/ruff/pull/10178))
|
||||||
|
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
|
|
||||||
This release introduces the new Ruff formatter 2024.2 style and adds a new lint rule to
|
This release introduces the new Ruff formatter 2024.2 style and adds a new lint rule to
|
||||||
|
|||||||
@@ -329,13 +329,13 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
|||||||
|
|
||||||
### Creating a new release
|
### Creating a new release
|
||||||
|
|
||||||
We use an experimental in-house tool for managing releases.
|
1. Install `uv`: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
||||||
|
1. Run `./scripts/release/bump.sh`; this command will:
|
||||||
1. Install `rooster`: `pip install git+https://github.com/zanieb/rooster@main`
|
- Generate a temporary virtual environment with `rooster`
|
||||||
1. Run `rooster release`; this command will:
|
|
||||||
- Generate a changelog entry in `CHANGELOG.md`
|
- Generate a changelog entry in `CHANGELOG.md`
|
||||||
- Update versions in `pyproject.toml` and `Cargo.toml`
|
- Update versions in `pyproject.toml` and `Cargo.toml`
|
||||||
- Update references to versions in the `README.md` and documentation
|
- Update references to versions in the `README.md` and documentation
|
||||||
|
- Display contributors for the release
|
||||||
1. The changelog should then be editorialized for consistency
|
1. The changelog should then be editorialized for consistency
|
||||||
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
|
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
|
||||||
- Changes should be edited to be user-facing descriptions, avoiding internal details
|
- Changes should be edited to be user-facing descriptions, avoiding internal details
|
||||||
@@ -359,7 +359,7 @@ We use an experimental in-house tool for managing releases.
|
|||||||
1. Open the draft release in the GitHub release section
|
1. Open the draft release in the GitHub release section
|
||||||
1. Copy the changelog for the release into the GitHub release
|
1. Copy the changelog for the release into the GitHub release
|
||||||
- See previous releases for formatting of section headers
|
- See previous releases for formatting of section headers
|
||||||
1. Generate the contributor list with `rooster contributors` and add to the release notes
|
1. Append the contributors from the `bump.sh` script
|
||||||
1. If needed, [update the schemastore](https://github.com/astral-sh/ruff/blob/main/scripts/update_schemastore.py).
|
1. If needed, [update the schemastore](https://github.com/astral-sh/ruff/blob/main/scripts/update_schemastore.py).
|
||||||
1. One can determine if an update is needed when
|
1. One can determine if an update is needed when
|
||||||
`git diff old-version-tag new-version-tag -- ruff.schema.json` returns a non-empty diff.
|
`git diff old-version-tag new-version-tag -- ruff.schema.json` returns a non-empty diff.
|
||||||
|
|||||||
466
Cargo.lock
generated
466
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
17
Cargo.toml
@@ -21,8 +21,8 @@ bincode = { version = "1.3.3" }
|
|||||||
bitflags = { version = "2.4.1" }
|
bitflags = { version = "2.4.1" }
|
||||||
bstr = { version = "1.9.1" }
|
bstr = { version = "1.9.1" }
|
||||||
cachedir = { version = "0.3.1" }
|
cachedir = { version = "0.3.1" }
|
||||||
chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
|
chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
|
||||||
clap = { version = "4.5.1", features = ["derive"] }
|
clap = { version = "4.5.2", features = ["derive"] }
|
||||||
clap_complete_command = { version = "0.5.1" }
|
clap_complete_command = { version = "0.5.1" }
|
||||||
clearscreen = { version = "2.0.0" }
|
clearscreen = { version = "2.0.0" }
|
||||||
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
|
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
|
||||||
@@ -32,6 +32,7 @@ console_error_panic_hook = { version = "0.1.7" }
|
|||||||
console_log = { version = "1.0.0" }
|
console_log = { version = "1.0.0" }
|
||||||
countme = { version = "3.0.1" }
|
countme = { version = "3.0.1" }
|
||||||
criterion = { version = "0.5.1", default-features = false }
|
criterion = { version = "0.5.1", default-features = false }
|
||||||
|
crossbeam = { version = "0.8.4" }
|
||||||
dirs = { version = "5.0.0" }
|
dirs = { version = "5.0.0" }
|
||||||
drop_bomb = { version = "0.1.5" }
|
drop_bomb = { version = "0.1.5" }
|
||||||
env_logger = { version = "0.10.1" }
|
env_logger = { version = "0.10.1" }
|
||||||
@@ -51,11 +52,15 @@ insta-cmd = { version = "0.4.0" }
|
|||||||
is-macro = { version = "0.3.5" }
|
is-macro = { version = "0.3.5" }
|
||||||
is-wsl = { version = "0.4.0" }
|
is-wsl = { version = "0.4.0" }
|
||||||
itertools = { version = "0.12.1" }
|
itertools = { version = "0.12.1" }
|
||||||
js-sys = { version = "0.3.67" }
|
js-sys = { version = "0.3.69" }
|
||||||
|
jod-thread = { version = "0.1.2" }
|
||||||
lalrpop-util = { version = "0.20.0", default-features = false }
|
lalrpop-util = { version = "0.20.0", default-features = false }
|
||||||
lexical-parse-float = { version = "0.8.0", features = ["format"] }
|
lexical-parse-float = { version = "0.8.0", features = ["format"] }
|
||||||
|
libc = { version = "0.2.153" }
|
||||||
libcst = { version = "1.1.0", default-features = false }
|
libcst = { version = "1.1.0", default-features = false }
|
||||||
log = { version = "0.4.17" }
|
log = { version = "0.4.17" }
|
||||||
|
lsp-server = { version = "0.7.6" }
|
||||||
|
lsp-types = { version = "0.95.0", features = ["proposed"] }
|
||||||
memchr = { version = "2.7.1" }
|
memchr = { version = "2.7.1" }
|
||||||
mimalloc = { version = "0.1.39" }
|
mimalloc = { version = "0.1.39" }
|
||||||
natord = { version = "1.0.9" }
|
natord = { version = "1.0.9" }
|
||||||
@@ -76,7 +81,6 @@ result-like = { version = "0.5.0" }
|
|||||||
rustc-hash = { version = "1.1.0" }
|
rustc-hash = { version = "1.1.0" }
|
||||||
schemars = { version = "0.8.16" }
|
schemars = { version = "0.8.16" }
|
||||||
seahash = { version = "4.1.0" }
|
seahash = { version = "4.1.0" }
|
||||||
semver = { version = "1.0.22" }
|
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
serde-wasm-bindgen = { version = "0.6.4" }
|
serde-wasm-bindgen = { version = "0.6.4" }
|
||||||
serde_json = { version = "1.0.113" }
|
serde_json = { version = "1.0.113" }
|
||||||
@@ -98,16 +102,17 @@ toml = { version = "0.8.9" }
|
|||||||
tracing = { version = "0.1.40" }
|
tracing = { version = "0.1.40" }
|
||||||
tracing-indicatif = { version = "0.3.6" }
|
tracing-indicatif = { version = "0.3.6" }
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
|
tracing-tree = { version = "0.2.4" }
|
||||||
typed-arena = { version = "2.0.2" }
|
typed-arena = { version = "2.0.2" }
|
||||||
unic-ucd-category = { version = "0.9" }
|
unic-ucd-category = { version = "0.9" }
|
||||||
unicode-ident = { version = "1.0.12" }
|
unicode-ident = { version = "1.0.12" }
|
||||||
unicode-width = { version = "0.1.11" }
|
unicode-width = { version = "0.1.11" }
|
||||||
unicode_names2 = { version = "1.2.1" }
|
unicode_names2 = { version = "1.2.2" }
|
||||||
ureq = { version = "2.9.6" }
|
ureq = { version = "2.9.6" }
|
||||||
url = { version = "2.5.0" }
|
url = { version = "2.5.0" }
|
||||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||||
walkdir = { version = "2.3.2" }
|
walkdir = { version = "2.3.2" }
|
||||||
wasm-bindgen = { version = "0.2.84" }
|
wasm-bindgen = { version = "0.2.92" }
|
||||||
wasm-bindgen-test = { version = "0.3.40" }
|
wasm-bindgen-test = { version = "0.3.40" }
|
||||||
wild = { version = "2" }
|
wild = { version = "2" }
|
||||||
|
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -7,8 +7,9 @@
|
|||||||
[](https://pypi.python.org/pypi/ruff)
|
[](https://pypi.python.org/pypi/ruff)
|
||||||
[](https://pypi.python.org/pypi/ruff)
|
[](https://pypi.python.org/pypi/ruff)
|
||||||
[](https://github.com/astral-sh/ruff/actions)
|
[](https://github.com/astral-sh/ruff/actions)
|
||||||
|
[](https://discord.com/invite/astral-sh)
|
||||||
|
|
||||||
[**Discord**](https://discord.com/invite/astral-sh) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
|
[**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
|
||||||
|
|
||||||
An extremely fast Python linter and code formatter, written in Rust.
|
An extremely fast Python linter and code formatter, written in Rust.
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ and with [a variety of other package managers](https://docs.astral.sh/ruff/insta
|
|||||||
To run Ruff as a linter, try any of the following:
|
To run Ruff as a linter, try any of the following:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
ruff check . # Lint all files in the current directory (and any subdirectories).
|
ruff check # Lint all files in the current directory (and any subdirectories).
|
||||||
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories).
|
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories).
|
||||||
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`.
|
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`.
|
||||||
ruff check path/to/code/to/file.py # Lint `file.py`.
|
ruff check path/to/code/to/file.py # Lint `file.py`.
|
||||||
@@ -138,7 +139,7 @@ ruff check @arguments.txt # Lint using an input file, treating its con
|
|||||||
Or, to run Ruff as a formatter:
|
Or, to run Ruff as a formatter:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
ruff format . # Format all files in the current directory (and any subdirectories).
|
ruff format # Format all files in the current directory (and any subdirectories).
|
||||||
ruff format path/to/code/ # Format all files in `/path/to/code` (and any subdirectories).
|
ruff format path/to/code/ # Format all files in `/path/to/code` (and any subdirectories).
|
||||||
ruff format path/to/code/*.py # Format all `.py` files in `/path/to/code`.
|
ruff format path/to/code/*.py # Format all `.py` files in `/path/to/code`.
|
||||||
ruff format path/to/code/to/file.py # Format `file.py`.
|
ruff format path/to/code/to/file.py # Format `file.py`.
|
||||||
@@ -150,7 +151,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
|||||||
```yaml
|
```yaml
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.3.0
|
rev: v0.3.2
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
@@ -182,10 +183,9 @@ Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml`
|
|||||||
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
|
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
|
||||||
for a complete list of all configuration options).
|
for a complete list of all configuration options).
|
||||||
|
|
||||||
If left unspecified, Ruff's default configuration is equivalent to:
|
If left unspecified, Ruff's default configuration is equivalent to the following `ruff.toml` file:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[tool.ruff]
|
|
||||||
# Exclude a variety of commonly ignored directories.
|
# Exclude a variety of commonly ignored directories.
|
||||||
exclude = [
|
exclude = [
|
||||||
".bzr",
|
".bzr",
|
||||||
@@ -223,7 +223,7 @@ indent-width = 4
|
|||||||
# Assume Python 3.8
|
# Assume Python 3.8
|
||||||
target-version = "py38"
|
target-version = "py38"
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[lint]
|
||||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||||
select = ["E4", "E7", "E9", "F"]
|
select = ["E4", "E7", "E9", "F"]
|
||||||
ignore = []
|
ignore = []
|
||||||
@@ -235,7 +235,7 @@ unfixable = []
|
|||||||
# Allow unused variables when underscore-prefixed.
|
# Allow unused variables when underscore-prefixed.
|
||||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
[tool.ruff.format]
|
[format]
|
||||||
# Like Black, use double quotes for strings.
|
# Like Black, use double quotes for strings.
|
||||||
quote-style = "double"
|
quote-style = "double"
|
||||||
|
|
||||||
@@ -249,11 +249,20 @@ skip-magic-trailing-comma = false
|
|||||||
line-ending = "auto"
|
line-ending = "auto"
|
||||||
```
|
```
|
||||||
|
|
||||||
Some configuration options can be provided via the command-line, such as those related to
|
Note that, in a `pyproject.toml`, each section header should be prefixed with `tool.ruff`. For
|
||||||
rule enablement and disablement, file discovery, and logging level:
|
example, `[lint]` should be replaced with `[tool.ruff.lint]`.
|
||||||
|
|
||||||
|
Some configuration options can be provided via dedicated command-line arguments, such as those
|
||||||
|
related to rule enablement and disablement, file discovery, and logging level:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
ruff check path/to/code/ --select F401 --select F403 --quiet
|
ruff check --select F401 --select F403 --quiet
|
||||||
|
```
|
||||||
|
|
||||||
|
The remaining configuration options can be provided through a catch-all `--config` argument:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ruff check --config "lint.per-file-ignores = {'some_file.py' = ['F841']}"
|
||||||
```
|
```
|
||||||
|
|
||||||
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format`
|
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format`
|
||||||
@@ -405,6 +414,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
|||||||
- [Ibis](https://github.com/ibis-project/ibis)
|
- [Ibis](https://github.com/ibis-project/ibis)
|
||||||
- [ivy](https://github.com/unifyai/ivy)
|
- [ivy](https://github.com/unifyai/ivy)
|
||||||
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
||||||
|
- [Kraken Tech](https://kraken.tech/)
|
||||||
- [LangChain](https://github.com/hwchase17/langchain)
|
- [LangChain](https://github.com/hwchase17/langchain)
|
||||||
- [Litestar](https://litestar.dev/)
|
- [Litestar](https://litestar.dev/)
|
||||||
- [LlamaIndex](https://github.com/jerryjliu/llama_index)
|
- [LlamaIndex](https://github.com/jerryjliu/llama_index)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.3.0"
|
version = "0.3.2"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
@@ -20,6 +20,7 @@ ruff_macros = { path = "../ruff_macros" }
|
|||||||
ruff_notebook = { path = "../ruff_notebook" }
|
ruff_notebook = { path = "../ruff_notebook" }
|
||||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||||
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
||||||
|
ruff_server = { path = "../ruff_server" }
|
||||||
ruff_source_file = { path = "../ruff_source_file" }
|
ruff_source_file = { path = "../ruff_source_file" }
|
||||||
ruff_text_size = { path = "../ruff_text_size" }
|
ruff_text_size = { path = "../ruff_text_size" }
|
||||||
ruff_workspace = { path = "../ruff_workspace" }
|
ruff_workspace = { path = "../ruff_workspace" }
|
||||||
@@ -52,6 +53,8 @@ tempfile = { workspace = true }
|
|||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
tracing = { workspace = true, features = ["log"] }
|
tracing = { workspace = true, features = ["log"] }
|
||||||
|
tracing-subscriber = { workspace = true, features = ["registry"]}
|
||||||
|
tracing-tree = { workspace = true }
|
||||||
walkdir = { workspace = true }
|
walkdir = { workspace = true }
|
||||||
wild = { workspace = true }
|
wild = { workspace = true }
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,52 @@ use ruff_workspace::configuration::{Configuration, RuleSelection};
|
|||||||
use ruff_workspace::options::{Options, PycodestyleOptions};
|
use ruff_workspace::options::{Options, PycodestyleOptions};
|
||||||
use ruff_workspace::resolver::ConfigurationTransformer;
|
use ruff_workspace::resolver::ConfigurationTransformer;
|
||||||
|
|
||||||
|
/// All configuration options that can be passed "globally",
|
||||||
|
/// i.e., can be passed to all subcommands
|
||||||
|
#[derive(Debug, Default, Clone, clap::Args)]
|
||||||
|
pub struct GlobalConfigArgs {
|
||||||
|
#[clap(flatten)]
|
||||||
|
log_level_args: LogLevelArgs,
|
||||||
|
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
|
||||||
|
/// or a TOML `<KEY> = <VALUE>` pair
|
||||||
|
/// (such as you might find in a `ruff.toml` configuration file)
|
||||||
|
/// overriding a specific configuration option.
|
||||||
|
/// Overrides of individual settings using this option always take precedence
|
||||||
|
/// over all configuration files, including configuration files that were also
|
||||||
|
/// specified using `--config`.
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
action = clap::ArgAction::Append,
|
||||||
|
value_name = "CONFIG_OPTION",
|
||||||
|
value_parser = ConfigArgumentParser,
|
||||||
|
global = true,
|
||||||
|
help_heading = "Global options",
|
||||||
|
)]
|
||||||
|
pub config: Vec<SingleConfigArgument>,
|
||||||
|
/// Ignore all configuration files.
|
||||||
|
//
|
||||||
|
// Note: We can't mark this as conflicting with `--config` here
|
||||||
|
// as `--config` can be used for specifying configuration overrides
|
||||||
|
// as well as configuration files.
|
||||||
|
// Specifying a configuration file conflicts with `--isolated`;
|
||||||
|
// specifying a configuration override does not.
|
||||||
|
// If a user specifies `ruff check --isolated --config=ruff.toml`,
|
||||||
|
// we emit an error later on, after the initial parsing by clap.
|
||||||
|
#[arg(long, help_heading = "Global options", global = true)]
|
||||||
|
pub isolated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalConfigArgs {
|
||||||
|
pub fn log_level(&self) -> LogLevel {
|
||||||
|
LogLevel::from(&self.log_level_args)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn partition(self) -> (LogLevel, Vec<SingleConfigArgument>, bool) {
|
||||||
|
(self.log_level(), self.config, self.isolated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
author,
|
author,
|
||||||
@@ -38,9 +84,9 @@ use ruff_workspace::resolver::ConfigurationTransformer;
|
|||||||
#[command(version)]
|
#[command(version)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Command,
|
pub(crate) command: Command,
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub log_level_args: LogLevelArgs,
|
pub(crate) global_options: GlobalConfigArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
@@ -80,6 +126,8 @@ pub enum Command {
|
|||||||
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
||||||
/// Run the Ruff formatter on the given files or directories.
|
/// Run the Ruff formatter on the given files or directories.
|
||||||
Format(FormatCommand),
|
Format(FormatCommand),
|
||||||
|
/// Run the language server.
|
||||||
|
Server(ServerCommand),
|
||||||
/// Display Ruff's version
|
/// Display Ruff's version
|
||||||
Version {
|
Version {
|
||||||
#[arg(long, value_enum, default_value = "text")]
|
#[arg(long, value_enum, default_value = "text")]
|
||||||
@@ -153,20 +201,6 @@ pub struct CheckCommand {
|
|||||||
preview: bool,
|
preview: bool,
|
||||||
#[clap(long, overrides_with("preview"), hide = true)]
|
#[clap(long, overrides_with("preview"), hide = true)]
|
||||||
no_preview: bool,
|
no_preview: bool,
|
||||||
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
|
|
||||||
/// or a TOML `<KEY> = <VALUE>` pair
|
|
||||||
/// (such as you might find in a `ruff.toml` configuration file)
|
|
||||||
/// overriding a specific configuration option.
|
|
||||||
/// Overrides of individual settings using this option always take precedence
|
|
||||||
/// over all configuration files, including configuration files that were also
|
|
||||||
/// specified using `--config`.
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
action = clap::ArgAction::Append,
|
|
||||||
value_name = "CONFIG_OPTION",
|
|
||||||
value_parser = ConfigArgumentParser,
|
|
||||||
)]
|
|
||||||
pub config: Vec<SingleConfigArgument>,
|
|
||||||
/// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
|
/// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
@@ -298,17 +332,6 @@ pub struct CheckCommand {
|
|||||||
/// Disable cache reads.
|
/// Disable cache reads.
|
||||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||||
pub no_cache: bool,
|
pub no_cache: bool,
|
||||||
/// Ignore all configuration files.
|
|
||||||
//
|
|
||||||
// Note: We can't mark this as conflicting with `--config` here
|
|
||||||
// as `--config` can be used for specifying configuration overrides
|
|
||||||
// as well as configuration files.
|
|
||||||
// Specifying a configuration file conflicts with `--isolated`;
|
|
||||||
// specifying a configuration override does not.
|
|
||||||
// If a user specifies `ruff check --isolated --config=ruff.toml`,
|
|
||||||
// we emit an error later on, after the initial parsing by clap.
|
|
||||||
#[arg(long, help_heading = "Miscellaneous")]
|
|
||||||
pub isolated: bool,
|
|
||||||
/// Path to the cache directory.
|
/// Path to the cache directory.
|
||||||
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
|
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
|
||||||
pub cache_dir: Option<PathBuf>,
|
pub cache_dir: Option<PathBuf>,
|
||||||
@@ -400,20 +423,6 @@ pub struct FormatCommand {
|
|||||||
/// difference between the current file and how the formatted file would look like.
|
/// difference between the current file and how the formatted file would look like.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub diff: bool,
|
pub diff: bool,
|
||||||
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
|
|
||||||
/// or a TOML `<KEY> = <VALUE>` pair
|
|
||||||
/// (such as you might find in a `ruff.toml` configuration file)
|
|
||||||
/// overriding a specific configuration option.
|
|
||||||
/// Overrides of individual settings using this option always take precedence
|
|
||||||
/// over all configuration files, including configuration files that were also
|
|
||||||
/// specified using `--config`.
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
action = clap::ArgAction::Append,
|
|
||||||
value_name = "CONFIG_OPTION",
|
|
||||||
value_parser = ConfigArgumentParser,
|
|
||||||
)]
|
|
||||||
pub config: Vec<SingleConfigArgument>,
|
|
||||||
|
|
||||||
/// Disable cache reads.
|
/// Disable cache reads.
|
||||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||||
@@ -454,17 +463,6 @@ pub struct FormatCommand {
|
|||||||
/// Set the line-length.
|
/// Set the line-length.
|
||||||
#[arg(long, help_heading = "Format configuration")]
|
#[arg(long, help_heading = "Format configuration")]
|
||||||
pub line_length: Option<LineLength>,
|
pub line_length: Option<LineLength>,
|
||||||
/// Ignore all configuration files.
|
|
||||||
//
|
|
||||||
// Note: We can't mark this as conflicting with `--config` here
|
|
||||||
// as `--config` can be used for specifying configuration overrides
|
|
||||||
// as well as configuration files.
|
|
||||||
// Specifying a configuration file conflicts with `--isolated`;
|
|
||||||
// specifying a configuration override does not.
|
|
||||||
// If a user specifies `ruff check --isolated --config=ruff.toml`,
|
|
||||||
// we emit an error later on, after the initial parsing by clap.
|
|
||||||
#[arg(long, help_heading = "Miscellaneous")]
|
|
||||||
pub isolated: bool,
|
|
||||||
/// The name of the file when passing it through stdin.
|
/// The name of the file when passing it through stdin.
|
||||||
#[arg(long, help_heading = "Miscellaneous")]
|
#[arg(long, help_heading = "Miscellaneous")]
|
||||||
pub stdin_filename: Option<PathBuf>,
|
pub stdin_filename: Option<PathBuf>,
|
||||||
@@ -498,6 +496,13 @@ pub struct FormatCommand {
|
|||||||
pub range: Option<FormatRange>,
|
pub range: Option<FormatRange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, clap::Parser)]
|
||||||
|
pub struct ServerCommand {
|
||||||
|
/// Enable preview mode; required for regular operation
|
||||||
|
#[arg(long)]
|
||||||
|
pub(crate) preview: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||||
pub enum HelpFormat {
|
pub enum HelpFormat {
|
||||||
Text,
|
Text,
|
||||||
@@ -505,7 +510,7 @@ pub enum HelpFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
#[derive(Debug, clap::Args)]
|
#[derive(Debug, Default, Clone, clap::Args)]
|
||||||
pub struct LogLevelArgs {
|
pub struct LogLevelArgs {
|
||||||
/// Enable verbose logging.
|
/// Enable verbose logging.
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -553,6 +558,10 @@ impl From<&LogLevelArgs> for LogLevel {
|
|||||||
/// Configuration-related arguments passed via the CLI.
|
/// Configuration-related arguments passed via the CLI.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ConfigArguments {
|
pub struct ConfigArguments {
|
||||||
|
/// Whether the user specified --isolated on the command line
|
||||||
|
pub(crate) isolated: bool,
|
||||||
|
/// The logging level to be used, derived from command-line arguments passed
|
||||||
|
pub(crate) log_level: LogLevel,
|
||||||
/// Path to a pyproject.toml or ruff.toml configuration file (etc.).
|
/// Path to a pyproject.toml or ruff.toml configuration file (etc.).
|
||||||
/// Either 0 or 1 configuration file paths may be provided on the command line.
|
/// Either 0 or 1 configuration file paths may be provided on the command line.
|
||||||
config_file: Option<PathBuf>,
|
config_file: Option<PathBuf>,
|
||||||
@@ -573,21 +582,19 @@ impl ConfigArguments {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn from_cli_arguments(
|
fn from_cli_arguments(
|
||||||
config_options: Vec<SingleConfigArgument>,
|
global_options: GlobalConfigArgs,
|
||||||
per_flag_overrides: ExplicitConfigOverrides,
|
per_flag_overrides: ExplicitConfigOverrides,
|
||||||
isolated: bool,
|
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let mut new = Self {
|
let (log_level, config_options, isolated) = global_options.partition();
|
||||||
per_flag_overrides,
|
let mut config_file: Option<PathBuf> = None;
|
||||||
..Self::default()
|
let mut overrides = Configuration::default();
|
||||||
};
|
|
||||||
|
|
||||||
for option in config_options {
|
for option in config_options {
|
||||||
match option {
|
match option {
|
||||||
SingleConfigArgument::SettingsOverride(overridden_option) => {
|
SingleConfigArgument::SettingsOverride(overridden_option) => {
|
||||||
let overridden_option = Arc::try_unwrap(overridden_option)
|
let overridden_option = Arc::try_unwrap(overridden_option)
|
||||||
.unwrap_or_else(|option| option.deref().clone());
|
.unwrap_or_else(|option| option.deref().clone());
|
||||||
new.overrides = new.overrides.combine(Configuration::from_options(
|
overrides = overrides.combine(Configuration::from_options(
|
||||||
overridden_option,
|
overridden_option,
|
||||||
None,
|
None,
|
||||||
&path_dedot::CWD,
|
&path_dedot::CWD,
|
||||||
@@ -606,7 +613,7 @@ The argument `--config={}` cannot be used with `--isolated`
|
|||||||
path.display()
|
path.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Some(ref config_file) = new.config_file {
|
if let Some(ref config_file) = config_file {
|
||||||
let (first, second) = (config_file.display(), path.display());
|
let (first, second) = (config_file.display(), path.display());
|
||||||
bail!(
|
bail!(
|
||||||
"\
|
"\
|
||||||
@@ -617,11 +624,17 @@ You cannot specify more than one configuration file on the command line.
|
|||||||
"
|
"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
new.config_file = Some(path);
|
config_file = Some(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(new)
|
Ok(Self {
|
||||||
|
isolated,
|
||||||
|
log_level,
|
||||||
|
config_file,
|
||||||
|
overrides,
|
||||||
|
per_flag_overrides,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,7 +648,10 @@ impl ConfigurationTransformer for ConfigArguments {
|
|||||||
impl CheckCommand {
|
impl CheckCommand {
|
||||||
/// Partition the CLI into command-line arguments and configuration
|
/// Partition the CLI into command-line arguments and configuration
|
||||||
/// overrides.
|
/// overrides.
|
||||||
pub fn partition(self) -> anyhow::Result<(CheckArguments, ConfigArguments)> {
|
pub fn partition(
|
||||||
|
self,
|
||||||
|
global_options: GlobalConfigArgs,
|
||||||
|
) -> anyhow::Result<(CheckArguments, ConfigArguments)> {
|
||||||
let check_arguments = CheckArguments {
|
let check_arguments = CheckArguments {
|
||||||
add_noqa: self.add_noqa,
|
add_noqa: self.add_noqa,
|
||||||
diff: self.diff,
|
diff: self.diff,
|
||||||
@@ -644,7 +660,6 @@ impl CheckCommand {
|
|||||||
exit_zero: self.exit_zero,
|
exit_zero: self.exit_zero,
|
||||||
files: self.files,
|
files: self.files,
|
||||||
ignore_noqa: self.ignore_noqa,
|
ignore_noqa: self.ignore_noqa,
|
||||||
isolated: self.isolated,
|
|
||||||
no_cache: self.no_cache,
|
no_cache: self.no_cache,
|
||||||
output_file: self.output_file,
|
output_file: self.output_file,
|
||||||
show_files: self.show_files,
|
show_files: self.show_files,
|
||||||
@@ -688,8 +703,7 @@ impl CheckCommand {
|
|||||||
extension: self.extension,
|
extension: self.extension,
|
||||||
};
|
};
|
||||||
|
|
||||||
let config_args =
|
let config_args = ConfigArguments::from_cli_arguments(global_options, cli_overrides)?;
|
||||||
ConfigArguments::from_cli_arguments(self.config, cli_overrides, self.isolated)?;
|
|
||||||
Ok((check_arguments, config_args))
|
Ok((check_arguments, config_args))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -697,12 +711,14 @@ impl CheckCommand {
|
|||||||
impl FormatCommand {
|
impl FormatCommand {
|
||||||
/// Partition the CLI into command-line arguments and configuration
|
/// Partition the CLI into command-line arguments and configuration
|
||||||
/// overrides.
|
/// overrides.
|
||||||
pub fn partition(self) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
|
pub fn partition(
|
||||||
|
self,
|
||||||
|
global_options: GlobalConfigArgs,
|
||||||
|
) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
|
||||||
let format_arguments = FormatArguments {
|
let format_arguments = FormatArguments {
|
||||||
check: self.check,
|
check: self.check,
|
||||||
diff: self.diff,
|
diff: self.diff,
|
||||||
files: self.files,
|
files: self.files,
|
||||||
isolated: self.isolated,
|
|
||||||
no_cache: self.no_cache,
|
no_cache: self.no_cache,
|
||||||
stdin_filename: self.stdin_filename,
|
stdin_filename: self.stdin_filename,
|
||||||
range: self.range,
|
range: self.range,
|
||||||
@@ -722,8 +738,7 @@ impl FormatCommand {
|
|||||||
..ExplicitConfigOverrides::default()
|
..ExplicitConfigOverrides::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let config_args =
|
let config_args = ConfigArguments::from_cli_arguments(global_options, cli_overrides)?;
|
||||||
ConfigArguments::from_cli_arguments(self.config, cli_overrides, self.isolated)?;
|
|
||||||
Ok((format_arguments, config_args))
|
Ok((format_arguments, config_args))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -806,14 +821,24 @@ impl TypedValueParser for ConfigArgumentParser {
|
|||||||
arg: Option<&clap::Arg>,
|
arg: Option<&clap::Arg>,
|
||||||
value: &std::ffi::OsStr,
|
value: &std::ffi::OsStr,
|
||||||
) -> Result<Self::Value, clap::Error> {
|
) -> Result<Self::Value, clap::Error> {
|
||||||
let path_to_config_file = PathBuf::from(value);
|
// Convert to UTF-8.
|
||||||
if path_to_config_file.exists() {
|
let Some(value) = value.to_str() else {
|
||||||
return Ok(SingleConfigArgument::FilePath(path_to_config_file));
|
// But respect non-UTF-8 paths.
|
||||||
}
|
let path_to_config_file = PathBuf::from(value);
|
||||||
|
if path_to_config_file.is_file() {
|
||||||
|
return Ok(SingleConfigArgument::FilePath(path_to_config_file));
|
||||||
|
}
|
||||||
|
return Err(clap::Error::new(clap::error::ErrorKind::InvalidUtf8));
|
||||||
|
};
|
||||||
|
|
||||||
let value = value
|
// Expand environment variables and tildes.
|
||||||
.to_str()
|
if let Ok(path_to_config_file) =
|
||||||
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
|
shellexpand::full(value).map(|config| PathBuf::from(&*config))
|
||||||
|
{
|
||||||
|
if path_to_config_file.is_file() {
|
||||||
|
return Ok(SingleConfigArgument::FilePath(path_to_config_file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let config_parse_error = match toml::Table::from_str(value) {
|
let config_parse_error = match toml::Table::from_str(value) {
|
||||||
Ok(table) => match table.try_into::<Options>() {
|
Ok(table) => match table.try_into::<Options>() {
|
||||||
@@ -881,7 +906,7 @@ A `--config` flag must either be a path to a `.toml` configuration file
|
|||||||
"
|
"
|
||||||
|
|
||||||
It looks like you were trying to pass a path to a configuration file.
|
It looks like you were trying to pass a path to a configuration file.
|
||||||
The path `{value}` does not exist"
|
The path `{value}` does not point to a configuration file"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if value.contains('=') {
|
} else if value.contains('=') {
|
||||||
@@ -949,7 +974,6 @@ pub struct CheckArguments {
|
|||||||
pub exit_zero: bool,
|
pub exit_zero: bool,
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
pub ignore_noqa: bool,
|
pub ignore_noqa: bool,
|
||||||
pub isolated: bool,
|
|
||||||
pub no_cache: bool,
|
pub no_cache: bool,
|
||||||
pub output_file: Option<PathBuf>,
|
pub output_file: Option<PathBuf>,
|
||||||
pub show_files: bool,
|
pub show_files: bool,
|
||||||
@@ -967,7 +991,6 @@ pub struct FormatArguments {
|
|||||||
pub no_cache: bool,
|
pub no_cache: bool,
|
||||||
pub diff: bool,
|
pub diff: bool,
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
pub isolated: bool,
|
|
||||||
pub stdin_filename: Option<PathBuf>,
|
pub stdin_filename: Option<PathBuf>,
|
||||||
pub range: Option<FormatRange>,
|
pub range: Option<FormatRange>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ pub(crate) fn init(path: &Path) -> Result<()> {
|
|||||||
let gitignore_path = path.join(".gitignore");
|
let gitignore_path = path.join(".gitignore");
|
||||||
if !gitignore_path.exists() {
|
if !gitignore_path.exists() {
|
||||||
let mut file = fs::File::create(gitignore_path)?;
|
let mut file = fs::File::create(gitignore_path)?;
|
||||||
file.write_all(b"*")?;
|
file.write_all(b"# Automatically created by ruff.\n*\n")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -61,13 +61,8 @@ impl FormatMode {
|
|||||||
pub(crate) fn format(
|
pub(crate) fn format(
|
||||||
cli: FormatArguments,
|
cli: FormatArguments,
|
||||||
config_arguments: &ConfigArguments,
|
config_arguments: &ConfigArguments,
|
||||||
log_level: LogLevel,
|
|
||||||
) -> Result<ExitStatus> {
|
) -> Result<ExitStatus> {
|
||||||
let pyproject_config = resolve(
|
let pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
|
||||||
cli.isolated,
|
|
||||||
config_arguments,
|
|
||||||
cli.stdin_filename.as_deref(),
|
|
||||||
)?;
|
|
||||||
let mode = FormatMode::from_cli(&cli);
|
let mode = FormatMode::from_cli(&cli);
|
||||||
let files = resolve_default_files(cli.files, false);
|
let files = resolve_default_files(cli.files, false);
|
||||||
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?;
|
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?;
|
||||||
@@ -202,7 +197,7 @@ pub(crate) fn format(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Report on the formatting changes.
|
// Report on the formatting changes.
|
||||||
if log_level >= LogLevel::Default {
|
if config_arguments.log_level >= LogLevel::Default {
|
||||||
if mode.is_diff() {
|
if mode.is_diff() {
|
||||||
// Allow piping the diff to e.g. a file by writing the summary to stderr
|
// Allow piping the diff to e.g. a file by writing the summary to stderr
|
||||||
results.write_summary(&mut stderr().lock())?;
|
results.write_summary(&mut stderr().lock())?;
|
||||||
@@ -532,7 +527,7 @@ impl<'a> FormatResults<'a> {
|
|||||||
})
|
})
|
||||||
.sorted_unstable_by_key(|(path, _, _)| *path)
|
.sorted_unstable_by_key(|(path, _, _)| *path)
|
||||||
{
|
{
|
||||||
unformatted.diff(formatted, Some(path), f)?;
|
write!(f, "{}", unformatted.diff(formatted, Some(path)).unwrap())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -23,11 +23,7 @@ pub(crate) fn format_stdin(
|
|||||||
cli: &FormatArguments,
|
cli: &FormatArguments,
|
||||||
config_arguments: &ConfigArguments,
|
config_arguments: &ConfigArguments,
|
||||||
) -> Result<ExitStatus> {
|
) -> Result<ExitStatus> {
|
||||||
let pyproject_config = resolve(
|
let pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
|
||||||
cli.isolated,
|
|
||||||
config_arguments,
|
|
||||||
cli.stdin_filename.as_deref(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut resolver = Resolver::new(&pyproject_config);
|
let mut resolver = Resolver::new(&pyproject_config);
|
||||||
warn_incompatible_formatter_settings(&resolver);
|
warn_incompatible_formatter_settings(&resolver);
|
||||||
@@ -122,9 +118,13 @@ fn format_source_code(
|
|||||||
}
|
}
|
||||||
FormatMode::Check => {}
|
FormatMode::Check => {}
|
||||||
FormatMode::Diff => {
|
FormatMode::Diff => {
|
||||||
source_kind
|
use std::io::Write;
|
||||||
.diff(formatted, path, &mut stdout().lock())
|
write!(
|
||||||
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
|
&mut stdout().lock(),
|
||||||
|
"{}",
|
||||||
|
source_kind.diff(formatted, path).unwrap()
|
||||||
|
)
|
||||||
|
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FormattedSource::Unchanged => {
|
FormattedSource::Unchanged => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub(crate) mod format;
|
|||||||
pub(crate) mod format_stdin;
|
pub(crate) mod format_stdin;
|
||||||
pub(crate) mod linter;
|
pub(crate) mod linter;
|
||||||
pub(crate) mod rule;
|
pub(crate) mod rule;
|
||||||
|
pub(crate) mod server;
|
||||||
pub(crate) mod show_files;
|
pub(crate) mod show_files;
|
||||||
pub(crate) mod show_settings;
|
pub(crate) mod show_settings;
|
||||||
pub(crate) mod version;
|
pub(crate) mod version;
|
||||||
|
|||||||
73
crates/ruff/src/commands/server.rs
Normal file
73
crates/ruff/src/commands/server.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use crate::ExitStatus;
|
||||||
|
use anyhow::Result;
|
||||||
|
use ruff_linter::logging::LogLevel;
|
||||||
|
use ruff_server::Server;
|
||||||
|
use tracing::{level_filters::LevelFilter, metadata::Level, subscriber::Interest, Metadata};
|
||||||
|
use tracing_subscriber::{
|
||||||
|
layer::{Context, Filter, SubscriberExt},
|
||||||
|
Layer, Registry,
|
||||||
|
};
|
||||||
|
use tracing_tree::time::Uptime;
|
||||||
|
|
||||||
|
pub(crate) fn run_server(preview: bool, log_level: LogLevel) -> Result<ExitStatus> {
|
||||||
|
if !preview {
|
||||||
|
tracing::error!("--preview needs to be provided as a command line argument while the server is still unstable.\nFor example: `ruff server --preview`");
|
||||||
|
return Ok(ExitStatus::Error);
|
||||||
|
}
|
||||||
|
let trace_level = if log_level == LogLevel::Verbose {
|
||||||
|
Level::TRACE
|
||||||
|
} else {
|
||||||
|
Level::DEBUG
|
||||||
|
};
|
||||||
|
|
||||||
|
let subscriber = Registry::default().with(
|
||||||
|
tracing_tree::HierarchicalLayer::default()
|
||||||
|
.with_indent_lines(true)
|
||||||
|
.with_indent_amount(2)
|
||||||
|
.with_bracketed_fields(true)
|
||||||
|
.with_targets(true)
|
||||||
|
.with_writer(|| Box::new(std::io::stderr()))
|
||||||
|
.with_timer(Uptime::default())
|
||||||
|
.with_filter(LoggingFilter { trace_level }),
|
||||||
|
);
|
||||||
|
|
||||||
|
tracing::subscriber::set_global_default(subscriber)?;
|
||||||
|
|
||||||
|
let server = Server::new()?;
|
||||||
|
|
||||||
|
server.run().map(|()| ExitStatus::Success)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoggingFilter {
|
||||||
|
trace_level: Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoggingFilter {
|
||||||
|
fn is_enabled(&self, meta: &Metadata<'_>) -> bool {
|
||||||
|
let filter = if meta.target().starts_with("ruff") {
|
||||||
|
self.trace_level
|
||||||
|
} else {
|
||||||
|
Level::INFO
|
||||||
|
};
|
||||||
|
|
||||||
|
meta.level() <= &filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Filter<S> for LoggingFilter {
|
||||||
|
fn enabled(&self, meta: &Metadata<'_>, _cx: &Context<'_, S>) -> bool {
|
||||||
|
self.is_enabled(meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
|
||||||
|
if self.is_enabled(meta) {
|
||||||
|
Interest::always()
|
||||||
|
} else {
|
||||||
|
Interest::never()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_level_hint(&self) -> Option<LevelFilter> {
|
||||||
|
Some(LevelFilter::from_level(self.trace_level))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -289,10 +290,10 @@ pub(crate) fn lint_path(
|
|||||||
match fix_mode {
|
match fix_mode {
|
||||||
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
|
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
|
||||||
flags::FixMode::Diff => {
|
flags::FixMode::Diff => {
|
||||||
source_kind.diff(
|
write!(
|
||||||
transformed.as_ref(),
|
|
||||||
Some(path),
|
|
||||||
&mut io::stdout().lock(),
|
&mut io::stdout().lock(),
|
||||||
|
"{}",
|
||||||
|
source_kind.diff(&transformed, Some(path)).unwrap()
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
flags::FixMode::Generate => {}
|
||||||
@@ -442,7 +443,11 @@ pub(crate) fn lint_stdin(
|
|||||||
flags::FixMode::Diff => {
|
flags::FixMode::Diff => {
|
||||||
// But only write a diff if it's non-empty.
|
// But only write a diff if it's non-empty.
|
||||||
if !fixed.is_empty() {
|
if !fixed.is_empty() {
|
||||||
source_kind.diff(transformed.as_ref(), path, &mut io::stdout().lock())?;
|
write!(
|
||||||
|
&mut io::stdout().lock(),
|
||||||
|
"{}",
|
||||||
|
source_kind.diff(&transformed, path).unwrap()
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
flags::FixMode::Generate => {}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use std::process::ExitCode;
|
|||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use args::{GlobalConfigArgs, ServerCommand};
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
@@ -117,7 +118,7 @@ fn resolve_default_files(files: Vec<PathBuf>, is_stdin: bool) -> Vec<PathBuf> {
|
|||||||
pub fn run(
|
pub fn run(
|
||||||
Args {
|
Args {
|
||||||
command,
|
command,
|
||||||
log_level_args,
|
global_options,
|
||||||
}: Args,
|
}: Args,
|
||||||
deprecated_alias_warning: Option<&'static str>,
|
deprecated_alias_warning: Option<&'static str>,
|
||||||
) -> Result<ExitStatus> {
|
) -> Result<ExitStatus> {
|
||||||
@@ -147,8 +148,7 @@ pub fn run(
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
assert!(colored::control::set_virtual_terminal(true).is_ok());
|
assert!(colored::control::set_virtual_terminal(true).is_ok());
|
||||||
|
|
||||||
let log_level = LogLevel::from(&log_level_args);
|
set_up_logging(global_options.log_level())?;
|
||||||
set_up_logging(&log_level)?;
|
|
||||||
|
|
||||||
if let Some(deprecated_alias_warning) = deprecated_alias_warning {
|
if let Some(deprecated_alias_warning) = deprecated_alias_warning {
|
||||||
warn_user!("{}", deprecated_alias_warning);
|
warn_user!("{}", deprecated_alias_warning);
|
||||||
@@ -181,38 +181,41 @@ pub fn run(
|
|||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
Command::Clean => {
|
Command::Clean => {
|
||||||
commands::clean::clean(log_level)?;
|
commands::clean::clean(global_options.log_level())?;
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
Command::GenerateShellCompletion { shell } => {
|
Command::GenerateShellCompletion { shell } => {
|
||||||
shell.generate(&mut Args::command(), &mut stdout());
|
shell.generate(&mut Args::command(), &mut stdout());
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
Command::Check(args) => check(args, log_level),
|
Command::Check(args) => check(args, global_options),
|
||||||
Command::Format(args) => format(args, log_level),
|
Command::Format(args) => format(args, global_options),
|
||||||
|
Command::Server(args) => server(args, global_options.log_level()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
fn format(args: FormatCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
|
||||||
let (cli, config_arguments) = args.partition()?;
|
let (cli, config_arguments) = args.partition(global_options)?;
|
||||||
|
|
||||||
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
|
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
|
||||||
commands::format_stdin::format_stdin(&cli, &config_arguments)
|
commands::format_stdin::format_stdin(&cli, &config_arguments)
|
||||||
} else {
|
} else {
|
||||||
commands::format::format(cli, &config_arguments, log_level)
|
commands::format::format(cli, &config_arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
#[allow(clippy::needless_pass_by_value)] // TODO: remove once we start taking arguments from here
|
||||||
let (cli, config_arguments) = args.partition()?;
|
fn server(args: ServerCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||||
|
let ServerCommand { preview } = args;
|
||||||
|
commands::server::run_server(preview, log_level)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
|
||||||
|
let (cli, config_arguments) = args.partition(global_options)?;
|
||||||
|
|
||||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||||
// files are present, or files are injected from outside of the hierarchy.
|
// files are present, or files are injected from outside of the hierarchy.
|
||||||
let pyproject_config = resolve::resolve(
|
let pyproject_config = resolve::resolve(&config_arguments, cli.stdin_filename.as_deref())?;
|
||||||
cli.isolated,
|
|
||||||
&config_arguments,
|
|
||||||
cli.stdin_filename.as_deref(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut writer: Box<dyn Write> = match cli.output_file {
|
let mut writer: Box<dyn Write> = match cli.output_file {
|
||||||
Some(path) if !cli.watch => {
|
Some(path) if !cli.watch => {
|
||||||
@@ -303,7 +306,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
}
|
}
|
||||||
let modifications =
|
let modifications =
|
||||||
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
|
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
|
||||||
if modifications > 0 && log_level >= LogLevel::Default {
|
if modifications > 0 && config_arguments.log_level >= LogLevel::Default {
|
||||||
let s = if modifications == 1 { "" } else { "s" };
|
let s = if modifications == 1 { "" } else { "s" };
|
||||||
#[allow(clippy::print_stderr)]
|
#[allow(clippy::print_stderr)]
|
||||||
{
|
{
|
||||||
@@ -315,7 +318,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
|
|
||||||
let printer = Printer::new(
|
let printer = Printer::new(
|
||||||
output_format,
|
output_format,
|
||||||
log_level,
|
config_arguments.log_level,
|
||||||
fix_mode,
|
fix_mode,
|
||||||
unsafe_fixes,
|
unsafe_fixes,
|
||||||
printer_flags,
|
printer_flags,
|
||||||
@@ -372,11 +375,8 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if matches!(change_kind, ChangeKind::Configuration) {
|
if matches!(change_kind, ChangeKind::Configuration) {
|
||||||
pyproject_config = resolve::resolve(
|
pyproject_config =
|
||||||
cli.isolated,
|
resolve::resolve(&config_arguments, cli.stdin_filename.as_deref())?;
|
||||||
&config_arguments,
|
|
||||||
cli.stdin_filename.as_deref(),
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
Printer::clear_screen()?;
|
Printer::clear_screen()?;
|
||||||
printer.write_to_user("File change detected...\n");
|
printer.write_to_user("File change detected...\n");
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ impl Printer {
|
|||||||
} else if remaining > 0 {
|
} else if remaining > 0 {
|
||||||
let s = if remaining == 1 { "" } else { "s" };
|
let s = if remaining == 1 { "" } else { "s" };
|
||||||
writeln!(writer, "Found {remaining} error{s}.")?;
|
writeln!(writer, "Found {remaining} error{s}.")?;
|
||||||
|
} else if remaining == 0 {
|
||||||
|
writeln!(writer, "All checks passed!")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(fixables) = fixables {
|
if let Some(fixables) = fixables {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
@@ -16,12 +16,11 @@ use crate::args::ConfigArguments;
|
|||||||
/// Resolve the relevant settings strategy and defaults for the current
|
/// Resolve the relevant settings strategy and defaults for the current
|
||||||
/// invocation.
|
/// invocation.
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
isolated: bool,
|
|
||||||
config_arguments: &ConfigArguments,
|
config_arguments: &ConfigArguments,
|
||||||
stdin_filename: Option<&Path>,
|
stdin_filename: Option<&Path>,
|
||||||
) -> Result<PyprojectConfig> {
|
) -> Result<PyprojectConfig> {
|
||||||
// First priority: if we're running in isolated mode, use the default settings.
|
// First priority: if we're running in isolated mode, use the default settings.
|
||||||
if isolated {
|
if config_arguments.isolated {
|
||||||
let config = config_arguments.transform(Configuration::default());
|
let config = config_arguments.transform(Configuration::default());
|
||||||
let settings = config.into_settings(&path_dedot::CWD)?;
|
let settings = config.into_settings(&path_dedot::CWD)?;
|
||||||
debug!("Isolated mode, not reading any pyproject.toml");
|
debug!("Isolated mode, not reading any pyproject.toml");
|
||||||
@@ -35,13 +34,8 @@ pub fn resolve(
|
|||||||
// Second priority: the user specified a `pyproject.toml` file. Use that
|
// Second priority: the user specified a `pyproject.toml` file. Use that
|
||||||
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
||||||
// current working directory. (This matches ESLint's behavior.)
|
// current working directory. (This matches ESLint's behavior.)
|
||||||
if let Some(pyproject) = config_arguments
|
if let Some(pyproject) = config_arguments.config_file() {
|
||||||
.config_file()
|
let settings = resolve_root_settings(pyproject, Relativity::Cwd, config_arguments)?;
|
||||||
.map(|config| config.display().to_string())
|
|
||||||
.map(|config| shellexpand::full(&config).map(|config| PathBuf::from(config.as_ref())))
|
|
||||||
.transpose()?
|
|
||||||
{
|
|
||||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, config_arguments)?;
|
|
||||||
debug!(
|
debug!(
|
||||||
"Using user-specified configuration file at: {}",
|
"Using user-specified configuration file at: {}",
|
||||||
pyproject.display()
|
pyproject.display()
|
||||||
@@ -49,7 +43,7 @@ pub fn resolve(
|
|||||||
return Ok(PyprojectConfig::new(
|
return Ok(PyprojectConfig::new(
|
||||||
PyprojectDiscoveryStrategy::Fixed,
|
PyprojectDiscoveryStrategy::Fixed,
|
||||||
settings,
|
settings,
|
||||||
Some(pyproject),
|
Some(pyproject.to_path_buf()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ fn default_options() {
|
|||||||
.arg("-")
|
.arg("-")
|
||||||
.pass_stdin(r#"
|
.pass_stdin(r#"
|
||||||
def foo(arg1, arg2,):
|
def foo(arg1, arg2,):
|
||||||
print('Should\'t change quotes')
|
print('Shouldn\'t change quotes')
|
||||||
|
|
||||||
|
|
||||||
if condition:
|
if condition:
|
||||||
@@ -38,7 +38,7 @@ if condition:
|
|||||||
arg1,
|
arg1,
|
||||||
arg2,
|
arg2,
|
||||||
):
|
):
|
||||||
print("Should't change quotes")
|
print("Shouldn't change quotes")
|
||||||
|
|
||||||
|
|
||||||
if condition:
|
if condition:
|
||||||
@@ -111,7 +111,7 @@ fn nonexistent_config_file() {
|
|||||||
option
|
option
|
||||||
|
|
||||||
It looks like you were trying to pass a path to a configuration file.
|
It looks like you were trying to pass a path to a configuration file.
|
||||||
The path `foo.toml` does not exist
|
The path `foo.toml` does not point to a configuration file
|
||||||
|
|
||||||
For more information, try '--help'.
|
For more information, try '--help'.
|
||||||
"###);
|
"###);
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ fn stdin_success() {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -222,6 +223,7 @@ fn stdin_source_type_pyi() {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -590,6 +592,7 @@ fn stdin_fix_when_no_issues_should_still_print_contents() {
|
|||||||
print(sys.version)
|
print(sys.version)
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
All checks passed!
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1023,6 +1026,7 @@ fn preview_disabled_direct() {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: Selection `RUF911` has no effect because preview is not enabled.
|
warning: Selection `RUF911` has no effect because preview is not enabled.
|
||||||
@@ -1039,6 +1043,7 @@ fn preview_disabled_prefix_empty() {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: Selection `RUF91` has no effect because preview is not enabled.
|
warning: Selection `RUF91` has no effect because preview is not enabled.
|
||||||
@@ -1055,6 +1060,7 @@ fn preview_disabled_does_not_warn_for_empty_ignore_selections() {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -1070,6 +1076,7 @@ fn preview_disabled_does_not_warn_for_empty_fixable_selections() {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -1175,6 +1182,7 @@ fn removed_indirect() {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -1205,6 +1213,7 @@ fn redirect_indirect() {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -1307,6 +1316,7 @@ fn deprecated_indirect_preview_enabled() {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -1383,6 +1393,7 @@ fn unreadable_dir() -> Result<()> {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: Encountered error: Permission denied (os error 13)
|
warning: Encountered error: Permission denied (os error 13)
|
||||||
@@ -1897,6 +1908,7 @@ def log(x, base) -> float:
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###
|
"###
|
||||||
|
|||||||
@@ -496,6 +496,7 @@ ignore = ["D203", "D212"]
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: No Python files found under the given path(s)
|
warning: No Python files found under the given path(s)
|
||||||
@@ -522,7 +523,7 @@ fn nonexistent_config_file() {
|
|||||||
option
|
option
|
||||||
|
|
||||||
It looks like you were trying to pass a path to a configuration file.
|
It looks like you were trying to pass a path to a configuration file.
|
||||||
The path `foo.toml` does not exist
|
The path `foo.toml` does not point to a configuration file
|
||||||
|
|
||||||
For more information, try '--help'.
|
For more information, try '--help'.
|
||||||
"###);
|
"###);
|
||||||
@@ -833,6 +834,7 @@ fn complex_config_setting_overridden_via_cli() -> Result<()> {
|
|||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -849,7 +851,7 @@ fn deprecated_config_option_overridden_via_cli() {
|
|||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
-:1:7: N801 Class name `lowercase` should use CapWords convention
|
-:1:7: N801 Class name `lowercase` should use CapWords convention
|
||||||
Found 1 error.
|
Found 1 error.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
@@ -933,3 +935,236 @@ include = ["*.ipy"]
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_noqa_external() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
external = ["AAA"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.arg("--config")
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
# flake8: noqa: AAA101, BBB102
|
||||||
|
import os
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:3:8: F401 [*] `os` imported but unused
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: Invalid rule code provided to `# ruff: noqa` at -:2: BBB102
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn required_version_exact_mismatch() -> Result<()> {
|
||||||
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
required-version = "0.1.0"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.arg("--config")
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
import os
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
ruff failed
|
||||||
|
Cause: Required version `==0.1.0` does not match the running version `[VERSION]`
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn required_version_exact_match() -> Result<()> {
|
||||||
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
required-version = "{version}"
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.arg("--config")
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
import os
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:2:8: F401 [*] `os` imported but unused
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn required_version_bound_mismatch() -> Result<()> {
|
||||||
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
required-version = ">{version}"
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.arg("--config")
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
import os
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
ruff failed
|
||||||
|
Cause: Required version `>[VERSION]` does not match the running version `[VERSION]`
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn required_version_bound_match() -> Result<()> {
|
||||||
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
required-version = ">=0.1.0"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.arg("--config")
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
import os
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:2:8: F401 [*] `os` imported but unused
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand environment variables in `--config` paths provided via the CLI.
|
||||||
|
#[test]
|
||||||
|
fn config_expand() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
ruff_toml,
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
select = ["F"]
|
||||||
|
ignore = ["F841"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.arg("--config")
|
||||||
|
.arg("${NAME}.toml")
|
||||||
|
.env("NAME", "ruff")
|
||||||
|
.arg("-")
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.pass_stdin(r#"
|
||||||
|
import os
|
||||||
|
|
||||||
|
def func():
|
||||||
|
x = 1
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:2:8: F401 [*] `os` imported but unused
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ marking it as unused, as in:
|
|||||||
from module import member as member
|
from module import member as member
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Fix safety
|
||||||
|
|
||||||
|
When `ignore_init_module_imports` is disabled, fixes can remove for unused imports in `__init__` files.
|
||||||
|
These fixes are considered unsafe because they can change the public interface.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
```python
|
```python
|
||||||
import numpy as np # unused import
|
import numpy as np # unused import
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ linter.allowed_confusables = []
|
|||||||
linter.builtins = []
|
linter.builtins = []
|
||||||
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
|
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
|
||||||
linter.external = []
|
linter.external = []
|
||||||
linter.ignore_init_module_imports = false
|
linter.ignore_init_module_imports = true
|
||||||
linter.logger_objects = []
|
linter.logger_objects = []
|
||||||
linter.namespace_packages = []
|
linter.namespace_packages = []
|
||||||
linter.src = [
|
linter.src = [
|
||||||
@@ -241,7 +241,22 @@ linter.flake8_gettext.functions_names = [
|
|||||||
ngettext,
|
ngettext,
|
||||||
]
|
]
|
||||||
linter.flake8_implicit_str_concat.allow_multiline = true
|
linter.flake8_implicit_str_concat.allow_multiline = true
|
||||||
linter.flake8_import_conventions.aliases = {"matplotlib": "mpl", "matplotlib.pyplot": "plt", "pandas": "pd", "seaborn": "sns", "tensorflow": "tf", "networkx": "nx", "plotly.express": "px", "polars": "pl", "numpy": "np", "panel": "pn", "pyarrow": "pa", "altair": "alt", "tkinter": "tk", "holoviews": "hv"}
|
linter.flake8_import_conventions.aliases = {
|
||||||
|
altair = alt,
|
||||||
|
holoviews = hv,
|
||||||
|
matplotlib = mpl,
|
||||||
|
matplotlib.pyplot = plt,
|
||||||
|
networkx = nx,
|
||||||
|
numpy = np,
|
||||||
|
pandas = pd,
|
||||||
|
panel = pn,
|
||||||
|
plotly.express = px,
|
||||||
|
polars = pl,
|
||||||
|
pyarrow = pa,
|
||||||
|
seaborn = sns,
|
||||||
|
tensorflow = tf,
|
||||||
|
tkinter = tk,
|
||||||
|
}
|
||||||
linter.flake8_import_conventions.banned_aliases = {}
|
linter.flake8_import_conventions.banned_aliases = {}
|
||||||
linter.flake8_import_conventions.banned_from = []
|
linter.flake8_import_conventions.banned_from = []
|
||||||
linter.flake8_pytest_style.fixture_parentheses = true
|
linter.flake8_pytest_style.fixture_parentheses = true
|
||||||
@@ -311,6 +326,7 @@ linter.isort.section_order = [
|
|||||||
known { type = first_party },
|
known { type = first_party },
|
||||||
known { type = local_folder },
|
known { type = local_folder },
|
||||||
]
|
]
|
||||||
|
linter.isort.default_section = known { type = third_party }
|
||||||
linter.isort.no_sections = false
|
linter.isort.no_sections = false
|
||||||
linter.isort.from_first = false
|
linter.isort.from_first = false
|
||||||
linter.isort.length_sort = false
|
linter.isort.length_sort = false
|
||||||
|
|||||||
105
crates/ruff/tests/version.rs
Normal file
105
crates/ruff/tests/version.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
//! Tests for the --version command
|
||||||
|
use std::fs;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
const BIN_NAME: &str = "ruff";
|
||||||
|
const VERSION_FILTER: [(&str, &str); 1] = [(
|
||||||
|
r"\d+\.\d+\.\d+(\+\d+)?( \(\w{9} \d\d\d\d-\d\d-\d\d\))?",
|
||||||
|
"[VERSION]",
|
||||||
|
)];
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_basics() {
|
||||||
|
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME)).arg("version"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
ruff [VERSION]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `--config` is a global option,
|
||||||
|
/// so it's allowed to pass --config to subcommands such as `version`
|
||||||
|
/// -- the flag is simply ignored
|
||||||
|
#[test]
|
||||||
|
fn config_option_allowed_but_ignored() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::File::create(&ruff_dot_toml)?;
|
||||||
|
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.arg("version")
|
||||||
|
.arg("--config")
|
||||||
|
.arg(&ruff_dot_toml)
|
||||||
|
.args(["--config", "lint.isort.extra-standard-library = ['foo', 'bar']"]), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
ruff [VERSION]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn config_option_ignored_but_validated() {
|
||||||
|
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.arg("version")
|
||||||
|
.args(["--config", "foo = bar"]), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: invalid value 'foo = bar' for '--config <CONFIG_OPTION>'
|
||||||
|
|
||||||
|
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||||
|
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||||
|
option
|
||||||
|
|
||||||
|
The supplied argument is not valid TOML:
|
||||||
|
|
||||||
|
TOML parse error at line 1, column 7
|
||||||
|
|
|
||||||
|
1 | foo = bar
|
||||||
|
| ^
|
||||||
|
invalid string
|
||||||
|
expected `"`, `'`
|
||||||
|
|
||||||
|
For more information, try '--help'.
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `--isolated` is also a global option,
|
||||||
|
#[test]
|
||||||
|
fn isolated_option_allowed() {
|
||||||
|
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME)).arg("version").arg("--isolated"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
ruff [VERSION]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ use tracing_subscriber::layer::SubscriberExt;
|
|||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, LogLevelArgs};
|
use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, GlobalConfigArgs, LogLevelArgs};
|
||||||
use ruff::resolve::resolve;
|
use ruff::resolve::resolve;
|
||||||
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||||
use ruff_linter::logging::LogLevel;
|
use ruff_linter::logging::LogLevel;
|
||||||
@@ -43,7 +43,7 @@ fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, ConfigArgumen
|
|||||||
.no_binary_name(true)
|
.no_binary_name(true)
|
||||||
.get_matches_from(dirs);
|
.get_matches_from(dirs);
|
||||||
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
|
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
|
||||||
let (cli, config_arguments) = arguments.partition()?;
|
let (cli, config_arguments) = arguments.partition(GlobalConfigArgs::default())?;
|
||||||
Ok((cli, config_arguments))
|
Ok((cli, config_arguments))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,11 +52,7 @@ fn find_pyproject_config(
|
|||||||
cli: &FormatArguments,
|
cli: &FormatArguments,
|
||||||
config_arguments: &ConfigArguments,
|
config_arguments: &ConfigArguments,
|
||||||
) -> anyhow::Result<PyprojectConfig> {
|
) -> anyhow::Result<PyprojectConfig> {
|
||||||
let mut pyproject_config = resolve(
|
let mut pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
|
||||||
cli.isolated,
|
|
||||||
config_arguments,
|
|
||||||
cli.stdin_filename.as_deref(),
|
|
||||||
)?;
|
|
||||||
// We don't want to format pyproject.toml
|
// We don't want to format pyproject.toml
|
||||||
pyproject_config.settings.file_resolver.include = FilePatternSet::try_from_iter([
|
pyproject_config.settings.file_resolver.include = FilePatternSet::try_from_iter([
|
||||||
FilePattern::Builtin("*.py"),
|
FilePattern::Builtin("*.py"),
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use ruff::check;
|
use ruff::{args::GlobalConfigArgs, check};
|
||||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
use ruff_linter::logging::set_up_logging;
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
mod format_dev;
|
mod format_dev;
|
||||||
@@ -28,6 +28,8 @@ const ROOT_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../");
|
|||||||
struct Args {
|
struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Command,
|
command: Command,
|
||||||
|
#[clap(flatten)]
|
||||||
|
global_options: GlobalConfigArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
@@ -57,8 +59,6 @@ enum Command {
|
|||||||
Repeat {
|
Repeat {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
args: ruff::args::CheckCommand,
|
args: ruff::args::CheckCommand,
|
||||||
#[clap(flatten)]
|
|
||||||
log_level_args: ruff::args::LogLevelArgs,
|
|
||||||
/// Run this many times
|
/// Run this many times
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
repeat: usize,
|
repeat: usize,
|
||||||
@@ -75,9 +75,12 @@ enum Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<ExitCode> {
|
fn main() -> Result<ExitCode> {
|
||||||
let args = Args::parse();
|
let Args {
|
||||||
|
command,
|
||||||
|
global_options,
|
||||||
|
} = Args::parse();
|
||||||
#[allow(clippy::print_stdout)]
|
#[allow(clippy::print_stdout)]
|
||||||
match args.command {
|
match command {
|
||||||
Command::GenerateAll(args) => generate_all::main(&args)?,
|
Command::GenerateAll(args) => generate_all::main(&args)?,
|
||||||
Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?,
|
Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?,
|
||||||
Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()),
|
Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()),
|
||||||
@@ -89,14 +92,12 @@ fn main() -> Result<ExitCode> {
|
|||||||
Command::PrintTokens(args) => print_tokens::main(&args)?,
|
Command::PrintTokens(args) => print_tokens::main(&args)?,
|
||||||
Command::RoundTrip(args) => round_trip::main(&args)?,
|
Command::RoundTrip(args) => round_trip::main(&args)?,
|
||||||
Command::Repeat {
|
Command::Repeat {
|
||||||
args,
|
args: subcommand_args,
|
||||||
repeat,
|
repeat,
|
||||||
log_level_args,
|
|
||||||
} => {
|
} => {
|
||||||
let log_level = LogLevel::from(&log_level_args);
|
set_up_logging(global_options.log_level())?;
|
||||||
set_up_logging(&log_level)?;
|
|
||||||
for _ in 0..repeat {
|
for _ in 0..repeat {
|
||||||
check(args.clone(), log_level)?;
|
check(subcommand_args.clone(), global_options.clone())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::FormatDev(args) => {
|
Command::FormatDev(args) => {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ pub trait Buffer {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn elements(&self) -> &[FormatElement];
|
fn elements(&self) -> &[FormatElement];
|
||||||
|
|
||||||
/// Glue for usage of the [`write!`] macro with implementors of this trait.
|
/// Glue for usage of the [`write!`] macro with implementers of this trait.
|
||||||
///
|
///
|
||||||
/// This method should generally not be invoked manually, but rather through the [`write!`] macro itself.
|
/// This method should generally not be invoked manually, but rather through the [`write!`] macro itself.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -545,6 +545,10 @@ impl PrintedRange {
|
|||||||
&self.code
|
&self.code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_code(self) -> String {
|
||||||
|
self.code
|
||||||
|
}
|
||||||
|
|
||||||
/// The range the formatted code corresponds to in the source document.
|
/// The range the formatted code corresponds to in the source document.
|
||||||
pub fn source_range(&self) -> TextRange {
|
pub fn source_range(&self) -> TextRange {
|
||||||
self.source_range
|
self.source_range
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff_linter"
|
name = "ruff_linter"
|
||||||
version = "0.3.0"
|
version = "0.3.2"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
@@ -60,7 +60,6 @@ regex = { workspace = true }
|
|||||||
result-like = { workspace = true }
|
result-like = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
semver = { workspace = true }
|
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
similar = { workspace = true }
|
similar = { workspace = true }
|
||||||
|
|||||||
@@ -18,3 +18,7 @@ func("0.0.0.0")
|
|||||||
def my_func():
|
def my_func():
|
||||||
x = "0.0.0.0"
|
x = "0.0.0.0"
|
||||||
print(x)
|
print(x)
|
||||||
|
|
||||||
|
|
||||||
|
# Implicit string concatenation
|
||||||
|
"0.0.0.0" f"0.0.0.0{expr}0.0.0.0"
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ with open("/dev/shm/unit/test", "w") as f:
|
|||||||
with open("/foo/bar", "w") as f:
|
with open("/foo/bar", "w") as f:
|
||||||
f.write("def")
|
f.write("def")
|
||||||
|
|
||||||
|
# Implicit string concatenation
|
||||||
|
with open("/tmp/" "abc", "w") as f:
|
||||||
|
f.write("def")
|
||||||
|
|
||||||
|
with open("/tmp/abc" f"/tmp/abc", "w") as f:
|
||||||
|
f.write("def")
|
||||||
|
|
||||||
# Using `tempfile` module should be ok
|
# Using `tempfile` module should be ok
|
||||||
import tempfile
|
import tempfile
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|||||||
22
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S311.py
vendored
Normal file
22
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S311.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
import a_lib
|
||||||
|
|
||||||
|
# OK
|
||||||
|
random.SystemRandom()
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
random.Random()
|
||||||
|
random.random()
|
||||||
|
random.randrange()
|
||||||
|
random.randint()
|
||||||
|
random.choice()
|
||||||
|
random.choices()
|
||||||
|
random.uniform()
|
||||||
|
random.triangular()
|
||||||
|
random.randbytes()
|
||||||
|
|
||||||
|
# Unrelated
|
||||||
|
os.urandom()
|
||||||
|
a_lib.random()
|
||||||
@@ -1,52 +1,47 @@
|
|||||||
|
import crypt
|
||||||
import hashlib
|
import hashlib
|
||||||
from hashlib import new as hashlib_new
|
from hashlib import new as hashlib_new
|
||||||
from hashlib import sha1 as hashlib_sha1
|
from hashlib import sha1 as hashlib_sha1
|
||||||
|
|
||||||
# Invalid
|
# Errors
|
||||||
|
|
||||||
hashlib.new('md5')
|
hashlib.new('md5')
|
||||||
|
|
||||||
hashlib.new('md4', b'test')
|
hashlib.new('md4', b'test')
|
||||||
|
|
||||||
hashlib.new(name='md5', data=b'test')
|
hashlib.new(name='md5', data=b'test')
|
||||||
|
|
||||||
hashlib.new('MD4', data=b'test')
|
hashlib.new('MD4', data=b'test')
|
||||||
|
|
||||||
hashlib.new('sha1')
|
hashlib.new('sha1')
|
||||||
|
|
||||||
hashlib.new('sha1', data=b'test')
|
hashlib.new('sha1', data=b'test')
|
||||||
|
|
||||||
hashlib.new('sha', data=b'test')
|
hashlib.new('sha', data=b'test')
|
||||||
|
|
||||||
hashlib.new(name='SHA', data=b'test')
|
hashlib.new(name='SHA', data=b'test')
|
||||||
|
|
||||||
hashlib.sha(data=b'test')
|
hashlib.sha(data=b'test')
|
||||||
|
|
||||||
hashlib.md5()
|
hashlib.md5()
|
||||||
|
|
||||||
hashlib_new('sha1')
|
hashlib_new('sha1')
|
||||||
|
|
||||||
hashlib_sha1('sha1')
|
hashlib_sha1('sha1')
|
||||||
|
|
||||||
# usedforsecurity arg only available in Python 3.9+
|
# usedforsecurity arg only available in Python 3.9+
|
||||||
hashlib.new('sha1', usedforsecurity=True)
|
hashlib.new('sha1', usedforsecurity=True)
|
||||||
|
|
||||||
# Valid
|
crypt.crypt("test", salt=crypt.METHOD_CRYPT)
|
||||||
|
crypt.crypt("test", salt=crypt.METHOD_MD5)
|
||||||
|
crypt.crypt("test", salt=crypt.METHOD_BLOWFISH)
|
||||||
|
crypt.crypt("test", crypt.METHOD_BLOWFISH)
|
||||||
|
|
||||||
|
crypt.mksalt(crypt.METHOD_CRYPT)
|
||||||
|
crypt.mksalt(crypt.METHOD_MD5)
|
||||||
|
crypt.mksalt(crypt.METHOD_BLOWFISH)
|
||||||
|
|
||||||
|
# OK
|
||||||
hashlib.new('sha256')
|
hashlib.new('sha256')
|
||||||
|
|
||||||
hashlib.new('SHA512')
|
hashlib.new('SHA512')
|
||||||
|
|
||||||
hashlib.sha256(data=b'test')
|
hashlib.sha256(data=b'test')
|
||||||
|
|
||||||
# usedforsecurity arg only available in Python 3.9+
|
# usedforsecurity arg only available in Python 3.9+
|
||||||
hashlib_new(name='sha1', usedforsecurity=False)
|
hashlib_new(name='sha1', usedforsecurity=False)
|
||||||
|
|
||||||
# usedforsecurity arg only available in Python 3.9+
|
|
||||||
hashlib_sha1(name='sha1', usedforsecurity=False)
|
hashlib_sha1(name='sha1', usedforsecurity=False)
|
||||||
|
|
||||||
# usedforsecurity arg only available in Python 3.9+
|
|
||||||
hashlib.md4(usedforsecurity=False)
|
hashlib.md4(usedforsecurity=False)
|
||||||
|
|
||||||
# usedforsecurity arg only available in Python 3.9+
|
|
||||||
hashlib.new(name='sha256', usedforsecurity=False)
|
hashlib.new(name='sha256', usedforsecurity=False)
|
||||||
|
|
||||||
|
crypt.crypt("test")
|
||||||
|
crypt.crypt("test", salt=crypt.METHOD_SHA256)
|
||||||
|
crypt.crypt("test", salt=crypt.METHOD_SHA512)
|
||||||
|
|
||||||
|
crypt.mksalt()
|
||||||
|
crypt.mksalt(crypt.METHOD_SHA256)
|
||||||
|
crypt.mksalt(crypt.METHOD_SHA512)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
import commands
|
import commands
|
||||||
import popen2
|
import popen2
|
||||||
@@ -16,6 +17,8 @@ popen2.Popen3("true")
|
|||||||
popen2.Popen4("true")
|
popen2.Popen4("true")
|
||||||
commands.getoutput("true")
|
commands.getoutput("true")
|
||||||
commands.getstatusoutput("true")
|
commands.getstatusoutput("true")
|
||||||
|
subprocess.getoutput("true")
|
||||||
|
subprocess.getstatusoutput("true")
|
||||||
|
|
||||||
|
|
||||||
# Check command argument looks unsafe.
|
# Check command argument looks unsafe.
|
||||||
|
|||||||
34
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S610.py
vendored
Normal file
34
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S610.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
User.objects.filter(username='admin').extra(dict(could_be='insecure'))
|
||||||
|
User.objects.filter(username='admin').extra(select=dict(could_be='insecure'))
|
||||||
|
User.objects.filter(username='admin').extra(select={'test': '%secure' % 'nos'})
|
||||||
|
User.objects.filter(username='admin').extra(select={'test': '{}secure'.format('nos')})
|
||||||
|
User.objects.filter(username='admin').extra(where=['%secure' % 'nos'])
|
||||||
|
User.objects.filter(username='admin').extra(where=['{}secure'.format('no')])
|
||||||
|
|
||||||
|
query = '"username") AS "username", * FROM "auth_user" WHERE 1=1 OR "username"=? --'
|
||||||
|
User.objects.filter(username='admin').extra(select={'test': query})
|
||||||
|
|
||||||
|
where_var = ['1=1) OR 1=1 AND (1=1']
|
||||||
|
User.objects.filter(username='admin').extra(where=where_var)
|
||||||
|
|
||||||
|
where_str = '1=1) OR 1=1 AND (1=1'
|
||||||
|
User.objects.filter(username='admin').extra(where=[where_str])
|
||||||
|
|
||||||
|
tables_var = ['django_content_type" WHERE "auth_user"."username"="admin']
|
||||||
|
User.objects.all().extra(tables=tables_var).distinct()
|
||||||
|
|
||||||
|
tables_str = 'django_content_type" WHERE "auth_user"."username"="admin'
|
||||||
|
User.objects.all().extra(tables=[tables_str]).distinct()
|
||||||
|
|
||||||
|
# OK
|
||||||
|
User.objects.filter(username='admin').extra(
|
||||||
|
select={'test': 'secure'},
|
||||||
|
where=['secure'],
|
||||||
|
tables=['secure']
|
||||||
|
)
|
||||||
|
User.objects.filter(username='admin').extra({'test': 'secure'})
|
||||||
|
User.objects.filter(username='admin').extra(select={'test': 'secure'})
|
||||||
|
User.objects.filter(username='admin').extra(where=['secure'])
|
||||||
@@ -14,9 +14,6 @@ reversed(sorted(x, reverse=not x))
|
|||||||
reversed(sorted(i for i in range(42)))
|
reversed(sorted(i for i in range(42)))
|
||||||
reversed(sorted((i for i in range(42)), reverse=True))
|
reversed(sorted((i for i in range(42)), reverse=True))
|
||||||
|
|
||||||
|
# Regression test for: https://github.com/astral-sh/ruff/issues/10335
|
||||||
def reversed(*args, **kwargs):
|
reversed(sorted([1, 2, 3], reverse=False or True))
|
||||||
return None
|
reversed(sorted([1, 2, 3], reverse=(False or True)))
|
||||||
|
|
||||||
|
|
||||||
reversed(sorted(x, reverse=True))
|
|
||||||
|
|||||||
@@ -7,7 +7,19 @@ from pdb import set_trace as st
|
|||||||
from celery.contrib.rdb import set_trace
|
from celery.contrib.rdb import set_trace
|
||||||
from celery.contrib import rdb
|
from celery.contrib import rdb
|
||||||
import celery.contrib.rdb
|
import celery.contrib.rdb
|
||||||
|
from debugpy import wait_for_client
|
||||||
|
import debugpy
|
||||||
|
from ptvsd import break_into_debugger
|
||||||
|
from ptvsd import enable_attach
|
||||||
|
from ptvsd import wait_for_attach
|
||||||
|
import ptvsd
|
||||||
|
|
||||||
breakpoint()
|
breakpoint()
|
||||||
st()
|
st()
|
||||||
set_trace()
|
set_trace()
|
||||||
|
debugpy.breakpoint()
|
||||||
|
wait_for_client()
|
||||||
|
debugpy.listen(1234)
|
||||||
|
enable_attach()
|
||||||
|
break_into_debugger()
|
||||||
|
wait_for_attach()
|
||||||
|
|||||||
@@ -64,3 +64,5 @@ def not_warnings_dot_deprecated(
|
|||||||
"Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
|
"Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
|
||||||
)
|
)
|
||||||
def not_a_deprecated_function() -> None: ...
|
def not_a_deprecated_function() -> None: ...
|
||||||
|
|
||||||
|
fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053
|
||||||
|
|||||||
@@ -40,4 +40,7 @@ f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
|
|||||||
|
|
||||||
# Make sure we do not unescape quotes
|
# Make sure we do not unescape quotes
|
||||||
this_is_fine = "This is an \\'escaped\\' quote"
|
this_is_fine = "This is an \\'escaped\\' quote"
|
||||||
this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash"
|
this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash" # Q004
|
||||||
|
|
||||||
|
# Invalid escapes in bytestrings are also triggered:
|
||||||
|
x = b"\xe7\xeb\x0c\xa1\x1b\x83tN\xce=x\xe9\xbe\x01\xb9\x13B_\xba\xe7\x0c2\xce\'rm\x0e\xcd\xe9.\xf8\xd2" # Q004
|
||||||
|
|||||||
@@ -93,3 +93,15 @@ def func():
|
|||||||
|
|
||||||
# OK
|
# OK
|
||||||
raise func()
|
raise func()
|
||||||
|
|
||||||
|
|
||||||
|
# OK
|
||||||
|
future = executor.submit(float, "a")
|
||||||
|
if future.exception():
|
||||||
|
raise future.exception()
|
||||||
|
|
||||||
|
|
||||||
|
# RSE102
|
||||||
|
future = executor.submit(float, "a")
|
||||||
|
if future.exception():
|
||||||
|
raise future.Exception()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ async def func():
|
|||||||
|
|
||||||
trio.sleep(0) # TRIO115
|
trio.sleep(0) # TRIO115
|
||||||
foo = 0
|
foo = 0
|
||||||
trio.sleep(foo) # TRIO115
|
trio.sleep(foo) # OK
|
||||||
trio.sleep(1) # OK
|
trio.sleep(1) # OK
|
||||||
time.sleep(0) # OK
|
time.sleep(0) # OK
|
||||||
|
|
||||||
@@ -20,26 +20,26 @@ async def func():
|
|||||||
trio.sleep(bar)
|
trio.sleep(bar)
|
||||||
|
|
||||||
x, y = 0, 2000
|
x, y = 0, 2000
|
||||||
trio.sleep(x) # TRIO115
|
trio.sleep(x) # OK
|
||||||
trio.sleep(y) # OK
|
trio.sleep(y) # OK
|
||||||
|
|
||||||
(a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
|
(a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
|
||||||
trio.sleep(c) # TRIO115
|
trio.sleep(c) # OK
|
||||||
trio.sleep(d) # OK
|
trio.sleep(d) # OK
|
||||||
trio.sleep(e) # TRIO115
|
trio.sleep(e) # OK
|
||||||
|
|
||||||
m_x, m_y = 0
|
m_x, m_y = 0
|
||||||
trio.sleep(m_y) # OK
|
trio.sleep(m_y) # OK
|
||||||
trio.sleep(m_x) # OK
|
trio.sleep(m_x) # OK
|
||||||
|
|
||||||
m_a = m_b = 0
|
m_a = m_b = 0
|
||||||
trio.sleep(m_a) # TRIO115
|
trio.sleep(m_a) # OK
|
||||||
trio.sleep(m_b) # TRIO115
|
trio.sleep(m_b) # OK
|
||||||
|
|
||||||
m_c = (m_d, m_e) = (0, 0)
|
m_c = (m_d, m_e) = (0, 0)
|
||||||
trio.sleep(m_c) # OK
|
trio.sleep(m_c) # OK
|
||||||
trio.sleep(m_d) # TRIO115
|
trio.sleep(m_d) # OK
|
||||||
trio.sleep(m_e) # TRIO115
|
trio.sleep(m_e) # OK
|
||||||
|
|
||||||
|
|
||||||
def func():
|
def func():
|
||||||
@@ -63,4 +63,16 @@ def func():
|
|||||||
import trio
|
import trio
|
||||||
|
|
||||||
if (walrus := 0) == 0:
|
if (walrus := 0) == 0:
|
||||||
trio.sleep(walrus) # TRIO115
|
trio.sleep(walrus) # OK
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
import trio
|
||||||
|
|
||||||
|
async def main() -> None:
|
||||||
|
sleep = 0
|
||||||
|
for _ in range(2):
|
||||||
|
await trio.sleep(sleep) # OK
|
||||||
|
sleep = 10
|
||||||
|
|
||||||
|
trio.run(main)
|
||||||
|
|||||||
9
crates/ruff_linter/resources/test/fixtures/isort/default_section_user_defined.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/isort/default_section_user_defined.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import django.settings
|
||||||
|
from library import foo
|
||||||
|
import os
|
||||||
|
import pytz
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from . import local
|
||||||
9
crates/ruff_linter/resources/test/fixtures/isort/no_standard_library.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/isort/no_standard_library.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import django.settings
|
||||||
|
from library import foo
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
from . import local
|
||||||
|
import sys
|
||||||
@@ -50,6 +50,29 @@ class MetaClass(ABCMeta):
|
|||||||
def static_method(not_cls) -> bool:
|
def static_method(not_cls) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
class ClsInArgsClass(ABCMeta):
|
||||||
|
def cls_as_argument(this, cls):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cls_as_pos_only_argument(this, cls, /):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cls_as_kw_only_argument(this, *, cls):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cls_as_varags(this, *cls):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cls_as_kwargs(this, **cls):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class RenamingInMethodBodyClass(ABCMeta):
|
||||||
|
def bad_method(this):
|
||||||
|
this = this
|
||||||
|
this
|
||||||
|
|
||||||
|
def bad_method(this):
|
||||||
|
self = this
|
||||||
|
|
||||||
def func(x):
|
def func(x):
|
||||||
return x
|
return x
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class PosOnlyClass:
|
|||||||
def good_method_pos_only(self, blah, /, something: str):
|
def good_method_pos_only(self, blah, /, something: str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def bad_method_pos_only(this, blah, /, self, something: str):
|
def bad_method_pos_only(this, blah, /, something: str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -93,3 +93,27 @@ class ModelClass:
|
|||||||
@foobar.thisisstatic
|
@foobar.thisisstatic
|
||||||
def badstatic(foo):
|
def badstatic(foo):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class SelfInArgsClass:
|
||||||
|
def self_as_argument(this, self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def self_as_pos_only_argument(this, self, /):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def self_as_kw_only_argument(this, *, self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def self_as_varags(this, *self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def self_as_kwargs(this, **self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class RenamingInMethodBodyClass:
|
||||||
|
def bad_method(this):
|
||||||
|
this = this
|
||||||
|
this
|
||||||
|
|
||||||
|
def bad_method(this):
|
||||||
|
self = this
|
||||||
|
|||||||
8
crates/ruff_linter/resources/test/fixtures/pycodestyle/.editorconfig
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/pycodestyle/.editorconfig
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# These rules test for intentional "odd" formatting. Using a formatter fixes that
|
||||||
|
[E{1,2,3}*.py]
|
||||||
|
generated_code = true
|
||||||
|
ij_formatter_enabled = false
|
||||||
|
|
||||||
|
[W*.py]
|
||||||
|
generated_code = true
|
||||||
|
ij_formatter_enabled = false
|
||||||
@@ -153,3 +153,9 @@ ham[lower +1 :, "columnname"]
|
|||||||
|
|
||||||
#: E203:1:13
|
#: E203:1:13
|
||||||
ham[lower + 1 :, "columnname"]
|
ham[lower + 1 :, "columnname"]
|
||||||
|
|
||||||
|
#: Okay
|
||||||
|
f"{ham[lower +1 :, "columnname"]}"
|
||||||
|
|
||||||
|
#: E203:1:13
|
||||||
|
f"{ham[lower + 1 :, "columnname"]}"
|
||||||
|
|||||||
1
crates/ruff_linter/resources/test/fixtures/pycodestyle/E2_syntax_error.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pycodestyle/E2_syntax_error.py
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
a = (1 or)
|
||||||
@@ -466,6 +466,29 @@ class Class:
|
|||||||
# end
|
# end
|
||||||
|
|
||||||
|
|
||||||
|
# E301
|
||||||
|
class Class:
|
||||||
|
"""Class for minimal repo."""
|
||||||
|
|
||||||
|
columns = []
|
||||||
|
@classmethod
|
||||||
|
def cls_method(cls) -> None:
|
||||||
|
pass
|
||||||
|
# end
|
||||||
|
|
||||||
|
|
||||||
|
# E301
|
||||||
|
class Class:
|
||||||
|
"""Class for minimal repo."""
|
||||||
|
|
||||||
|
def method(cls) -> None:
|
||||||
|
pass
|
||||||
|
@classmethod
|
||||||
|
def cls_method(cls) -> None:
|
||||||
|
pass
|
||||||
|
# end
|
||||||
|
|
||||||
|
|
||||||
# E302
|
# E302
|
||||||
"""Main module."""
|
"""Main module."""
|
||||||
def fn():
|
def fn():
|
||||||
|
|||||||
50
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.pyi
vendored
Normal file
50
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.pyi
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from typing import Any, Sequence
|
||||||
|
|
||||||
|
class MissingCommand(TypeError): ...
|
||||||
|
class AnoherClass: ...
|
||||||
|
|
||||||
|
def a(): ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def a(arg: int): ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def a(arg: int, name: str): ...
|
||||||
|
|
||||||
|
|
||||||
|
def grouped1(): ...
|
||||||
|
def grouped2(): ...
|
||||||
|
def grouped3( ): ...
|
||||||
|
|
||||||
|
|
||||||
|
class BackendProxy:
|
||||||
|
backend_module: str
|
||||||
|
backend_object: str | None
|
||||||
|
backend: Any
|
||||||
|
|
||||||
|
def grouped1(): ...
|
||||||
|
def grouped2(): ...
|
||||||
|
def grouped3( ): ...
|
||||||
|
@decorated
|
||||||
|
|
||||||
|
def with_blank_line(): ...
|
||||||
|
|
||||||
|
|
||||||
|
def ungrouped(): ...
|
||||||
|
a = "test"
|
||||||
|
|
||||||
|
def function_def():
|
||||||
|
pass
|
||||||
|
b = "test"
|
||||||
|
|
||||||
|
|
||||||
|
def outer():
|
||||||
|
def inner():
|
||||||
|
pass
|
||||||
|
def inner2():
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Foo: ...
|
||||||
|
class Bar: ...
|
||||||
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_docstring.py
vendored
Normal file
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_docstring.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
"""Test where the error is after the module's docstring."""
|
||||||
|
|
||||||
|
def fn():
|
||||||
|
pass
|
||||||
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_expression.py
vendored
Normal file
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_expression.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
"Test where the first line is a comment, " + "and the rule violation follows it."
|
||||||
|
|
||||||
|
def fn():
|
||||||
|
pass
|
||||||
5
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_function.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_function.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
def fn1():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def fn2():
|
||||||
|
pass
|
||||||
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_statement.py
vendored
Normal file
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_statement.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
print("Test where the first line is a statement, and the rule violation follows it.")
|
||||||
|
|
||||||
|
def fn():
|
||||||
|
pass
|
||||||
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_comment.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_comment.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Test where the first line is a comment, and the rule violation follows it.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def fn():
|
||||||
|
pass
|
||||||
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_docstring.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_docstring.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
"""Test where the error is after the module's docstring."""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def fn():
|
||||||
|
pass
|
||||||
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_expression.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_expression.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
"Test where the first line is a comment, " + "and the rule violation follows it."
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def fn():
|
||||||
|
pass
|
||||||
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_statement.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_statement.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
print("Test where the first line is a statement, and the rule violation follows it.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def fn():
|
||||||
|
pass
|
||||||
62
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30_isort.py
vendored
Normal file
62
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30_isort.py
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Any, Sequence
|
||||||
|
|
||||||
|
|
||||||
|
class MissingCommand(TypeError): ... # noqa: N818
|
||||||
|
|
||||||
|
|
||||||
|
class BackendProxy:
|
||||||
|
backend_module: str
|
||||||
|
backend_object: str | None
|
||||||
|
backend: Any
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import abcd
|
||||||
|
|
||||||
|
|
||||||
|
abcd.foo()
|
||||||
|
|
||||||
|
def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from typing_extensions import TypeAlias
|
||||||
|
|
||||||
|
|
||||||
|
abcd.foo()
|
||||||
|
|
||||||
|
def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import TypeAlias
|
||||||
|
|
||||||
|
def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def _exit(self) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
def _optional_commands(self) -> dict[str, bool]: ...
|
||||||
|
|
||||||
|
|
||||||
|
def run(argv: Sequence[str]) -> int: ...
|
||||||
|
|
||||||
|
|
||||||
|
def read_line(fd: int = 0) -> bytearray: ...
|
||||||
|
|
||||||
|
|
||||||
|
def flush() -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Any, Sequence
|
||||||
|
|
||||||
|
class MissingCommand(TypeError): ... # noqa: N818
|
||||||
62
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30_isort.pyi
vendored
Normal file
62
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30_isort.pyi
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Any, Sequence
|
||||||
|
|
||||||
|
|
||||||
|
class MissingCommand(TypeError): ... # noqa: N818
|
||||||
|
|
||||||
|
|
||||||
|
class BackendProxy:
|
||||||
|
backend_module: str
|
||||||
|
backend_object: str | None
|
||||||
|
backend: Any
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import abcd
|
||||||
|
|
||||||
|
|
||||||
|
abcd.foo()
|
||||||
|
|
||||||
|
def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from typing_extensions import TypeAlias
|
||||||
|
|
||||||
|
|
||||||
|
abcd.foo()
|
||||||
|
|
||||||
|
def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import TypeAlias
|
||||||
|
|
||||||
|
def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def _exit(self) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
def _optional_commands(self) -> dict[str, bool]: ...
|
||||||
|
|
||||||
|
|
||||||
|
def run(argv: Sequence[str]) -> int: ...
|
||||||
|
|
||||||
|
|
||||||
|
def read_line(fd: int = 0) -> bytearray: ...
|
||||||
|
|
||||||
|
|
||||||
|
def flush() -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Any, Sequence
|
||||||
|
|
||||||
|
class MissingCommand(TypeError): ... # noqa: N818
|
||||||
88
crates/ruff_linter/resources/test/fixtures/pycodestyle/E502.py
vendored
Normal file
88
crates/ruff_linter/resources/test/fixtures/pycodestyle/E502.py
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
a = 2 + 2
|
||||||
|
|
||||||
|
a = (2 + 2)
|
||||||
|
|
||||||
|
a = 2 + \
|
||||||
|
3 \
|
||||||
|
+ 4
|
||||||
|
|
||||||
|
a = (3 -\
|
||||||
|
2 + \
|
||||||
|
7)
|
||||||
|
|
||||||
|
z = 5 + \
|
||||||
|
(3 -\
|
||||||
|
2 + \
|
||||||
|
7) + \
|
||||||
|
4
|
||||||
|
|
||||||
|
b = [2 +
|
||||||
|
2]
|
||||||
|
|
||||||
|
b = [
|
||||||
|
2 + 4 + 5 + \
|
||||||
|
44 \
|
||||||
|
- 5
|
||||||
|
]
|
||||||
|
|
||||||
|
c = (True and
|
||||||
|
False \
|
||||||
|
or False \
|
||||||
|
and True \
|
||||||
|
)
|
||||||
|
|
||||||
|
c = (True and
|
||||||
|
False)
|
||||||
|
|
||||||
|
d = True and \
|
||||||
|
False or \
|
||||||
|
False \
|
||||||
|
and not True
|
||||||
|
|
||||||
|
|
||||||
|
s = {
|
||||||
|
'x': 2 + \
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
s = {
|
||||||
|
'x': 2 +
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
x = {2 + 4 \
|
||||||
|
+ 3}
|
||||||
|
|
||||||
|
y = (
|
||||||
|
2 + 2 # \
|
||||||
|
+ 3 # \
|
||||||
|
+ 4 \
|
||||||
|
+ 3
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
x = """
|
||||||
|
(\\
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
("""hello \
|
||||||
|
""")
|
||||||
|
|
||||||
|
("hello \
|
||||||
|
")
|
||||||
|
|
||||||
|
|
||||||
|
x = "abc" \
|
||||||
|
"xyz"
|
||||||
|
|
||||||
|
x = ("abc" \
|
||||||
|
"xyz")
|
||||||
|
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
x = (a + \
|
||||||
|
2)
|
||||||
14
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_0.py
vendored
Normal file
14
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_0.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Unix style
|
||||||
|
def foo() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def bar() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
foo()
|
||||||
|
bar()
|
||||||
|
|
||||||
13
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_1.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_1.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Unix style
|
||||||
|
def foo() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def bar() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
foo()
|
||||||
|
bar()
|
||||||
17
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_2.py
vendored
Normal file
17
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_2.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Windows style
|
||||||
|
def foo() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def bar() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
foo()
|
||||||
|
bar()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
13
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Windows style
|
||||||
|
def foo() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def bar() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
foo()
|
||||||
|
bar()
|
||||||
5
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_4.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_4.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# This is fine
|
||||||
|
def foo():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Some comment
|
||||||
@@ -10,7 +10,7 @@ def f1():
|
|||||||
# Here's a standalone comment that's over the limit.
|
# Here's a standalone comment that's over the limit.
|
||||||
|
|
||||||
x = 2
|
x = 2
|
||||||
# Another standalone that is preceded by a newline and indent toke and is over the limit.
|
# Another standalone that is preceded by a newline and indent token and is over the limit.
|
||||||
|
|
||||||
print("Here's a string that's over the limit, but it's not a docstring.")
|
print("Here's a string that's over the limit, but it's not a docstring.")
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ def f1():
|
|||||||
# Here's a standalone comment that's over theß9💣2ℝ.
|
# Here's a standalone comment that's over theß9💣2ℝ.
|
||||||
|
|
||||||
x = 2
|
x = 2
|
||||||
# Another standalone that is preceded by a newline and indent toke and is over theß9💣2ℝ.
|
# Another standalone that is preceded by a newline and indent token and is over theß9💣2ℝ.
|
||||||
|
|
||||||
print("Here's a string that's over theß9💣2ℝ, but it's not a ß9💣2ℝing.")
|
print("Here's a string that's over theß9💣2ℝ, but it's not a ß9💣2ℝing.")
|
||||||
|
|
||||||
|
|||||||
7
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_23.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_23.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"""Test: ensure that we treat strings in `typing.Annotation` as type definitions."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from re import RegexFlag
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
p: Annotated["Path", int] = 1
|
||||||
6
crates/ruff_linter/resources/test/fixtures/pyflakes/F811_28.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pyflakes/F811_28.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
"""Regression test for: https://github.com/astral-sh/ruff/issues/10384"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
datetime(1, 2, 3)
|
||||||
16
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_11.pyi
vendored
Normal file
16
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_11.pyi
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""Test case: strings used within calls within type annotations."""
|
||||||
|
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from mypy_extensions import VarArg
|
||||||
|
|
||||||
|
class LightShow(bpy.types.Operator):
|
||||||
|
label = "Create Character"
|
||||||
|
name = "lightshow.letter_creation"
|
||||||
|
|
||||||
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # OK
|
||||||
|
|
||||||
|
|
||||||
|
def f(x: Callable[[VarArg("os")], None]): # F821
|
||||||
|
pass
|
||||||
44
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py
vendored
Normal file
44
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"""Tests for constructs allowed in `.pyi` stub files but not at runtime"""
|
||||||
|
|
||||||
|
from typing import Optional, TypeAlias, Union
|
||||||
|
|
||||||
|
__version__: str
|
||||||
|
__author__: str
|
||||||
|
|
||||||
|
# Forward references:
|
||||||
|
MaybeCStr: TypeAlias = Optional[CStr] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
MaybeCStr2: TypeAlias = Optional["CStr"] # always okay
|
||||||
|
CStr: TypeAlias = Union[C, str] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
CStr2: TypeAlias = Union["C", str] # always okay
|
||||||
|
|
||||||
|
# References to a class from inside the class:
|
||||||
|
class C:
|
||||||
|
other: C = ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
other2: "C" = ... # always okay
|
||||||
|
def from_str(self, s: str) -> C: ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
def from_str2(self, s: str) -> "C": ... # always okay
|
||||||
|
|
||||||
|
# Circular references:
|
||||||
|
class A:
|
||||||
|
foo: B # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
foo2: "B" # always okay
|
||||||
|
bar: dict[str, B] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
bar2: dict[str, "A"] # always okay
|
||||||
|
|
||||||
|
class B:
|
||||||
|
foo: A # always okay
|
||||||
|
bar: dict[str, A] # always okay
|
||||||
|
|
||||||
|
class Leaf: ...
|
||||||
|
class Tree(list[Tree | Leaf]): ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
class Tree2(list["Tree | Leaf"]): ... # always okay
|
||||||
|
|
||||||
|
# Annotations are treated as assignments in .pyi files, but not in .py files
|
||||||
|
class MyClass:
|
||||||
|
foo: int
|
||||||
|
bar = foo # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
bar = "foo" # always okay
|
||||||
|
|
||||||
|
baz: MyClass
|
||||||
|
eggs = baz # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
eggs = "baz" # always okay
|
||||||
44
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.pyi
vendored
Normal file
44
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.pyi
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"""Tests for constructs allowed in `.pyi` stub files but not at runtime"""
|
||||||
|
|
||||||
|
from typing import Optional, TypeAlias, Union
|
||||||
|
|
||||||
|
__version__: str
|
||||||
|
__author__: str
|
||||||
|
|
||||||
|
# Forward references:
|
||||||
|
MaybeCStr: TypeAlias = Optional[CStr] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
MaybeCStr2: TypeAlias = Optional["CStr"] # always okay
|
||||||
|
CStr: TypeAlias = Union[C, str] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
CStr2: TypeAlias = Union["C", str] # always okay
|
||||||
|
|
||||||
|
# References to a class from inside the class:
|
||||||
|
class C:
|
||||||
|
other: C = ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
other2: "C" = ... # always okay
|
||||||
|
def from_str(self, s: str) -> C: ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
def from_str2(self, s: str) -> "C": ... # always okay
|
||||||
|
|
||||||
|
# Circular references:
|
||||||
|
class A:
|
||||||
|
foo: B # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
foo2: "B" # always okay
|
||||||
|
bar: dict[str, B] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
bar2: dict[str, "A"] # always okay
|
||||||
|
|
||||||
|
class B:
|
||||||
|
foo: A # always okay
|
||||||
|
bar: dict[str, A] # always okay
|
||||||
|
|
||||||
|
class Leaf: ...
|
||||||
|
class Tree(list[Tree | Leaf]): ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
class Tree2(list["Tree | Leaf"]): ... # always okay
|
||||||
|
|
||||||
|
# Annotations are treated as assignments in .pyi files, but not in .py files
|
||||||
|
class MyClass:
|
||||||
|
foo: int
|
||||||
|
bar = foo # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
bar = "foo" # always okay
|
||||||
|
|
||||||
|
baz: MyClass
|
||||||
|
eggs = baz # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
eggs = "baz" # always okay
|
||||||
48
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_27.py
vendored
Normal file
48
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_27.py
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
"""Tests for constructs allowed when `__future__` annotations are enabled but not otherwise"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Optional, TypeAlias, Union
|
||||||
|
|
||||||
|
__version__: str
|
||||||
|
__author__: str
|
||||||
|
|
||||||
|
# References to a class from inside the class:
|
||||||
|
class C:
|
||||||
|
other: C = ... # valid when `__future__.annotations are enabled
|
||||||
|
other2: "C" = ... # always okay
|
||||||
|
def from_str(self, s: str) -> C: ... # valid when `__future__.annotations are enabled
|
||||||
|
def from_str2(self, s: str) -> "C": ... # always okay
|
||||||
|
|
||||||
|
# Circular references:
|
||||||
|
class A:
|
||||||
|
foo: B # valid when `__future__.annotations are enabled
|
||||||
|
foo2: "B" # always okay
|
||||||
|
bar: dict[str, B] # valid when `__future__.annotations are enabled
|
||||||
|
bar2: dict[str, "A"] # always okay
|
||||||
|
|
||||||
|
class B:
|
||||||
|
foo: A # always okay
|
||||||
|
bar: dict[str, A] # always okay
|
||||||
|
|
||||||
|
# Annotations are treated as assignments in .pyi files, but not in .py files
|
||||||
|
class MyClass:
|
||||||
|
foo: int
|
||||||
|
bar = foo # Still invalid even when `__future__.annotations` are enabled
|
||||||
|
bar = "foo" # always okay
|
||||||
|
|
||||||
|
baz: MyClass
|
||||||
|
eggs = baz # Still invalid even when `__future__.annotations` are enabled
|
||||||
|
eggs = "baz" # always okay
|
||||||
|
|
||||||
|
# Forward references:
|
||||||
|
MaybeDStr: TypeAlias = Optional[DStr] # Still invalid even when `__future__.annotations` are enabled
|
||||||
|
MaybeDStr2: TypeAlias = Optional["DStr"] # always okay
|
||||||
|
DStr: TypeAlias = Union[D, str] # Still invalid even when `__future__.annotations` are enabled
|
||||||
|
DStr2: TypeAlias = Union["D", str] # always okay
|
||||||
|
|
||||||
|
class D: ...
|
||||||
|
|
||||||
|
# More circular references
|
||||||
|
class Leaf: ...
|
||||||
|
class Tree(list[Tree | Leaf]): ... # Still invalid even when `__future__.annotations` are enabled
|
||||||
|
class Tree2(list["Tree | Leaf"]): ... # always okay
|
||||||
10
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_5.pyi
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_5.pyi
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
"""Test: inner class annotation."""
|
||||||
|
|
||||||
|
class RandomClass:
|
||||||
|
def bad_func(self) -> InnerClass: ... # F821
|
||||||
|
def good_func(self) -> OuterClass.InnerClass: ... # Okay
|
||||||
|
|
||||||
|
class OuterClass:
|
||||||
|
class InnerClass: ...
|
||||||
|
|
||||||
|
def good_func(self) -> InnerClass: ... # Okay
|
||||||
4
crates/ruff_linter/resources/test/fixtures/pyflakes/F822_0.pyi
vendored
Normal file
4
crates/ruff_linter/resources/test/fixtures/pyflakes/F822_0.pyi
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
a = 1
|
||||||
|
b: int # Considered a binding in a `.pyi` stub file, not in a `.py` runtime file
|
||||||
|
|
||||||
|
__all__ = ["a", "b", "c"] # c is flagged as missing; b is not
|
||||||
37
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_bool.py
vendored
Normal file
37
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_bool.py
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# These testcases should raise errors
|
||||||
|
|
||||||
|
class Float:
|
||||||
|
def __bool__(self):
|
||||||
|
return 3.05 # [invalid-bool-return]
|
||||||
|
|
||||||
|
class Int:
|
||||||
|
def __bool__(self):
|
||||||
|
return 0 # [invalid-bool-return]
|
||||||
|
|
||||||
|
|
||||||
|
class Str:
|
||||||
|
def __bool__(self):
|
||||||
|
x = "ruff"
|
||||||
|
return x # [invalid-bool-return]
|
||||||
|
|
||||||
|
# TODO: Once Ruff has better type checking
|
||||||
|
def return_int():
|
||||||
|
return 3
|
||||||
|
|
||||||
|
class ComplexReturn:
|
||||||
|
def __bool__(self):
|
||||||
|
return return_int() # [invalid-bool-return]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# These testcases should NOT raise errors
|
||||||
|
|
||||||
|
class Bool:
|
||||||
|
def __bool__(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Bool2:
|
||||||
|
def __bool__(self):
|
||||||
|
x = True
|
||||||
|
return x
|
||||||
@@ -1,28 +1,36 @@
|
|||||||
class Str:
|
# These testcases should raise errors
|
||||||
def __str__(self):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
class Float:
|
class Float:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 3.05
|
return 3.05
|
||||||
|
|
||||||
class Int:
|
class Int:
|
||||||
|
def __str__(self):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
class Int2:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
class Bool:
|
class Bool:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class Str2:
|
# TODO: Once Ruff has better type checking
|
||||||
def __str__(self):
|
|
||||||
x = "ruff"
|
|
||||||
return x
|
|
||||||
|
|
||||||
# TODO fixme once Ruff has better type checking
|
|
||||||
def return_int():
|
def return_int():
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
class ComplexReturn:
|
class ComplexReturn:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return return_int()
|
return return_int()
|
||||||
|
|
||||||
|
# These testcases should NOT raise errors
|
||||||
|
|
||||||
|
class Str:
|
||||||
|
def __str__(self):
|
||||||
|
return "ruff"
|
||||||
|
|
||||||
|
class Str2:
|
||||||
|
def __str__(self):
|
||||||
|
x = "ruff"
|
||||||
|
return x
|
||||||
|
|||||||
@@ -54,3 +54,15 @@ class StudentE(StudentD):
|
|||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StudentF(object):
|
||||||
|
__slots__ = ("name", "__dict__")
|
||||||
|
|
||||||
|
def __init__(self, name, middle_name):
|
||||||
|
self.name = name
|
||||||
|
self.middle_name = middle_name # [assigning-non-slot]
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
pass
|
||||||
|
|||||||
39
crates/ruff_linter/resources/test/fixtures/pylint/singledispatch_method.py
vendored
Normal file
39
crates/ruff_linter/resources/test/fixtures/pylint/singledispatch_method.py
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from functools import singledispatch, singledispatchmethod
|
||||||
|
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
def convert_position(position):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Board:
|
||||||
|
@singledispatch # [singledispatch-method]
|
||||||
|
@classmethod
|
||||||
|
def convert_position(cls, position):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@singledispatch # [singledispatch-method]
|
||||||
|
def move(self, position):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@singledispatchmethod
|
||||||
|
def place(self, position):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
@staticmethod
|
||||||
|
def do(position):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# False negative (flagged by Pylint).
|
||||||
|
@convert_position.register
|
||||||
|
@classmethod
|
||||||
|
def _(cls, position: str) -> tuple:
|
||||||
|
position_a, position_b = position.split(",")
|
||||||
|
return (int(position_a), int(position_b))
|
||||||
|
|
||||||
|
# False negative (flagged by Pylint).
|
||||||
|
@convert_position.register
|
||||||
|
@classmethod
|
||||||
|
def _(cls, position: tuple) -> str:
|
||||||
|
return f"{position[0]},{position[1]}"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
a = 2
|
||||||
print((3.0).__add__(4.0)) # PLC2801
|
print((3.0).__add__(4.0)) # PLC2801
|
||||||
print((3.0).__sub__(4.0)) # PLC2801
|
print((3.0).__sub__(4.0)) # PLC2801
|
||||||
print((3.0).__mul__(4.0)) # PLC2801
|
print((3.0).__mul__(4.0)) # PLC2801
|
||||||
@@ -17,6 +17,67 @@ print((3.0).__str__()) # PLC2801
|
|||||||
print((3.0).__repr__()) # PLC2801
|
print((3.0).__repr__()) # PLC2801
|
||||||
print([1, 2, 3].__len__()) # PLC2801
|
print([1, 2, 3].__len__()) # PLC2801
|
||||||
print((1).__neg__()) # PLC2801
|
print((1).__neg__()) # PLC2801
|
||||||
|
print(-a.__sub__(1)) # PLC2801
|
||||||
|
print(-(a).__sub__(1)) # PLC2801
|
||||||
|
print(-(-a.__sub__(1))) # PLC2801
|
||||||
|
print((5 - a).__sub__(1)) # PLC2801
|
||||||
|
print(-(5 - a).__sub__(1)) # PLC2801
|
||||||
|
print(-(-5 - a).__sub__(1)) # PLC2801
|
||||||
|
print(+-+-+-a.__sub__(1)) # PLC2801
|
||||||
|
print(a.__rsub__(2 - 1)) # PLC2801
|
||||||
|
print(a.__sub__(((((1)))))) # PLC2801
|
||||||
|
print(a.__sub__(((((2 - 1)))))) # PLC2801
|
||||||
|
print(a.__sub__(
|
||||||
|
3
|
||||||
|
+
|
||||||
|
4
|
||||||
|
))
|
||||||
|
print(a.__rsub__(
|
||||||
|
3
|
||||||
|
+
|
||||||
|
4
|
||||||
|
))
|
||||||
|
print(2 * a.__add__(3)) # PLC2801
|
||||||
|
x = 2 * a.__add__(3) # PLC2801
|
||||||
|
x = 2 * -a.__add__(3) # PLC2801
|
||||||
|
x = a.__add__(3) # PLC2801
|
||||||
|
x = -a.__add__(3) # PLC2801
|
||||||
|
x = (-a).__add__(3) # PLC2801
|
||||||
|
x = -(-a).__add__(3) # PLC2801
|
||||||
|
|
||||||
|
# Calls
|
||||||
|
print(a.__call__()) # PLC2801 (no fix, intentional)
|
||||||
|
|
||||||
|
# Lambda expressions
|
||||||
|
blah = lambda: a.__add__(1) # PLC2801
|
||||||
|
|
||||||
|
# If expressions
|
||||||
|
print(a.__add__(1) if a > 0 else a.__sub__(1)) # PLC2801
|
||||||
|
|
||||||
|
# Dict/Set/List/Tuple
|
||||||
|
print({"a": a.__add__(1)}) # PLC2801
|
||||||
|
print({a.__add__(1)}) # PLC2801
|
||||||
|
print([a.__add__(1)]) # PLC2801
|
||||||
|
print((a.__add__(1),)) # PLC2801
|
||||||
|
|
||||||
|
# Comprehension variants
|
||||||
|
print({i: i.__add__(1) for i in range(5)}) # PLC2801
|
||||||
|
print({i.__add__(1) for i in range(5)}) # PLC2801
|
||||||
|
print([i.__add__(1) for i in range(5)]) # PLC2801
|
||||||
|
print((i.__add__(1) for i in range(5))) # PLC2801
|
||||||
|
|
||||||
|
# Generators
|
||||||
|
gen = (i.__add__(1) for i in range(5)) # PLC2801
|
||||||
|
print(next(gen))
|
||||||
|
|
||||||
|
# Subscripts
|
||||||
|
print({"a": a.__add__(1)}["a"]) # PLC2801
|
||||||
|
|
||||||
|
# Starred
|
||||||
|
print(*[a.__add__(1)]) # PLC2801
|
||||||
|
|
||||||
|
# Slices
|
||||||
|
print([a.__add__(1), a.__sub__(1)][0:1]) # PLC2801
|
||||||
|
|
||||||
|
|
||||||
class Thing:
|
class Thing:
|
||||||
|
|||||||
121
crates/ruff_linter/resources/test/fixtures/pylint/useless_exception_statement.py
vendored
Normal file
121
crates/ruff_linter/resources/test/fixtures/pylint/useless_exception_statement.py
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 1: Useless exception statement
|
||||||
|
def func():
|
||||||
|
AssertionError("This is an assertion error") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 2: Useless exception statement in try-except block
|
||||||
|
def func():
|
||||||
|
try:
|
||||||
|
Exception("This is an exception") # PLW0133
|
||||||
|
except Exception as err:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 3: Useless exception statement in if statement
|
||||||
|
def func():
|
||||||
|
if True:
|
||||||
|
RuntimeError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 4: Useless exception statement in class
|
||||||
|
def func():
|
||||||
|
class Class:
|
||||||
|
def __init__(self):
|
||||||
|
TypeError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 5: Useless exception statement in function
|
||||||
|
def func():
|
||||||
|
def inner():
|
||||||
|
IndexError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
inner()
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 6: Useless exception statement in while loop
|
||||||
|
def func():
|
||||||
|
while True:
|
||||||
|
KeyError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 7: Useless exception statement in abstract class
|
||||||
|
def func():
|
||||||
|
class Class(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def method(self):
|
||||||
|
NotImplementedError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 8: Useless exception statement inside context manager
|
||||||
|
def func():
|
||||||
|
with suppress(AttributeError):
|
||||||
|
AttributeError("This is an exception") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 9: Useless exception statement in parentheses
|
||||||
|
def func():
|
||||||
|
(RuntimeError("This is an exception")) # PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 10: Useless exception statement in continuation
|
||||||
|
def func():
|
||||||
|
x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 11: Useless warning statement
|
||||||
|
def func():
|
||||||
|
UserWarning("This is an assertion error") # PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Non-violation test cases: PLW0133
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 1: Used exception statement in try-except block
|
||||||
|
def func():
|
||||||
|
raise Exception("This is an exception") # OK
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 2: Used exception statement in if statement
|
||||||
|
def func():
|
||||||
|
if True:
|
||||||
|
raise ValueError("This is an exception") # OK
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 3: Used exception statement in class
|
||||||
|
def func():
|
||||||
|
class Class:
|
||||||
|
def __init__(self):
|
||||||
|
raise TypeError("This is an exception") # OK
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 4: Exception statement used in list comprehension
|
||||||
|
def func():
|
||||||
|
[ValueError("This is an exception") for i in range(10)] # OK
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 5: Exception statement used when initializing a dictionary
|
||||||
|
def func():
|
||||||
|
{i: TypeError("This is an exception") for i in range(10)} # OK
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 6: Exception statement used in function
|
||||||
|
def func():
|
||||||
|
def inner():
|
||||||
|
raise IndexError("This is an exception") # OK
|
||||||
|
|
||||||
|
inner()
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 7: Exception statement used in variable assignment
|
||||||
|
def func():
|
||||||
|
err = KeyError("This is an exception") # OK
|
||||||
|
|
||||||
|
|
||||||
|
# Test case 8: Exception statement inside context manager
|
||||||
|
def func():
|
||||||
|
with suppress(AttributeError):
|
||||||
|
raise AttributeError("This is an exception") # OK
|
||||||
@@ -252,3 +252,10 @@ raise ValueError(
|
|||||||
|
|
||||||
# The dictionary should be parenthesized.
|
# The dictionary should be parenthesized.
|
||||||
"{}".format({0: 1}())
|
"{}".format({0: 1}())
|
||||||
|
|
||||||
|
# The string shouldn't be converted, since it would require repeating the function call.
|
||||||
|
"{x} {x}".format(x=foo())
|
||||||
|
"{0} {0}".format(foo())
|
||||||
|
|
||||||
|
# The string _should_ be converted, since the function call is repeated in the arguments.
|
||||||
|
"{0} {1}".format(foo(), foo())
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ if (
|
|||||||
and some_third_reasonably_long_condition
|
and some_third_reasonably_long_condition
|
||||||
or some_fourth_reasonably_long_condition
|
or some_fourth_reasonably_long_condition
|
||||||
and some_fifth_reasonably_long_condition
|
and some_fifth_reasonably_long_condition
|
||||||
# a commment
|
# a comment
|
||||||
and some_sixth_reasonably_long_condition
|
and some_sixth_reasonably_long_condition
|
||||||
and some_seventh_reasonably_long_condition
|
and some_seventh_reasonably_long_condition
|
||||||
# another comment
|
# another comment
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ __all__ = [
|
|||||||
# we implement an "isort-style sort":
|
# we implement an "isort-style sort":
|
||||||
# SCEAMING_CASE constants first,
|
# SCEAMING_CASE constants first,
|
||||||
# then CamelCase classes,
|
# then CamelCase classes,
|
||||||
# then anything thats lowercase_snake_case.
|
# then anything that's lowercase_snake_case.
|
||||||
# This (which is currently alphabetically sorted)
|
# This (which is currently alphabetically sorted)
|
||||||
# should get reordered accordingly:
|
# should get reordered accordingly:
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|||||||
@@ -61,3 +61,29 @@ def fmt_on_trailing():
|
|||||||
# fmt: off
|
# fmt: off
|
||||||
val = 5 # fmt: on
|
val = 5 # fmt: on
|
||||||
pass # fmt: on
|
pass # fmt: on
|
||||||
|
|
||||||
|
|
||||||
|
# all of these should be fine
|
||||||
|
def match_case_and_elif():
|
||||||
|
string = "hello"
|
||||||
|
match string:
|
||||||
|
case ("C"
|
||||||
|
| "CX"
|
||||||
|
| "R"
|
||||||
|
| "RX"
|
||||||
|
| "S"
|
||||||
|
| "SP"
|
||||||
|
| "WAP"
|
||||||
|
| "XX"
|
||||||
|
| "Y"
|
||||||
|
| "YY"
|
||||||
|
| "YZ"
|
||||||
|
| "Z"
|
||||||
|
| "ZZ"
|
||||||
|
): # fmt: skip
|
||||||
|
pass
|
||||||
|
case _: # fmt: skip
|
||||||
|
if string != "Hello":
|
||||||
|
pass
|
||||||
|
elif string == "Hello": # fmt: skip
|
||||||
|
pass
|
||||||
|
|||||||
@@ -53,3 +53,6 @@ class Labware:
|
|||||||
|
|
||||||
|
|
||||||
assert getattr(Labware(), "µL") == 1.5
|
assert getattr(Labware(), "µL") == 1.5
|
||||||
|
|
||||||
|
# Implicit string concatenation
|
||||||
|
x = "𝐁ad" f"𝐁ad string"
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use crate::checkers::ast::Checker;
|
|||||||
use crate::codes::Rule;
|
use crate::codes::Rule;
|
||||||
use crate::fix;
|
use crate::fix;
|
||||||
use crate::rules::{
|
use crate::rules::{
|
||||||
flake8_builtins, flake8_pyi, flake8_type_checking, flake8_unused_arguments, pyflakes, pylint,
|
flake8_builtins, flake8_pyi, flake8_type_checking, flake8_unused_arguments, pep8_naming,
|
||||||
ruff,
|
pyflakes, pylint, ruff,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
|
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
|
||||||
@@ -18,6 +18,8 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
|||||||
Rule::GlobalVariableNotAssigned,
|
Rule::GlobalVariableNotAssigned,
|
||||||
Rule::ImportPrivateName,
|
Rule::ImportPrivateName,
|
||||||
Rule::ImportShadowedByLoopVar,
|
Rule::ImportShadowedByLoopVar,
|
||||||
|
Rule::InvalidFirstArgumentNameForMethod,
|
||||||
|
Rule::InvalidFirstArgumentNameForClassMethod,
|
||||||
Rule::NoSelfUse,
|
Rule::NoSelfUse,
|
||||||
Rule::RedefinedArgumentFromLocal,
|
Rule::RedefinedArgumentFromLocal,
|
||||||
Rule::RedefinedWhileUnused,
|
Rule::RedefinedWhileUnused,
|
||||||
@@ -40,6 +42,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
|||||||
Rule::UnusedPrivateTypedDict,
|
Rule::UnusedPrivateTypedDict,
|
||||||
Rule::UnusedStaticMethodArgument,
|
Rule::UnusedStaticMethodArgument,
|
||||||
Rule::UnusedVariable,
|
Rule::UnusedVariable,
|
||||||
|
Rule::SingledispatchMethod,
|
||||||
]) {
|
]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -256,23 +259,29 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
|||||||
diagnostic.set_parent(range.start());
|
diagnostic.set_parent(range.start());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(import) = binding.as_any_import() {
|
// Remove the import if the binding and the shadowed binding are both imports,
|
||||||
if let Some(source) = binding.source {
|
// and both point to the same qualified name.
|
||||||
diagnostic.try_set_fix(|| {
|
if let Some(shadowed_import) = shadowed.as_any_import() {
|
||||||
let statement = checker.semantic().statement(source);
|
if let Some(import) = binding.as_any_import() {
|
||||||
let parent = checker.semantic().parent_statement(source);
|
if shadowed_import.qualified_name() == import.qualified_name() {
|
||||||
let edit = fix::edits::remove_unused_imports(
|
if let Some(source) = binding.source {
|
||||||
std::iter::once(import.member_name().as_ref()),
|
diagnostic.try_set_fix(|| {
|
||||||
statement,
|
let statement = checker.semantic().statement(source);
|
||||||
parent,
|
let parent = checker.semantic().parent_statement(source);
|
||||||
checker.locator(),
|
let edit = fix::edits::remove_unused_imports(
|
||||||
checker.stylist(),
|
std::iter::once(import.member_name().as_ref()),
|
||||||
checker.indexer(),
|
statement,
|
||||||
)?;
|
parent,
|
||||||
Ok(Fix::safe_edit(edit).isolate(Checker::isolation(
|
checker.locator(),
|
||||||
checker.semantic().parent_statement_id(source),
|
checker.stylist(),
|
||||||
)))
|
checker.indexer(),
|
||||||
});
|
)?;
|
||||||
|
Ok(Fix::safe_edit(edit).isolate(Checker::isolation(
|
||||||
|
checker.semantic().parent_statement_id(source),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,6 +398,17 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
|||||||
if checker.enabled(Rule::TooManyLocals) {
|
if checker.enabled(Rule::TooManyLocals) {
|
||||||
pylint::rules::too_many_locals(checker, scope, &mut diagnostics);
|
pylint::rules::too_many_locals(checker, scope, &mut diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if checker.enabled(Rule::SingledispatchMethod) {
|
||||||
|
pylint::rules::singledispatch_method(checker, scope, &mut diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if checker.any_enabled(&[
|
||||||
|
Rule::InvalidFirstArgumentNameForClassMethod,
|
||||||
|
Rule::InvalidFirstArgumentNameForMethod,
|
||||||
|
]) {
|
||||||
|
pep8_naming::rules::invalid_first_argument_name(checker, scope, &mut diagnostics);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checker.diagnostics.extend(diagnostics);
|
checker.diagnostics.extend(diagnostics);
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
|||||||
pyupgrade::rules::format_literals(checker, call, &summary);
|
pyupgrade::rules::format_literals(checker, call, &summary);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::FString) {
|
if checker.enabled(Rule::FString) {
|
||||||
pyupgrade::rules::f_strings(checker, call, &summary, value);
|
pyupgrade::rules::f_strings(checker, call, &summary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -632,6 +632,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
|||||||
]) {
|
]) {
|
||||||
flake8_bandit::rules::shell_injection(checker, call);
|
flake8_bandit::rules::shell_injection(checker, call);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::DjangoExtra) {
|
||||||
|
flake8_bandit::rules::django_extra(checker, call);
|
||||||
|
}
|
||||||
if checker.enabled(Rule::DjangoRawSql) {
|
if checker.enabled(Rule::DjangoRawSql) {
|
||||||
flake8_bandit::rules::django_raw_sql(checker, call);
|
flake8_bandit::rules::django_raw_sql(checker, call);
|
||||||
}
|
}
|
||||||
@@ -1327,8 +1330,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::IfExp(
|
Expr::If(
|
||||||
if_exp @ ast::ExprIfExp {
|
if_exp @ ast::ExprIf {
|
||||||
test,
|
test,
|
||||||
body,
|
body,
|
||||||
orelse,
|
orelse,
|
||||||
@@ -1450,8 +1453,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
|||||||
flake8_bugbear::rules::static_key_dict_comprehension(checker, dict_comp);
|
flake8_bugbear::rules::static_key_dict_comprehension(checker, dict_comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::GeneratorExp(
|
Expr::Generator(
|
||||||
generator @ ast::ExprGeneratorExp {
|
generator @ ast::ExprGenerator {
|
||||||
generators,
|
generators,
|
||||||
elt: _,
|
elt: _,
|
||||||
range: _,
|
range: _,
|
||||||
@@ -1517,7 +1520,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
|||||||
ruff::rules::parenthesize_chained_logical_operators(checker, bool_op);
|
ruff::rules::parenthesize_chained_logical_operators(checker, bool_op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::NamedExpr(..) => {
|
Expr::Named(..) => {
|
||||||
if checker.enabled(Rule::AssignmentInAssert) {
|
if checker.enabled(Rule::AssignmentInAssert) {
|
||||||
ruff::rules::assignment_in_assert(checker, expr);
|
ruff::rules::assignment_in_assert(checker, expr);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::rules::{flake8_builtins, pep8_naming, pycodestyle};
|
|||||||
pub(crate) fn parameter(parameter: &Parameter, checker: &mut Checker) {
|
pub(crate) fn parameter(parameter: &Parameter, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::AmbiguousVariableName) {
|
if checker.enabled(Rule::AmbiguousVariableName) {
|
||||||
if let Some(diagnostic) =
|
if let Some(diagnostic) =
|
||||||
pycodestyle::rules::ambiguous_variable_name(¶meter.name, parameter.range())
|
pycodestyle::rules::ambiguous_variable_name(¶meter.name, parameter.name.range())
|
||||||
{
|
{
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
|||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::InvalidBoolReturnType) {
|
||||||
|
pylint::rules::invalid_bool_return(checker, name, body);
|
||||||
|
}
|
||||||
if checker.enabled(Rule::InvalidStrReturnType) {
|
if checker.enabled(Rule::InvalidStrReturnType) {
|
||||||
pylint::rules::invalid_str_return(checker, name, body);
|
pylint::rules::invalid_str_return(checker, name, body);
|
||||||
}
|
}
|
||||||
@@ -105,30 +108,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
|||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::InvalidFirstArgumentNameForClassMethod) {
|
|
||||||
if let Some(diagnostic) =
|
|
||||||
pep8_naming::rules::invalid_first_argument_name_for_class_method(
|
|
||||||
checker,
|
|
||||||
checker.semantic.current_scope(),
|
|
||||||
name,
|
|
||||||
decorator_list,
|
|
||||||
parameters,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
checker.diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if checker.enabled(Rule::InvalidFirstArgumentNameForMethod) {
|
|
||||||
if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method(
|
|
||||||
checker,
|
|
||||||
checker.semantic.current_scope(),
|
|
||||||
name,
|
|
||||||
decorator_list,
|
|
||||||
parameters,
|
|
||||||
) {
|
|
||||||
checker.diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if checker.source_type.is_stub() {
|
if checker.source_type.is_stub() {
|
||||||
if checker.enabled(Rule::PassStatementStubBody) {
|
if checker.enabled(Rule::PassStatementStubBody) {
|
||||||
flake8_pyi::rules::pass_statement_stub_body(checker, body);
|
flake8_pyi::rules::pass_statement_stub_body(checker, body);
|
||||||
@@ -1609,7 +1588,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
|||||||
refurb::rules::delete_full_slice(checker, delete);
|
refurb::rules::delete_full_slice(checker, delete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
|
Stmt::Expr(expr @ ast::StmtExpr { value, range: _ }) => {
|
||||||
if checker.enabled(Rule::UselessComparison) {
|
if checker.enabled(Rule::UselessComparison) {
|
||||||
flake8_bugbear::rules::useless_comparison(checker, value);
|
flake8_bugbear::rules::useless_comparison(checker, value);
|
||||||
}
|
}
|
||||||
@@ -1632,6 +1611,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
|||||||
if checker.enabled(Rule::RepeatedAppend) {
|
if checker.enabled(Rule::RepeatedAppend) {
|
||||||
refurb::rules::repeated_append(checker, stmt);
|
refurb::rules::repeated_append(checker, stmt);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::UselessExceptionStatement) {
|
||||||
|
pylint::rules::useless_exception_statement(checker, expr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,10 +43,11 @@ use ruff_python_ast::helpers::{
|
|||||||
collect_import_from_member, extract_handled_exceptions, is_docstring_stmt, to_module_path,
|
collect_import_from_member, extract_handled_exceptions, is_docstring_stmt, to_module_path,
|
||||||
};
|
};
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::str::trailing_quote;
|
use ruff_python_ast::name::QualifiedName;
|
||||||
use ruff_python_ast::visitor::{walk_except_handler, walk_f_string_element, walk_pattern, Visitor};
|
use ruff_python_ast::str::Quote;
|
||||||
|
use ruff_python_ast::visitor::{walk_except_handler, walk_pattern, Visitor};
|
||||||
use ruff_python_ast::{helpers, str, visitor, PySourceType};
|
use ruff_python_ast::{helpers, str, visitor, PySourceType};
|
||||||
use ruff_python_codegen::{Generator, Quote, Stylist};
|
use ruff_python_codegen::{Generator, Stylist};
|
||||||
use ruff_python_index::Indexer;
|
use ruff_python_index::Indexer;
|
||||||
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
|
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
|
||||||
use ruff_python_semantic::analyze::{imports, typing, visibility};
|
use ruff_python_semantic::analyze::{imports, typing, visibility};
|
||||||
@@ -227,16 +228,11 @@ impl<'a> Checker<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find the quote character used to start the containing f-string.
|
// Find the quote character used to start the containing f-string.
|
||||||
let expr = self.semantic.current_expression()?;
|
let ast::ExprFString { value, .. } = self
|
||||||
let string_range = self.indexer.fstring_ranges().innermost(expr.start())?;
|
.semantic
|
||||||
let trailing_quote = trailing_quote(self.locator.slice(string_range))?;
|
.current_expressions()
|
||||||
|
.find_map(|expr| expr.as_f_string_expr())?;
|
||||||
// Invert the quote character, if it's a single quote.
|
Some(value.iter().next()?.quote_style().opposite())
|
||||||
match trailing_quote {
|
|
||||||
"'" => Some(Quote::Double),
|
|
||||||
"\"" => Some(Quote::Single),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`SourceRow`] for the given offset.
|
/// Returns the [`SourceRow`] for the given offset.
|
||||||
@@ -320,11 +316,8 @@ impl<'a> Checker<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Visitor<'b> for Checker<'a>
|
impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
where
|
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||||
'b: 'a,
|
|
||||||
{
|
|
||||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
|
||||||
// Step 0: Pre-processing
|
// Step 0: Pre-processing
|
||||||
self.semantic.push_node(stmt);
|
self.semantic.push_node(stmt);
|
||||||
|
|
||||||
@@ -421,11 +414,13 @@ where
|
|||||||
self.semantic.add_module(module);
|
self.semantic.add_module(module);
|
||||||
|
|
||||||
if alias.asname.is_none() && alias.name.contains('.') {
|
if alias.asname.is_none() && alias.name.contains('.') {
|
||||||
let call_path: Box<[&str]> = alias.name.split('.').collect();
|
let qualified_name = QualifiedName::user_defined(&alias.name);
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
module,
|
module,
|
||||||
alias.identifier(),
|
alias.identifier(),
|
||||||
BindingKind::SubmoduleImport(SubmoduleImport { call_path }),
|
BindingKind::SubmoduleImport(SubmoduleImport {
|
||||||
|
qualified_name: Box::new(qualified_name),
|
||||||
|
}),
|
||||||
BindingFlags::EXTERNAL,
|
BindingFlags::EXTERNAL,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -442,11 +437,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let name = alias.asname.as_ref().unwrap_or(&alias.name);
|
let name = alias.asname.as_ref().unwrap_or(&alias.name);
|
||||||
let call_path: Box<[&str]> = alias.name.split('.').collect();
|
let qualified_name = QualifiedName::user_defined(&alias.name);
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
name,
|
name,
|
||||||
alias.identifier(),
|
alias.identifier(),
|
||||||
BindingKind::Import(Import { call_path }),
|
BindingKind::Import(Import {
|
||||||
|
qualified_name: Box::new(qualified_name),
|
||||||
|
}),
|
||||||
flags,
|
flags,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -506,12 +503,13 @@ where
|
|||||||
// Attempt to resolve any relative imports; but if we don't know the current
|
// Attempt to resolve any relative imports; but if we don't know the current
|
||||||
// module path, or the relative import extends beyond the package root,
|
// module path, or the relative import extends beyond the package root,
|
||||||
// fallback to a literal representation (e.g., `[".", "foo"]`).
|
// fallback to a literal representation (e.g., `[".", "foo"]`).
|
||||||
let call_path = collect_import_from_member(level, module, &alias.name)
|
let qualified_name = collect_import_from_member(level, module, &alias.name);
|
||||||
.into_boxed_slice();
|
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
name,
|
name,
|
||||||
alias.identifier(),
|
alias.identifier(),
|
||||||
BindingKind::FromImport(FromImport { call_path }),
|
BindingKind::FromImport(FromImport {
|
||||||
|
qualified_name: Box::new(qualified_name),
|
||||||
|
}),
|
||||||
flags,
|
flags,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -754,8 +752,8 @@ where
|
|||||||
}) => {
|
}) => {
|
||||||
let mut handled_exceptions = Exceptions::empty();
|
let mut handled_exceptions = Exceptions::empty();
|
||||||
for type_ in extract_handled_exceptions(handlers) {
|
for type_ in extract_handled_exceptions(handlers) {
|
||||||
if let Some(call_path) = self.semantic.resolve_call_path(type_) {
|
if let Some(qualified_name) = self.semantic.resolve_qualified_name(type_) {
|
||||||
match call_path.as_slice() {
|
match qualified_name.segments() {
|
||||||
["", "NameError"] => {
|
["", "NameError"] => {
|
||||||
handled_exceptions |= Exceptions::NAME_ERROR;
|
handled_exceptions |= Exceptions::NAME_ERROR;
|
||||||
}
|
}
|
||||||
@@ -922,19 +920,20 @@ where
|
|||||||
self.last_stmt_end = stmt.end();
|
self.last_stmt_end = stmt.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_annotation(&mut self, expr: &'b Expr) {
|
fn visit_annotation(&mut self, expr: &'a Expr) {
|
||||||
let flags_snapshot = self.semantic.flags;
|
let flags_snapshot = self.semantic.flags;
|
||||||
self.semantic.flags |= SemanticModelFlags::TYPING_ONLY_ANNOTATION;
|
self.semantic.flags |= SemanticModelFlags::TYPING_ONLY_ANNOTATION;
|
||||||
self.visit_type_definition(expr);
|
self.visit_type_definition(expr);
|
||||||
self.semantic.flags = flags_snapshot;
|
self.semantic.flags = flags_snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||||
// Step 0: Pre-processing
|
// Step 0: Pre-processing
|
||||||
if !self.semantic.in_typing_literal()
|
if !self.semantic.in_typing_literal()
|
||||||
&& !self.semantic.in_deferred_type_definition()
|
&& !self.semantic.in_deferred_type_definition()
|
||||||
&& self.semantic.in_type_definition()
|
&& self.semantic.in_type_definition()
|
||||||
&& self.semantic.future_annotations()
|
&& self.semantic.future_annotations()
|
||||||
|
&& (self.semantic.in_typing_only_annotation() || self.source_type.is_stub())
|
||||||
{
|
{
|
||||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
||||||
self.visit.string_type_definitions.push((
|
self.visit.string_type_definitions.push((
|
||||||
@@ -1003,7 +1002,7 @@ where
|
|||||||
generators,
|
generators,
|
||||||
range: _,
|
range: _,
|
||||||
})
|
})
|
||||||
| Expr::GeneratorExp(ast::ExprGeneratorExp {
|
| Expr::Generator(ast::ExprGenerator {
|
||||||
elt,
|
elt,
|
||||||
generators,
|
generators,
|
||||||
range: _,
|
range: _,
|
||||||
@@ -1051,7 +1050,7 @@ where
|
|||||||
self.visit.lambdas.push(self.semantic.snapshot());
|
self.visit.lambdas.push(self.semantic.snapshot());
|
||||||
self.analyze.lambdas.push(self.semantic.snapshot());
|
self.analyze.lambdas.push(self.semantic.snapshot());
|
||||||
}
|
}
|
||||||
Expr::IfExp(ast::ExprIfExp {
|
Expr::If(ast::ExprIf {
|
||||||
test,
|
test,
|
||||||
body,
|
body,
|
||||||
orelse,
|
orelse,
|
||||||
@@ -1068,42 +1067,54 @@ where
|
|||||||
}) => {
|
}) => {
|
||||||
self.visit_expr(func);
|
self.visit_expr(func);
|
||||||
|
|
||||||
let callable = self.semantic.resolve_call_path(func).and_then(|call_path| {
|
let callable =
|
||||||
if self.semantic.match_typing_call_path(&call_path, "cast") {
|
self.semantic
|
||||||
Some(typing::Callable::Cast)
|
.resolve_qualified_name(func)
|
||||||
} else if self.semantic.match_typing_call_path(&call_path, "NewType") {
|
.and_then(|qualified_name| {
|
||||||
Some(typing::Callable::NewType)
|
if self
|
||||||
} else if self.semantic.match_typing_call_path(&call_path, "TypeVar") {
|
.semantic
|
||||||
Some(typing::Callable::TypeVar)
|
.match_typing_qualified_name(&qualified_name, "cast")
|
||||||
} else if self
|
{
|
||||||
.semantic
|
Some(typing::Callable::Cast)
|
||||||
.match_typing_call_path(&call_path, "NamedTuple")
|
} else if self
|
||||||
{
|
.semantic
|
||||||
Some(typing::Callable::NamedTuple)
|
.match_typing_qualified_name(&qualified_name, "NewType")
|
||||||
} else if self
|
{
|
||||||
.semantic
|
Some(typing::Callable::NewType)
|
||||||
.match_typing_call_path(&call_path, "TypedDict")
|
} else if self
|
||||||
{
|
.semantic
|
||||||
Some(typing::Callable::TypedDict)
|
.match_typing_qualified_name(&qualified_name, "TypeVar")
|
||||||
} else if matches!(
|
{
|
||||||
call_path.as_slice(),
|
Some(typing::Callable::TypeVar)
|
||||||
[
|
} else if self
|
||||||
"mypy_extensions",
|
.semantic
|
||||||
"Arg"
|
.match_typing_qualified_name(&qualified_name, "NamedTuple")
|
||||||
| "DefaultArg"
|
{
|
||||||
| "NamedArg"
|
Some(typing::Callable::NamedTuple)
|
||||||
| "DefaultNamedArg"
|
} else if self
|
||||||
| "VarArg"
|
.semantic
|
||||||
| "KwArg"
|
.match_typing_qualified_name(&qualified_name, "TypedDict")
|
||||||
]
|
{
|
||||||
) {
|
Some(typing::Callable::TypedDict)
|
||||||
Some(typing::Callable::MypyExtension)
|
} else if matches!(
|
||||||
} else if matches!(call_path.as_slice(), ["", "bool"]) {
|
qualified_name.segments(),
|
||||||
Some(typing::Callable::Bool)
|
[
|
||||||
} else {
|
"mypy_extensions",
|
||||||
None
|
"Arg"
|
||||||
}
|
| "DefaultArg"
|
||||||
});
|
| "NamedArg"
|
||||||
|
| "DefaultNamedArg"
|
||||||
|
| "VarArg"
|
||||||
|
| "KwArg"
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
Some(typing::Callable::MypyExtension)
|
||||||
|
} else if matches!(qualified_name.segments(), ["", "bool"]) {
|
||||||
|
Some(typing::Callable::Bool)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
match callable {
|
match callable {
|
||||||
Some(typing::Callable::Bool) => {
|
Some(typing::Callable::Bool) => {
|
||||||
let mut args = arguments.args.iter();
|
let mut args = arguments.args.iter();
|
||||||
@@ -1333,7 +1344,7 @@ where
|
|||||||
{
|
{
|
||||||
let mut iter = elts.iter();
|
let mut iter = elts.iter();
|
||||||
if let Some(expr) = iter.next() {
|
if let Some(expr) = iter.next() {
|
||||||
self.visit_expr(expr);
|
self.visit_type_definition(expr);
|
||||||
}
|
}
|
||||||
for expr in iter {
|
for expr in iter {
|
||||||
self.visit_non_type_definition(expr);
|
self.visit_non_type_definition(expr);
|
||||||
@@ -1363,7 +1374,7 @@ where
|
|||||||
self.semantic.flags |= SemanticModelFlags::F_STRING;
|
self.semantic.flags |= SemanticModelFlags::F_STRING;
|
||||||
visitor::walk_expr(self, expr);
|
visitor::walk_expr(self, expr);
|
||||||
}
|
}
|
||||||
Expr::NamedExpr(ast::ExprNamedExpr {
|
Expr::Named(ast::ExprNamed {
|
||||||
target,
|
target,
|
||||||
value,
|
value,
|
||||||
range: _,
|
range: _,
|
||||||
@@ -1379,7 +1390,7 @@ where
|
|||||||
// Step 3: Clean-up
|
// Step 3: Clean-up
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Lambda(_)
|
Expr::Lambda(_)
|
||||||
| Expr::GeneratorExp(_)
|
| Expr::Generator(_)
|
||||||
| Expr::ListComp(_)
|
| Expr::ListComp(_)
|
||||||
| Expr::DictComp(_)
|
| Expr::DictComp(_)
|
||||||
| Expr::SetComp(_) => {
|
| Expr::SetComp(_) => {
|
||||||
@@ -1396,6 +1407,7 @@ where
|
|||||||
analyze::string_like(string_literal.into(), self);
|
analyze::string_like(string_literal.into(), self);
|
||||||
}
|
}
|
||||||
Expr::BytesLiteral(bytes_literal) => analyze::string_like(bytes_literal.into(), self),
|
Expr::BytesLiteral(bytes_literal) => analyze::string_like(bytes_literal.into(), self),
|
||||||
|
Expr::FString(f_string) => analyze::string_like(f_string.into(), self),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1403,7 +1415,7 @@ where
|
|||||||
self.semantic.pop_node();
|
self.semantic.pop_node();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_except_handler(&mut self, except_handler: &'b ExceptHandler) {
|
fn visit_except_handler(&mut self, except_handler: &'a ExceptHandler) {
|
||||||
let flags_snapshot = self.semantic.flags;
|
let flags_snapshot = self.semantic.flags;
|
||||||
self.semantic.flags |= SemanticModelFlags::EXCEPTION_HANDLER;
|
self.semantic.flags |= SemanticModelFlags::EXCEPTION_HANDLER;
|
||||||
|
|
||||||
@@ -1453,7 +1465,7 @@ where
|
|||||||
self.semantic.flags = flags_snapshot;
|
self.semantic.flags = flags_snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_parameters(&mut self, parameters: &'b Parameters) {
|
fn visit_parameters(&mut self, parameters: &'a Parameters) {
|
||||||
// Step 1: Binding.
|
// Step 1: Binding.
|
||||||
// Bind, but intentionally avoid walking default expressions, as we handle them
|
// Bind, but intentionally avoid walking default expressions, as we handle them
|
||||||
// upstream.
|
// upstream.
|
||||||
@@ -1477,7 +1489,7 @@ where
|
|||||||
analyze::parameters(parameters, self);
|
analyze::parameters(parameters, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_parameter(&mut self, parameter: &'b Parameter) {
|
fn visit_parameter(&mut self, parameter: &'a Parameter) {
|
||||||
// Step 1: Binding.
|
// Step 1: Binding.
|
||||||
// Bind, but intentionally avoid walking the annotation, as we handle it
|
// Bind, but intentionally avoid walking the annotation, as we handle it
|
||||||
// upstream.
|
// upstream.
|
||||||
@@ -1492,7 +1504,7 @@ where
|
|||||||
analyze::parameter(parameter, self);
|
analyze::parameter(parameter, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_pattern(&mut self, pattern: &'b Pattern) {
|
fn visit_pattern(&mut self, pattern: &'a Pattern) {
|
||||||
// Step 1: Binding
|
// Step 1: Binding
|
||||||
if let Pattern::MatchAs(ast::PatternMatchAs {
|
if let Pattern::MatchAs(ast::PatternMatchAs {
|
||||||
name: Some(name), ..
|
name: Some(name), ..
|
||||||
@@ -1517,7 +1529,7 @@ where
|
|||||||
walk_pattern(self, pattern);
|
walk_pattern(self, pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_body(&mut self, body: &'b [Stmt]) {
|
fn visit_body(&mut self, body: &'a [Stmt]) {
|
||||||
// Step 4: Analysis
|
// Step 4: Analysis
|
||||||
analyze::suite(body, self);
|
analyze::suite(body, self);
|
||||||
|
|
||||||
@@ -1527,7 +1539,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_match_case(&mut self, match_case: &'b MatchCase) {
|
fn visit_match_case(&mut self, match_case: &'a MatchCase) {
|
||||||
self.visit_pattern(&match_case.pattern);
|
self.visit_pattern(&match_case.pattern);
|
||||||
if let Some(expr) = &match_case.guard {
|
if let Some(expr) = &match_case.guard {
|
||||||
self.visit_boolean_test(expr);
|
self.visit_boolean_test(expr);
|
||||||
@@ -1538,7 +1550,7 @@ where
|
|||||||
self.semantic.pop_branch();
|
self.semantic.pop_branch();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_type_param(&mut self, type_param: &'b ast::TypeParam) {
|
fn visit_type_param(&mut self, type_param: &'a ast::TypeParam) {
|
||||||
// Step 1: Binding
|
// Step 1: Binding
|
||||||
match type_param {
|
match type_param {
|
||||||
ast::TypeParam::TypeVar(ast::TypeParamTypeVar { name, range, .. })
|
ast::TypeParam::TypeVar(ast::TypeParamTypeVar { name, range, .. })
|
||||||
@@ -1562,16 +1574,6 @@ where
|
|||||||
.push((bound, self.semantic.snapshot()));
|
.push((bound, self.semantic.snapshot()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_f_string_element(&mut self, f_string_element: &'b ast::FStringElement) {
|
|
||||||
// Step 2: Traversal
|
|
||||||
walk_f_string_element(self, f_string_element);
|
|
||||||
|
|
||||||
// Step 4: Analysis
|
|
||||||
if let Some(literal) = f_string_element.as_literal() {
|
|
||||||
analyze::string_like(literal.into(), self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Checker<'a> {
|
impl<'a> Checker<'a> {
|
||||||
@@ -1824,11 +1826,13 @@ impl<'a> Checker<'a> {
|
|||||||
flags.insert(BindingFlags::UNPACKED_ASSIGNMENT);
|
flags.insert(BindingFlags::UNPACKED_ASSIGNMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match the left-hand side of an annotated assignment, like `x` in `x: int`.
|
// Match the left-hand side of an annotated assignment without a value,
|
||||||
|
// like `x` in `x: int`. N.B. In stub files, these should be viewed
|
||||||
|
// as assignments on par with statements such as `x: int = 5`.
|
||||||
if matches!(
|
if matches!(
|
||||||
parent,
|
parent,
|
||||||
Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. })
|
Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. })
|
||||||
) && !self.semantic.in_annotation()
|
) && !(self.semantic.in_annotation() || self.source_type.is_stub())
|
||||||
{
|
{
|
||||||
self.add_binding(id, expr.range(), BindingKind::Annotation, flags);
|
self.add_binding(id, expr.range(), BindingKind::Annotation, flags);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::line_width::IndentWidth;
|
use crate::line_width::IndentWidth;
|
||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
|
use ruff_python_index::Indexer;
|
||||||
use ruff_python_parser::lexer::LexResult;
|
use ruff_python_parser::lexer::LexResult;
|
||||||
use ruff_python_parser::TokenKind;
|
use ruff_python_parser::TokenKind;
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
@@ -9,8 +10,8 @@ use ruff_text_size::{Ranged, TextRange};
|
|||||||
use crate::registry::AsRule;
|
use crate::registry::AsRule;
|
||||||
use crate::rules::pycodestyle::rules::logical_lines::{
|
use crate::rules::pycodestyle::rules::logical_lines::{
|
||||||
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
|
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
|
||||||
missing_whitespace_around_operator, space_after_comma, space_around_operator,
|
missing_whitespace_around_operator, redundant_backslash, space_after_comma,
|
||||||
whitespace_around_keywords, whitespace_around_named_parameter_equals,
|
space_around_operator, whitespace_around_keywords, whitespace_around_named_parameter_equals,
|
||||||
whitespace_before_comment, whitespace_before_parameters, LogicalLines, TokenFlags,
|
whitespace_before_comment, whitespace_before_parameters, LogicalLines, TokenFlags,
|
||||||
};
|
};
|
||||||
use crate::settings::LinterSettings;
|
use crate::settings::LinterSettings;
|
||||||
@@ -35,6 +36,7 @@ pub(crate) fn expand_indent(line: &str, indent_width: IndentWidth) -> usize {
|
|||||||
pub(crate) fn check_logical_lines(
|
pub(crate) fn check_logical_lines(
|
||||||
tokens: &[LexResult],
|
tokens: &[LexResult],
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
|
indexer: &Indexer,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
settings: &LinterSettings,
|
settings: &LinterSettings,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
@@ -73,6 +75,7 @@ pub(crate) fn check_logical_lines(
|
|||||||
|
|
||||||
if line.flags().contains(TokenFlags::BRACKET) {
|
if line.flags().contains(TokenFlags::BRACKET) {
|
||||||
whitespace_before_parameters(&line, &mut context);
|
whitespace_before_parameters(&line, &mut context);
|
||||||
|
redundant_backslash(&line, locator, indexer, &mut context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the indentation level.
|
// Extract the indentation level.
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ pub(crate) fn check_noqa(
|
|||||||
settings: &LinterSettings,
|
settings: &LinterSettings,
|
||||||
) -> Vec<usize> {
|
) -> Vec<usize> {
|
||||||
// Identify any codes that are globally exempted (within the current file).
|
// Identify any codes that are globally exempted (within the current file).
|
||||||
let exemption = FileExemption::try_extract(locator.contents(), comment_ranges, path, locator);
|
let exemption = FileExemption::try_extract(
|
||||||
|
locator.contents(),
|
||||||
|
comment_ranges,
|
||||||
|
&settings.external,
|
||||||
|
path,
|
||||||
|
locator,
|
||||||
|
);
|
||||||
|
|
||||||
// Extract all `noqa` directives.
|
// Extract all `noqa` directives.
|
||||||
let mut noqa_directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator);
|
let mut noqa_directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator);
|
||||||
|
|||||||
@@ -41,14 +41,8 @@ pub(crate) fn check_tokens(
|
|||||||
Rule::BlankLinesAfterFunctionOrClass,
|
Rule::BlankLinesAfterFunctionOrClass,
|
||||||
Rule::BlankLinesBeforeNestedDefinition,
|
Rule::BlankLinesBeforeNestedDefinition,
|
||||||
]) {
|
]) {
|
||||||
let mut blank_lines_checker = BlankLinesChecker::default();
|
BlankLinesChecker::new(locator, stylist, settings, source_type)
|
||||||
blank_lines_checker.check_lines(
|
.check_lines(tokens, &mut diagnostics);
|
||||||
tokens,
|
|
||||||
locator,
|
|
||||||
stylist,
|
|
||||||
settings.tab_size,
|
|
||||||
&mut diagnostics,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.rules.enabled(Rule::BlanketNOQA) {
|
if settings.rules.enabled(Rule::BlanketNOQA) {
|
||||||
@@ -209,6 +203,10 @@ pub(crate) fn check_tokens(
|
|||||||
flake8_fixme::rules::todos(&mut diagnostics, &todo_comments);
|
flake8_fixme::rules::todos(&mut diagnostics, &todo_comments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings.rules.enabled(Rule::TooManyNewlinesAtEndOfFile) {
|
||||||
|
pycodestyle::rules::too_many_newlines_at_end_of_file(&mut diagnostics, tokens);
|
||||||
|
}
|
||||||
|
|
||||||
diagnostics.retain(|diagnostic| settings.rules.enabled(diagnostic.kind.rule()));
|
diagnostics.retain(|diagnostic| settings.rules.enabled(diagnostic.kind.rule()));
|
||||||
|
|
||||||
diagnostics
|
diagnostics
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||||||
(Pycodestyle, "E401") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleImportsOnOneLine),
|
(Pycodestyle, "E401") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleImportsOnOneLine),
|
||||||
(Pycodestyle, "E402") => (RuleGroup::Stable, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile),
|
(Pycodestyle, "E402") => (RuleGroup::Stable, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile),
|
||||||
(Pycodestyle, "E501") => (RuleGroup::Stable, rules::pycodestyle::rules::LineTooLong),
|
(Pycodestyle, "E501") => (RuleGroup::Stable, rules::pycodestyle::rules::LineTooLong),
|
||||||
|
(Pycodestyle, "E502") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::RedundantBackslash),
|
||||||
(Pycodestyle, "E701") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleStatementsOnOneLineColon),
|
(Pycodestyle, "E701") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleStatementsOnOneLineColon),
|
||||||
(Pycodestyle, "E702") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleStatementsOnOneLineSemicolon),
|
(Pycodestyle, "E702") => (RuleGroup::Stable, rules::pycodestyle::rules::MultipleStatementsOnOneLineSemicolon),
|
||||||
(Pycodestyle, "E703") => (RuleGroup::Stable, rules::pycodestyle::rules::UselessSemicolon),
|
(Pycodestyle, "E703") => (RuleGroup::Stable, rules::pycodestyle::rules::UselessSemicolon),
|
||||||
@@ -167,6 +168,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||||||
(Pycodestyle, "W291") => (RuleGroup::Stable, rules::pycodestyle::rules::TrailingWhitespace),
|
(Pycodestyle, "W291") => (RuleGroup::Stable, rules::pycodestyle::rules::TrailingWhitespace),
|
||||||
(Pycodestyle, "W292") => (RuleGroup::Stable, rules::pycodestyle::rules::MissingNewlineAtEndOfFile),
|
(Pycodestyle, "W292") => (RuleGroup::Stable, rules::pycodestyle::rules::MissingNewlineAtEndOfFile),
|
||||||
(Pycodestyle, "W293") => (RuleGroup::Stable, rules::pycodestyle::rules::BlankLineWithWhitespace),
|
(Pycodestyle, "W293") => (RuleGroup::Stable, rules::pycodestyle::rules::BlankLineWithWhitespace),
|
||||||
|
(Pycodestyle, "W391") => (RuleGroup::Preview, rules::pycodestyle::rules::TooManyNewlinesAtEndOfFile),
|
||||||
(Pycodestyle, "W505") => (RuleGroup::Stable, rules::pycodestyle::rules::DocLineTooLong),
|
(Pycodestyle, "W505") => (RuleGroup::Stable, rules::pycodestyle::rules::DocLineTooLong),
|
||||||
(Pycodestyle, "W605") => (RuleGroup::Stable, rules::pycodestyle::rules::InvalidEscapeSequence),
|
(Pycodestyle, "W605") => (RuleGroup::Stable, rules::pycodestyle::rules::InvalidEscapeSequence),
|
||||||
|
|
||||||
@@ -238,6 +240,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||||||
(Pylint, "E0237") => (RuleGroup::Stable, rules::pylint::rules::NonSlotAssignment),
|
(Pylint, "E0237") => (RuleGroup::Stable, rules::pylint::rules::NonSlotAssignment),
|
||||||
(Pylint, "E0241") => (RuleGroup::Stable, rules::pylint::rules::DuplicateBases),
|
(Pylint, "E0241") => (RuleGroup::Stable, rules::pylint::rules::DuplicateBases),
|
||||||
(Pylint, "E0302") => (RuleGroup::Stable, rules::pylint::rules::UnexpectedSpecialMethodSignature),
|
(Pylint, "E0302") => (RuleGroup::Stable, rules::pylint::rules::UnexpectedSpecialMethodSignature),
|
||||||
|
(Pylint, "E0304") => (RuleGroup::Preview, rules::pylint::rules::InvalidBoolReturnType),
|
||||||
(Pylint, "E0307") => (RuleGroup::Stable, rules::pylint::rules::InvalidStrReturnType),
|
(Pylint, "E0307") => (RuleGroup::Stable, rules::pylint::rules::InvalidStrReturnType),
|
||||||
(Pylint, "E0604") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllObject),
|
(Pylint, "E0604") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllObject),
|
||||||
(Pylint, "E0605") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllFormat),
|
(Pylint, "E0605") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllFormat),
|
||||||
@@ -252,6 +255,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||||||
(Pylint, "E1307") => (RuleGroup::Stable, rules::pylint::rules::BadStringFormatType),
|
(Pylint, "E1307") => (RuleGroup::Stable, rules::pylint::rules::BadStringFormatType),
|
||||||
(Pylint, "E1310") => (RuleGroup::Stable, rules::pylint::rules::BadStrStripCall),
|
(Pylint, "E1310") => (RuleGroup::Stable, rules::pylint::rules::BadStrStripCall),
|
||||||
(Pylint, "E1507") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarValue),
|
(Pylint, "E1507") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarValue),
|
||||||
|
(Pylint, "E1519") => (RuleGroup::Preview, rules::pylint::rules::SingledispatchMethod),
|
||||||
(Pylint, "E1700") => (RuleGroup::Stable, rules::pylint::rules::YieldFromInAsyncFunction),
|
(Pylint, "E1700") => (RuleGroup::Stable, rules::pylint::rules::YieldFromInAsyncFunction),
|
||||||
(Pylint, "E2502") => (RuleGroup::Stable, rules::pylint::rules::BidirectionalUnicode),
|
(Pylint, "E2502") => (RuleGroup::Stable, rules::pylint::rules::BidirectionalUnicode),
|
||||||
(Pylint, "E2510") => (RuleGroup::Stable, rules::pylint::rules::InvalidCharacterBackspace),
|
(Pylint, "E2510") => (RuleGroup::Stable, rules::pylint::rules::InvalidCharacterBackspace),
|
||||||
@@ -292,6 +296,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||||||
(Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable),
|
(Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable),
|
||||||
(Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral),
|
(Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral),
|
||||||
(Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext),
|
(Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext),
|
||||||
|
(Pylint, "W0133") => (RuleGroup::Preview, rules::pylint::rules::UselessExceptionStatement),
|
||||||
(Pylint, "W0245") => (RuleGroup::Preview, rules::pylint::rules::SuperWithoutBrackets),
|
(Pylint, "W0245") => (RuleGroup::Preview, rules::pylint::rules::SuperWithoutBrackets),
|
||||||
(Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf),
|
(Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf),
|
||||||
(Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
|
(Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
|
||||||
@@ -678,6 +683,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||||||
(Flake8Bandit, "607") => (RuleGroup::Stable, rules::flake8_bandit::rules::StartProcessWithPartialPath),
|
(Flake8Bandit, "607") => (RuleGroup::Stable, rules::flake8_bandit::rules::StartProcessWithPartialPath),
|
||||||
(Flake8Bandit, "608") => (RuleGroup::Stable, rules::flake8_bandit::rules::HardcodedSQLExpression),
|
(Flake8Bandit, "608") => (RuleGroup::Stable, rules::flake8_bandit::rules::HardcodedSQLExpression),
|
||||||
(Flake8Bandit, "609") => (RuleGroup::Stable, rules::flake8_bandit::rules::UnixCommandWildcardInjection),
|
(Flake8Bandit, "609") => (RuleGroup::Stable, rules::flake8_bandit::rules::UnixCommandWildcardInjection),
|
||||||
|
(Flake8Bandit, "610") => (RuleGroup::Preview, rules::flake8_bandit::rules::DjangoExtra),
|
||||||
(Flake8Bandit, "611") => (RuleGroup::Stable, rules::flake8_bandit::rules::DjangoRawSql),
|
(Flake8Bandit, "611") => (RuleGroup::Stable, rules::flake8_bandit::rules::DjangoRawSql),
|
||||||
(Flake8Bandit, "612") => (RuleGroup::Stable, rules::flake8_bandit::rules::LoggingConfigInsecureListen),
|
(Flake8Bandit, "612") => (RuleGroup::Stable, rules::flake8_bandit::rules::LoggingConfigInsecureListen),
|
||||||
(Flake8Bandit, "701") => (RuleGroup::Stable, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
|
(Flake8Bandit, "701") => (RuleGroup::Stable, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
|
||||||
|
|||||||
@@ -1,44 +1,8 @@
|
|||||||
use libcst_native::{
|
use libcst_native::{
|
||||||
Expression, Name, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace, UnaryOperation,
|
Expression, LeftParen, Name, ParenthesizableWhitespace, ParenthesizedNode, RightParen,
|
||||||
|
SimpleWhitespace, UnaryOperation,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
|
|
||||||
match expr {
|
|
||||||
Expression::Call(expr) => {
|
|
||||||
compose_call_path_inner(&expr.func, parts);
|
|
||||||
}
|
|
||||||
Expression::Attribute(expr) => {
|
|
||||||
compose_call_path_inner(&expr.value, parts);
|
|
||||||
parts.push(expr.attr.value);
|
|
||||||
}
|
|
||||||
Expression::Name(expr) => {
|
|
||||||
parts.push(expr.value);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn compose_call_path(expr: &Expression) -> Option<String> {
|
|
||||||
let mut segments = vec![];
|
|
||||||
compose_call_path_inner(expr, &mut segments);
|
|
||||||
if segments.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(segments.join("."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn compose_module_path(module: &NameOrAttribute) -> String {
|
|
||||||
match module {
|
|
||||||
NameOrAttribute::N(name) => name.value.to_string(),
|
|
||||||
NameOrAttribute::A(attr) => {
|
|
||||||
let name = attr.attr.value;
|
|
||||||
let prefix = compose_call_path(&attr.value);
|
|
||||||
prefix.map_or_else(|| name.to_string(), |prefix| format!("{prefix}.{name}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a [`ParenthesizableWhitespace`] containing a single space.
|
/// Return a [`ParenthesizableWhitespace`] containing a single space.
|
||||||
pub(crate) fn space() -> ParenthesizableWhitespace<'static> {
|
pub(crate) fn space() -> ParenthesizableWhitespace<'static> {
|
||||||
ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" "))
|
ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" "))
|
||||||
@@ -61,6 +25,7 @@ pub(crate) fn negate<'a>(expression: &Expression<'a>) -> Expression<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the expression is `True` or `False`, return the opposite.
|
||||||
if let Expression::Name(ref expression) = expression {
|
if let Expression::Name(ref expression) = expression {
|
||||||
match expression.value {
|
match expression.value {
|
||||||
"True" => {
|
"True" => {
|
||||||
@@ -81,11 +46,32 @@ pub(crate) fn negate<'a>(expression: &Expression<'a>) -> Expression<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the expression is higher precedence than the unary `not`, we need to wrap it in
|
||||||
|
// parentheses.
|
||||||
|
//
|
||||||
|
// For example: given `a and b`, we need to return `not (a and b)`, rather than `not a and b`.
|
||||||
|
//
|
||||||
|
// See: <https://docs.python.org/3/reference/expressions.html#operator-precedence>
|
||||||
|
let needs_parens = matches!(
|
||||||
|
expression,
|
||||||
|
Expression::BooleanOperation(_)
|
||||||
|
| Expression::IfExp(_)
|
||||||
|
| Expression::Lambda(_)
|
||||||
|
| Expression::NamedExpr(_)
|
||||||
|
);
|
||||||
|
let has_parens = !expression.lpar().is_empty() && !expression.rpar().is_empty();
|
||||||
|
// Otherwise, wrap in a `not` operator.
|
||||||
Expression::UnaryOperation(Box::new(UnaryOperation {
|
Expression::UnaryOperation(Box::new(UnaryOperation {
|
||||||
operator: libcst_native::UnaryOp::Not {
|
operator: libcst_native::UnaryOp::Not {
|
||||||
whitespace_after: space(),
|
whitespace_after: space(),
|
||||||
},
|
},
|
||||||
expression: Box::new(expression.clone()),
|
expression: Box::new(if needs_parens && !has_parens {
|
||||||
|
expression
|
||||||
|
.clone()
|
||||||
|
.with_parens(LeftParen::default(), RightParen::default())
|
||||||
|
} else {
|
||||||
|
expression.clone()
|
||||||
|
}),
|
||||||
lpar: vec![],
|
lpar: vec![],
|
||||||
rpar: vec![],
|
rpar: vec![],
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -131,10 +131,7 @@ fn extract_noqa_line_for(lxr: &[LexResult], locator: &Locator, indexer: &Indexer
|
|||||||
|
|
||||||
// For multi-line strings, we expect `noqa` directives on the last line of the
|
// For multi-line strings, we expect `noqa` directives on the last line of the
|
||||||
// string.
|
// string.
|
||||||
Tok::String {
|
Tok::String { kind, .. } if kind.is_triple_quoted() => {
|
||||||
triple_quoted: true,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if locator.contains_line_break(*range) {
|
if locator.contains_line_break(*range) {
|
||||||
string_mappings.push(TextRange::new(
|
string_mappings.push(TextRange::new(
|
||||||
locator.line_start(range.start()),
|
locator.line_start(range.start()),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user