Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d9912a83a | ||
|
|
93258e8d5b | ||
|
|
b90027d037 | ||
|
|
060a25df09 | ||
|
|
f5d4676c13 | ||
|
|
df69dc9f8d | ||
|
|
cb6c37abd9 | ||
|
|
54de990621 | ||
|
|
b91b09b961 | ||
|
|
0bda1913d1 | ||
|
|
7e390d3772 | ||
|
|
0bf0aa28ac | ||
|
|
8088c5367a | ||
|
|
6fe8f8a272 | ||
|
|
bfae1f1412 | ||
|
|
b358cbf398 | ||
|
|
17c8817695 | ||
|
|
1dda669f9a | ||
|
|
3fbabfe126 | ||
|
|
20ab14e354 | ||
|
|
22d8a989d4 | ||
|
|
35082b28cd | ||
|
|
5aaf99b856 | ||
|
|
58bf6f5762 | ||
|
|
277cd80175 | ||
|
|
20a40771a5 | ||
|
|
4af3f43e5e | ||
|
|
0b1a36f8c8 | ||
|
|
64c2535e28 | ||
|
|
5510a6131e | ||
|
|
e5db72459e | ||
|
|
d66063bb33 | ||
|
|
506be68782 | ||
|
|
cb1d3df085 | ||
|
|
69dfe0a207 | ||
|
|
46a174a22e | ||
|
|
912c39ce2a | ||
|
|
b2638c62a5 | ||
|
|
eaa310429f | ||
|
|
019d9aebe9 | ||
|
|
f06c5dc896 | ||
|
|
c1dc4a60be | ||
|
|
70febb1862 | ||
|
|
4212b41796 | ||
|
|
bbad4b4c93 | ||
|
|
3ee1ec70cc | ||
|
|
ee5d95f751 | ||
|
|
d674e7946d | ||
|
|
20782ab02c | ||
|
|
073eddb1d9 | ||
|
|
e8da95d09c | ||
|
|
c324cb6202 | ||
|
|
774c77adae | ||
|
|
fd70cd789f | ||
|
|
08f3110f1e | ||
|
|
2314b9aaca | ||
|
|
cddc696896 | ||
|
|
6435e4e4aa | ||
|
|
ec7456bac0 | ||
|
|
b28556d739 | ||
|
|
4957d94beb | ||
|
|
5d554edace | ||
|
|
412688826c | ||
|
|
47d80f29a7 | ||
|
|
9dee1883ce | ||
|
|
f585e3e2dc | ||
|
|
578ddf1bb1 | ||
|
|
ed14fd9163 | ||
|
|
60eb11fa50 | ||
|
|
33caa2ab1c | ||
|
|
d9845a2628 | ||
|
|
1f14d9a9f7 | ||
|
|
0202a49297 | ||
|
|
d0591561c9 | ||
|
|
2f5859e79e | ||
|
|
16085339bc | ||
|
|
074812115f | ||
|
|
9b17724d77 | ||
|
|
0d4af9d3c6 | ||
|
|
1dbfab9a0c | ||
|
|
501cca8b72 | ||
|
|
626b0577cd | ||
|
|
017e829115 | ||
|
|
2590aa30ae | ||
|
|
852a8f4a4f | ||
|
|
8365d2e0fd | ||
|
|
5b726f70f4 | ||
|
|
727e389cac | ||
|
|
0cb438dd65 | ||
|
|
63a87dda63 | ||
|
|
359a68d18f | ||
|
|
948094e691 | ||
|
|
f1ed0f27c2 | ||
|
|
5ce6299e22 | ||
|
|
5373759f62 | ||
|
|
d9151b1948 | ||
|
|
b61ce7fa46 | ||
|
|
6fb6478887 | ||
|
|
bf729e7a77 | ||
|
|
6ca2aaa245 | ||
|
|
e306359411 | ||
|
|
aec80dc3ab | ||
|
|
10d937c1a1 | ||
|
|
653e51ae97 | ||
|
|
04f0625d23 | ||
|
|
29dc8b986b | ||
|
|
f750257ef4 | ||
|
|
0f2c9ab4d2 | ||
|
|
779a5a9c32 | ||
|
|
bba43029d4 | ||
|
|
1de5425f7d | ||
|
|
71573fd35c | ||
|
|
95e2f632e6 | ||
|
|
00a015ca24 | ||
|
|
94178a0320 | ||
|
|
7165f8f05d | ||
|
|
415e808046 | ||
|
|
9279114521 | ||
|
|
8b86e8004d | ||
|
|
a7fc785cc5 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -3,5 +3,8 @@
|
||||
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_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
|
||||
|
||||
ruff.schema.json linguist-generated=true text=auto eol=lf
|
||||
*.md.snap linguist-language=Markdown
|
||||
|
||||
37
.github/workflows/ci.yaml
vendored
37
.github/workflows/ci.yaml
vendored
@@ -23,8 +23,13 @@ jobs:
|
||||
name: "Determine changes"
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
# Flag that is raised when any code that affects linter is changed
|
||||
linter: ${{ steps.changed.outputs.linter_any_changed }}
|
||||
# Flag that is raised when any code that affects formatter is changed
|
||||
formatter: ${{ steps.changed.outputs.formatter_any_changed }}
|
||||
# Flag that is raised when any code is changed
|
||||
# This is superset of the linter and formatter
|
||||
code: ${{ steps.changed.outputs.code_any_changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -44,6 +49,7 @@ jobs:
|
||||
- "!crates/ruff_shrinking/**"
|
||||
- scripts/*
|
||||
- .github/workflows/ci.yaml
|
||||
- python/**
|
||||
|
||||
formatter:
|
||||
- Cargo.toml
|
||||
@@ -58,8 +64,15 @@ jobs:
|
||||
- crates/ruff_python_parser/**
|
||||
- crates/ruff_dev/**
|
||||
- scripts/*
|
||||
- python/**
|
||||
- .github/workflows/ci.yaml
|
||||
|
||||
code:
|
||||
- "*/**"
|
||||
- "!**/*.md"
|
||||
- "!docs/**"
|
||||
- "!assets/**"
|
||||
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -72,6 +85,8 @@ jobs:
|
||||
cargo-clippy:
|
||||
name: "cargo clippy"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -86,6 +101,8 @@ jobs:
|
||||
|
||||
cargo-test-linux:
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
|
||||
name: "cargo test (linux)"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -110,6 +127,8 @@ jobs:
|
||||
|
||||
cargo-test-windows:
|
||||
runs-on: windows-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
|
||||
name: "cargo test (windows)"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -127,6 +146,8 @@ jobs:
|
||||
|
||||
cargo-test-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
|
||||
name: "cargo test (wasm)"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -146,6 +167,8 @@ jobs:
|
||||
|
||||
cargo-fuzz:
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
|
||||
name: "cargo fuzz"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -163,6 +186,8 @@ jobs:
|
||||
scripts:
|
||||
name: "test scripts"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -186,8 +211,7 @@ jobs:
|
||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||
# Ecosystem check needs linter and/or formatter changes.
|
||||
if: github.event_name == 'pull_request' && ${{
|
||||
needs.determine_changes.outputs.linter == 'true' ||
|
||||
needs.determine_changes.outputs.formatter == 'true'
|
||||
needs.determine_changes.outputs.code == 'true'
|
||||
}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -296,6 +320,8 @@ jobs:
|
||||
cargo-udeps:
|
||||
name: "cargo udeps"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install nightly Rust toolchain"
|
||||
@@ -415,7 +441,10 @@ jobs:
|
||||
check-ruff-lsp:
|
||||
name: "test ruff-lsp"
|
||||
runs-on: ubuntu-latest
|
||||
needs: cargo-test-linux
|
||||
needs:
|
||||
- cargo-test-linux
|
||||
- determine_changes
|
||||
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: extractions/setup-just@v1
|
||||
env:
|
||||
@@ -453,6 +482,8 @@ jobs:
|
||||
|
||||
benchmarks:
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
58
.github/workflows/release.yaml
vendored
58
.github/workflows/release.yaml
vendored
@@ -516,6 +516,62 @@ jobs:
|
||||
files: binaries/*
|
||||
tag_name: v${{ inputs.tag }}
|
||||
|
||||
docker-publish:
|
||||
# This action doesn't need to wait on any other task, it's easy to re-tag if something failed and we're validating
|
||||
# the tag here also
|
||||
name: Push Docker image ghcr.io/astral-sh/ruff
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: release
|
||||
permissions:
|
||||
# For the docker push
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/astral-sh/ruff
|
||||
|
||||
- name: Check tag consistency
|
||||
# Unlike validate-tag we don't check if the commit is on the main branch, but it seems good enough since we can
|
||||
# change docker tags
|
||||
if: ${{ inputs.tag }}
|
||||
run: |
|
||||
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
|
||||
if [ "${{ inputs.tag }}" != "${version}" ]; then
|
||||
echo "The input tag does not match the version from pyproject.toml:" >&2
|
||||
echo "${{ inputs.tag }}" >&2
|
||||
echo "${version}" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "Releasing ${version}"
|
||||
fi
|
||||
|
||||
- name: "Build and push Docker image"
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
# Reuse the builder
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
push: ${{ inputs.tag != '' }}
|
||||
tags: ghcr.io/astral-sh/ruff:latest,ghcr.io/astral-sh/ruff:${{ inputs.tag || 'dry-run' }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
# After the release has been published, we update downstream repositories
|
||||
# This is separate because if this fails the release is still fine, we just need to do some manual workflow triggers
|
||||
update-dependents:
|
||||
@@ -524,7 +580,7 @@ jobs:
|
||||
needs: publish-release
|
||||
steps:
|
||||
- name: "Update pre-commit mirror"
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.RUFF_PRE_COMMIT_PAT }}
|
||||
script: |
|
||||
|
||||
94
CHANGELOG.md
94
CHANGELOG.md
@@ -1,5 +1,99 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Preview features
|
||||
|
||||
- Implement multiline dictionary and list hugging for preview style ([#8293](https://github.com/astral-sh/ruff/pull/8293))
|
||||
- Implement the `fix_power_op_line_length` preview style ([#8947](https://github.com/astral-sh/ruff/pull/8947))
|
||||
- Use Python version to determine typing rewrite safety ([#8919](https://github.com/astral-sh/ruff/pull/8919))
|
||||
- \[`flake8-annotations`\] Enable auto-return-type involving `Optional` and `Union` annotations ([#8885](https://github.com/astral-sh/ruff/pull/8885))
|
||||
- \[`flake8-bandit`\] Implement `django-raw-sql` (`S611`) ([#8651](https://github.com/astral-sh/ruff/pull/8651))
|
||||
- \[`flake8-bandit`\] Implement `tarfile-unsafe-members` (`S202`) ([#8829](https://github.com/astral-sh/ruff/pull/8829))
|
||||
- \[`flake8-pyi`\] Implement fix for `unnecessary-literal-union` (`PYI030`) ([#7934](https://github.com/astral-sh/ruff/pull/7934))
|
||||
- \[`flake8-simplify`\] Extend `dict-get-with-none-default` (`SIM910`) to non-literals ([#8762](https://github.com/astral-sh/ruff/pull/8762))
|
||||
- \[`pylint`\] - add `unnecessary-list-index-lookup` (`PLR1736`) + autofix ([#7999](https://github.com/astral-sh/ruff/pull/7999))
|
||||
- \[`pylint`\] - implement R0202 and R0203 with autofixes ([#8335](https://github.com/astral-sh/ruff/pull/8335))
|
||||
- \[`pylint`\] Implement `repeated-keyword` (`PLe1132`) ([#8706](https://github.com/astral-sh/ruff/pull/8706))
|
||||
- \[`pylint`\] Implement `too-many-positional` (`PLR0917`) ([#8995](https://github.com/astral-sh/ruff/pull/8995))
|
||||
- \[`pylint`\] Implement `unnecessary-dict-index-lookup` (`PLR1733`) ([#8036](https://github.com/astral-sh/ruff/pull/8036))
|
||||
- \[`refurb`\] Implement `redundant-log-base` (`FURB163`) ([#8842](https://github.com/astral-sh/ruff/pull/8842))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-boolean-trap`\] Allow booleans in `@override` methods ([#8882](https://github.com/astral-sh/ruff/pull/8882))
|
||||
- \[`flake8-bugbear`\] Avoid `B015`,`B018` for last expression in a cell ([#8815](https://github.com/astral-sh/ruff/pull/8815))
|
||||
- \[`flake8-pie`\] Allow ellipses for enum values in stub files ([#8825](https://github.com/astral-sh/ruff/pull/8825))
|
||||
- \[`flake8-pyi`\] Check PEP 695 type aliases for `snake-case-type-alias` and `t-suffixed-type-alias` ([#8966](https://github.com/astral-sh/ruff/pull/8966))
|
||||
- \[`flake8-pyi`\] Check for kwarg and vararg `NoReturn` type annotations ([#8948](https://github.com/astral-sh/ruff/pull/8948))
|
||||
- \[`flake8-simplify`\] Omit select context managers from `SIM117` ([#8801](https://github.com/astral-sh/ruff/pull/8801))
|
||||
- \[`pep8-naming`\] Allow Django model loads in `non-lowercase-variable-in-function` (`N806`) ([#8917](https://github.com/astral-sh/ruff/pull/8917))
|
||||
- \[`pycodestyle`\] Avoid `E703` for last expression in a cell ([#8821](https://github.com/astral-sh/ruff/pull/8821))
|
||||
- \[`pycodestyle`\] Update `E402` to work at cell level for notebooks ([#8872](https://github.com/astral-sh/ruff/pull/8872))
|
||||
- \[`pydocstyle`\] Avoid `D100` for Jupyter Notebooks ([#8816](https://github.com/astral-sh/ruff/pull/8816))
|
||||
- \[`pylint`\] Implement fix for `unspecified-encoding` (`PLW1514`) ([#8928](https://github.com/astral-sh/ruff/pull/8928))
|
||||
|
||||
### Formatter
|
||||
|
||||
- Avoid unstable formatting in ellipsis-only body with trailing comment ([#8984](https://github.com/astral-sh/ruff/pull/8984))
|
||||
- Inline trailing comments for type alias similar to assignments ([#8941](https://github.com/astral-sh/ruff/pull/8941))
|
||||
- Insert trailing comma when function breaks with single argument ([#8921](https://github.com/astral-sh/ruff/pull/8921))
|
||||
|
||||
### CLI
|
||||
|
||||
- Update `ruff check` and `ruff format` to default to the current directory ([#8791](https://github.com/astral-sh/ruff/pull/8791))
|
||||
- Stop at the first resolved parent configuration ([#8864](https://github.com/astral-sh/ruff/pull/8864))
|
||||
|
||||
### Configuration
|
||||
|
||||
- \[`pylint`\] Default `max-positional-args` to `max-args` ([#8998](https://github.com/astral-sh/ruff/pull/8998))
|
||||
- \[`pylint`\] Add `allow-dunder-method-names` setting for `bad-dunder-method-name` (`PLW3201`) ([#8812](https://github.com/astral-sh/ruff/pull/8812))
|
||||
- \[`isort`\] Add support for `from-first` setting ([#8663](https://github.com/astral-sh/ruff/pull/8663))
|
||||
- \[`isort`\] Add support for `length-sort` settings ([#8841](https://github.com/astral-sh/ruff/pull/8841))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Add support for `@functools.singledispatch` ([#8934](https://github.com/astral-sh/ruff/pull/8934))
|
||||
- Avoid off-by-one error in stripping noqa following multi-byte char ([#8979](https://github.com/astral-sh/ruff/pull/8979))
|
||||
- Avoid off-by-one error in with-item named expressions ([#8915](https://github.com/astral-sh/ruff/pull/8915))
|
||||
- Avoid syntax error via invalid ur string prefix ([#8971](https://github.com/astral-sh/ruff/pull/8971))
|
||||
- Avoid underflow in `get_model` matching ([#8965](https://github.com/astral-sh/ruff/pull/8965))
|
||||
- Avoid unnecessary index diagnostics when value is modified ([#8970](https://github.com/astral-sh/ruff/pull/8970))
|
||||
- Convert over-indentation rule to use number of characters ([#8983](https://github.com/astral-sh/ruff/pull/8983))
|
||||
- Detect implicit returns in auto-return-types ([#8952](https://github.com/astral-sh/ruff/pull/8952))
|
||||
- Fix start >= end error in over-indentation ([#8982](https://github.com/astral-sh/ruff/pull/8982))
|
||||
- Ignore `@overload` and `@override` methods for too-many-arguments checks ([#8954](https://github.com/astral-sh/ruff/pull/8954))
|
||||
- Lexer start of line is false only for `Mode::Expression` ([#8880](https://github.com/astral-sh/ruff/pull/8880))
|
||||
- Mark `pydantic_settings.BaseSettings` as having default copy semantics ([#8793](https://github.com/astral-sh/ruff/pull/8793))
|
||||
- Respect dictionary unpacking in `NamedTuple` assignments ([#8810](https://github.com/astral-sh/ruff/pull/8810))
|
||||
- Respect local subclasses in `flake8-type-checking` ([#8768](https://github.com/astral-sh/ruff/pull/8768))
|
||||
- Support type alias statements in simple statement positions ([#8916](https://github.com/astral-sh/ruff/pull/8916))
|
||||
- \[`flake8-annotations`\] Avoid filtering out un-representable types in return annotation ([#8881](https://github.com/astral-sh/ruff/pull/8881))
|
||||
- \[`flake8-pie`\] Retain extra ellipses in protocols and abstract methods ([#8769](https://github.com/astral-sh/ruff/pull/8769))
|
||||
- \[`flake8-pyi`\] Respect local enum subclasses in `simple-defaults` (`PYI052`) ([#8767](https://github.com/astral-sh/ruff/pull/8767))
|
||||
- \[`flake8-trio`\] Use correct range for `TRIO115` fix ([#8933](https://github.com/astral-sh/ruff/pull/8933))
|
||||
- \[`flake8-trio`\] Use full arguments range for zero-sleep-call ([#8936](https://github.com/astral-sh/ruff/pull/8936))
|
||||
- \[`isort`\] fix: mark `__main__` as first-party import ([#8805](https://github.com/astral-sh/ruff/pull/8805))
|
||||
- \[`pep8-naming`\] Avoid `N806` errors for type alias statements ([#8785](https://github.com/astral-sh/ruff/pull/8785))
|
||||
- \[`perflint`\] Avoid `PERF101` if there's an append in loop body ([#8809](https://github.com/astral-sh/ruff/pull/8809))
|
||||
- \[`pycodestyle`\] Allow space-before-colon after end-of-slice ([#8838](https://github.com/astral-sh/ruff/pull/8838))
|
||||
- \[`pydocstyle`\] Avoid non-character breaks in `over-indentation` (`D208`) ([#8866](https://github.com/astral-sh/ruff/pull/8866))
|
||||
- \[`pydocstyle`\] Ignore underlines when determining docstring logical lines ([#8929](https://github.com/astral-sh/ruff/pull/8929))
|
||||
- \[`pylint`\] Extend `self-assigning-variable` to multi-target assignments ([#8839](https://github.com/astral-sh/ruff/pull/8839))
|
||||
- \[`tryceratops`\] Avoid repeated triggers in nested `tryceratops` diagnostics ([#8772](https://github.com/astral-sh/ruff/pull/8772))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add advice for fixing RUF008 when mutability is not desired ([#8853](https://github.com/astral-sh/ruff/pull/8853))
|
||||
- Added the command to run ruff using pkgx to the installation.md ([#8955](https://github.com/astral-sh/ruff/pull/8955))
|
||||
- Document fix safety for flake8-comprehensions and some pyupgrade rules ([#8918](https://github.com/astral-sh/ruff/pull/8918))
|
||||
- Fix doc formatting for zero-sleep-call ([#8937](https://github.com/astral-sh/ruff/pull/8937))
|
||||
- Remove duplicate imports from os-stat documentation ([#8930](https://github.com/astral-sh/ruff/pull/8930))
|
||||
- Replace generated reference to MkDocs ([#8806](https://github.com/astral-sh/ruff/pull/8806))
|
||||
- Update Arch Linux package URL in installation.md ([#8802](https://github.com/astral-sh/ruff/pull/8802))
|
||||
- \[`flake8-pyi`\] Fix error in `t-suffixed-type-alias` (`PYI043`) example ([#8963](https://github.com/astral-sh/ruff/pull/8963))
|
||||
- \[`flake8-pyi`\] Improve motivation for `custom-type-var-return-type` (`PYI019`) ([#8766](https://github.com/astral-sh/ruff/pull/8766))
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Preview features
|
||||
|
||||
@@ -295,7 +295,7 @@ To preview any changes to the documentation locally:
|
||||
|
||||
```shell
|
||||
# For contributors.
|
||||
mkdocs serve -f mkdocs.generated.yml
|
||||
mkdocs serve -f mkdocs.public.yml
|
||||
|
||||
# For members of the Astral org, which has access to MkDocs Insiders via sponsorship.
|
||||
mkdocs serve -f mkdocs.insiders.yml
|
||||
|
||||
120
Cargo.lock
generated
120
Cargo.lock
generated
@@ -405,9 +405,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "2.3.1"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "918b13a0f1a32460ab3bd5debd56b5a27a7071fa5ff5dfeb3a5cf291a85b174b"
|
||||
checksum = "0eb4ab4dcb6554eb4f590fb16f99d3b102ab76f5f56554c9a5340518b32c499b"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"libc",
|
||||
@@ -416,9 +416,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat"
|
||||
version = "2.3.1"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c683c7fef2b873fbbdf4062782914c652309951244bf0bd362fe608b7d6f901c"
|
||||
checksum = "cc07a3d3f7e0c8961d0ffdee149d39b231bafdcdc3d978dc5ad790c615f55f3f"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"colored",
|
||||
@@ -444,9 +444,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "configparser"
|
||||
version = "3.0.2"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5458d9d1a587efaf5091602c59d299696a3877a439c8f6d461a2d3cce11df87a"
|
||||
checksum = "e0e56e414a2a52ab2a104f85cd40933c2fbc278b83637facf646ecf451b49237"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
@@ -808,7 +808,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -848,18 +848,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs-err"
|
||||
version = "2.10.0"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5fd9bcbe8b1087cbd395b51498c01bc997cef73e778a80b77a811af5e2d29f"
|
||||
checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
@@ -903,15 +903,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.13"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
|
||||
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
"regex-automata 0.4.3",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -987,9 +987,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
@@ -1170,9 +1170,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.64"
|
||||
version = "0.3.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
|
||||
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -1622,9 +1622,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
@@ -1793,9 +1793,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2062,7 +2062,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.2",
|
||||
"anyhow",
|
||||
@@ -2198,7 +2198,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2334,6 +2334,7 @@ dependencies = [
|
||||
"itertools 0.11.0",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"ruff_cache",
|
||||
"ruff_formatter",
|
||||
"ruff_macros",
|
||||
@@ -2449,7 +2450,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2617,9 +2618,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.15"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
|
||||
checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
@@ -2629,9 +2630,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.15"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
|
||||
checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3194,20 +3195,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
@@ -3333,9 +3334,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.8.0"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3"
|
||||
checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
@@ -3349,9 +3350,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
@@ -3367,9 +3368,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.5.0"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
|
||||
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
@@ -3379,9 +3380,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid-macro-internal"
|
||||
version = "1.5.0"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d8c6bba9b149ee82950daefc9623b32bb1dacbfb1890e352f6b887bd582adaf"
|
||||
checksum = "f49e7f3f3db8040a100710a11932239fd30697115e2ba4107080d8252939845e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3460,9 +3461,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.87"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
|
||||
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
@@ -3470,9 +3471,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.87"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
|
||||
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@@ -3485,9 +3486,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.37"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
|
||||
checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -3497,9 +3498,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.87"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
|
||||
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -3507,9 +3508,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.87"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3520,15 +3521,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.87"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.37"
|
||||
version = "0.3.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671"
|
||||
checksum = "c6433b7c56db97397842c46b67e11873eda263170afeb3a2dc74a7cb370fee0d"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
@@ -3540,12 +3541,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.37"
|
||||
version = "0.3.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575"
|
||||
checksum = "493fcbab756bb764fa37e6bee8cec2dd709eb4273d06d0c282a5e74275ded735"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -19,7 +19,7 @@ clap = { version = "4.4.7", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
filetime = { version = "0.2.20" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.10" }
|
||||
globset = { version = "0.4.14" }
|
||||
ignore = { version = "0.4.20" }
|
||||
insta = { version = "1.34.0", feature = ["filters", "glob"] }
|
||||
is-macro = { version = "0.3.0" }
|
||||
@@ -29,11 +29,11 @@ log = { version = "0.4.17" }
|
||||
memchr = { version = "2.6.4" }
|
||||
once_cell = { version = "1.17.1" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
proc-macro2 = { version = "1.0.69" }
|
||||
proc-macro2 = { version = "1.0.70" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.15" }
|
||||
schemars = { version = "0.8.16" }
|
||||
serde = { version = "1.0.190", features = ["derive"] }
|
||||
serde_json = { version = "1.0.108" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
@@ -48,11 +48,11 @@ thiserror = { version = "1.0.50" }
|
||||
toml = { version = "0.7.8" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-indicatif = { version = "0.3.4" }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
unicode_names2 = { version = "1.2.0" }
|
||||
unicode-width = { version = "0.1.11" }
|
||||
uuid = { version = "1.5.0", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
|
||||
38
Dockerfile
Normal file
38
Dockerfile
Normal file
@@ -0,0 +1,38 @@
|
||||
FROM --platform=$BUILDPLATFORM ubuntu as build
|
||||
ENV HOME="/root"
|
||||
WORKDIR $HOME
|
||||
|
||||
RUN apt update && apt install -y build-essential curl python3-venv
|
||||
|
||||
# Setup zig as cross compiling linker
|
||||
RUN python3 -m venv $HOME/.venv
|
||||
RUN .venv/bin/pip install cargo-zigbuild
|
||||
ENV PATH="$HOME/.venv/bin:$PATH"
|
||||
|
||||
# Install rust
|
||||
ARG TARGETPLATFORM
|
||||
RUN case "$TARGETPLATFORM" in \
|
||||
"linux/arm64") echo "aarch64-unknown-linux-musl" > rust_target.txt ;; \
|
||||
"linux/amd64") echo "x86_64-unknown-linux-musl" > rust_target.txt ;; \
|
||||
*) exit 1 ;; \
|
||||
esac
|
||||
# Update rustup whenever we bump the rust version
|
||||
COPY rust-toolchain.toml rust-toolchain.toml
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --target $(cat rust_target.txt) --profile minimal --default-toolchain none
|
||||
ENV PATH="$HOME/.cargo/bin:$PATH"
|
||||
# Installs the correct toolchain version from rust-toolchain.toml and then the musl target
|
||||
RUN rustup target add $(cat rust_target.txt)
|
||||
|
||||
# Build
|
||||
COPY crates crates
|
||||
COPY Cargo.toml Cargo.toml
|
||||
COPY Cargo.lock Cargo.lock
|
||||
RUN cargo zigbuild --bin ruff --target $(cat rust_target.txt) --release
|
||||
RUN cp target/$(cat rust_target.txt)/release/ruff /ruff
|
||||
# TODO: Optimize binary size, with a version that also works when cross compiling
|
||||
# RUN strip --strip-all /ruff
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /ruff /ruff
|
||||
WORKDIR /io
|
||||
ENTRYPOINT ["/ruff"]
|
||||
@@ -150,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.1.6
|
||||
rev: v0.1.7
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
@@ -19,7 +19,7 @@ ruff_workspace = { path = "../ruff_workspace" }
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
colored = { workspace = true }
|
||||
configparser = { version = "3.0.2" }
|
||||
configparser = { version = "3.0.3" }
|
||||
itertools = { workspace = true }
|
||||
log = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
|
||||
@@ -34,10 +34,10 @@ harness = false
|
||||
once_cell.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
url = "2.3.1"
|
||||
ureq = "2.8.0"
|
||||
url = "2.5.0"
|
||||
ureq = "2.9.1"
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
codspeed-criterion-compat = { version="2.3.1", default-features = false, optional = true}
|
||||
codspeed-criterion-compat = { version="2.3.3", default-features = false, optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_linter.path = "../ruff_linter"
|
||||
|
||||
@@ -4,7 +4,7 @@ use ruff_benchmark::criterion::{
|
||||
criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
|
||||
};
|
||||
use ruff_benchmark::{TestCase, TestFile, TestFileDownloadError};
|
||||
use ruff_python_formatter::{format_module_ast, PyFormatOptions};
|
||||
use ruff_python_formatter::{format_module_ast, PreviewMode, PyFormatOptions};
|
||||
use ruff_python_index::CommentRangesBuilder;
|
||||
use ruff_python_parser::lexer::lex;
|
||||
use ruff_python_parser::{parse_tokens, Mode};
|
||||
@@ -69,7 +69,8 @@ fn benchmark_formatter(criterion: &mut Criterion) {
|
||||
.expect("Input to be a valid python program");
|
||||
|
||||
b.iter(|| {
|
||||
let options = PyFormatOptions::from_extension(Path::new(case.name()));
|
||||
let options = PyFormatOptions::from_extension(Path::new(case.name()))
|
||||
.with_preview(PreviewMode::Enabled);
|
||||
let formatted =
|
||||
format_module_ast(&module, &comment_ranges, case.code(), options)
|
||||
.expect("Formatting to succeed");
|
||||
|
||||
@@ -3,7 +3,9 @@ use ruff_benchmark::criterion::{
|
||||
};
|
||||
use ruff_benchmark::{TestCase, TestFile, TestFileDownloadError};
|
||||
use ruff_linter::linter::lint_only;
|
||||
use ruff_linter::rule_selector::PreviewOptions;
|
||||
use ruff_linter::settings::rule_table::RuleTable;
|
||||
use ruff_linter::settings::types::PreviewMode;
|
||||
use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_linter::{registry::Rule, RuleSelector};
|
||||
@@ -78,12 +80,21 @@ fn benchmark_default_rules(criterion: &mut Criterion) {
|
||||
benchmark_linter(group, &LinterSettings::default());
|
||||
}
|
||||
|
||||
fn benchmark_all_rules(criterion: &mut Criterion) {
|
||||
let mut rules: RuleTable = RuleSelector::All.all_rules().collect();
|
||||
|
||||
// Disable IO based rules because it is a source of flakiness
|
||||
/// Disables IO based rules because they are a source of flakiness
|
||||
fn disable_io_rules(rules: &mut RuleTable) {
|
||||
rules.disable(Rule::ShebangMissingExecutableFile);
|
||||
rules.disable(Rule::ShebangNotExecutable);
|
||||
}
|
||||
|
||||
fn benchmark_all_rules(criterion: &mut Criterion) {
|
||||
let mut rules: RuleTable = RuleSelector::All
|
||||
.rules(&PreviewOptions {
|
||||
mode: PreviewMode::Disabled,
|
||||
require_explicit: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
disable_io_rules(&mut rules);
|
||||
|
||||
let settings = LinterSettings {
|
||||
rules,
|
||||
@@ -94,6 +105,22 @@ fn benchmark_all_rules(criterion: &mut Criterion) {
|
||||
benchmark_linter(group, &settings);
|
||||
}
|
||||
|
||||
fn benchmark_preview_rules(criterion: &mut Criterion) {
|
||||
let mut rules: RuleTable = RuleSelector::All.all_rules().collect();
|
||||
|
||||
disable_io_rules(&mut rules);
|
||||
|
||||
let settings = LinterSettings {
|
||||
rules,
|
||||
preview: PreviewMode::Enabled,
|
||||
..LinterSettings::default()
|
||||
};
|
||||
|
||||
let group = criterion.benchmark_group("linter/all-with-preview-rules");
|
||||
benchmark_linter(group, &settings);
|
||||
}
|
||||
|
||||
criterion_group!(default_rules, benchmark_default_rules);
|
||||
criterion_group!(all_rules, benchmark_all_rules);
|
||||
criterion_main!(default_rules, all_rules);
|
||||
criterion_group!(preview_rules, benchmark_preview_rules);
|
||||
criterion_main!(default_rules, all_rules, preview_rules);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -69,7 +69,7 @@ insta = { workspace = true, features = ["filters", "json"] }
|
||||
insta-cmd = { version = "0.4.0" }
|
||||
tempfile = "3.8.1"
|
||||
test-case = { workspace = true }
|
||||
ureq = { version = "2.8.0", features = [] }
|
||||
ureq = { version = "2.9.1", features = [] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = "0.1.39"
|
||||
|
||||
0
crates/ruff_cli/resources/test/fixtures/include-test/a.py
vendored
Normal file
0
crates/ruff_cli/resources/test/fixtures/include-test/a.py
vendored
Normal file
0
crates/ruff_cli/resources/test/fixtures/include-test/b.py
vendored
Normal file
0
crates/ruff_cli/resources/test/fixtures/include-test/b.py
vendored
Normal file
0
crates/ruff_cli/resources/test/fixtures/include-test/nested-project/e.py
vendored
Normal file
0
crates/ruff_cli/resources/test/fixtures/include-test/nested-project/e.py
vendored
Normal file
2
crates/ruff_cli/resources/test/fixtures/include-test/nested-project/pyproject.toml
vendored
Normal file
2
crates/ruff_cli/resources/test/fixtures/include-test/nested-project/pyproject.toml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[tool.ruff]
|
||||
select = []
|
||||
2
crates/ruff_cli/resources/test/fixtures/include-test/pyproject.toml
vendored
Normal file
2
crates/ruff_cli/resources/test/fixtures/include-test/pyproject.toml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[tool.ruff]
|
||||
include = ["a.py", "subdirectory/c.py"]
|
||||
0
crates/ruff_cli/resources/test/fixtures/include-test/subdirectory/c.py
vendored
Normal file
0
crates/ruff_cli/resources/test/fixtures/include-test/subdirectory/c.py
vendored
Normal file
0
crates/ruff_cli/resources/test/fixtures/include-test/subdirectory/d.py
vendored
Normal file
0
crates/ruff_cli/resources/test/fixtures/include-test/subdirectory/d.py
vendored
Normal file
@@ -88,6 +88,7 @@ pub enum Command {
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct CheckCommand {
|
||||
/// List of files or directories to check.
|
||||
#[clap(help = "List of files or directories to check [default: .]")]
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Apply fixes to resolve lint violations.
|
||||
/// Use `--no-fix` to disable or `--unsafe-fixes` to include unsafe fixes.
|
||||
@@ -363,6 +364,7 @@ pub struct CheckCommand {
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct FormatCommand {
|
||||
/// List of files or directories to format.
|
||||
#[clap(help = "List of files or directories to format [default: .]")]
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Avoid writing any formatted files back; instead, exit with a non-zero status code if any
|
||||
/// files would have been modified, and zero otherwise.
|
||||
|
||||
@@ -34,7 +34,7 @@ use crate::args::{CliOverrides, FormatArguments};
|
||||
use crate::cache::{Cache, FileCacheKey, PackageCacheMap, PackageCaches};
|
||||
use crate::panic::{catch_unwind, PanicError};
|
||||
use crate::resolve::resolve;
|
||||
use crate::ExitStatus;
|
||||
use crate::{resolve_default_files, ExitStatus};
|
||||
|
||||
#[derive(Debug, Copy, Clone, is_macro::Is)]
|
||||
pub(crate) enum FormatMode {
|
||||
@@ -60,7 +60,7 @@ impl FormatMode {
|
||||
|
||||
/// Format a set of files, and return the exit status.
|
||||
pub(crate) fn format(
|
||||
cli: &FormatArguments,
|
||||
cli: FormatArguments,
|
||||
overrides: &CliOverrides,
|
||||
log_level: LogLevel,
|
||||
) -> Result<ExitStatus> {
|
||||
@@ -70,8 +70,9 @@ pub(crate) fn format(
|
||||
overrides,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
let mode = FormatMode::from_cli(cli);
|
||||
let (paths, resolver) = python_files_in_path(&cli.files, &pyproject_config, overrides)?;
|
||||
let mode = FormatMode::from_cli(&cli);
|
||||
let files = resolve_default_files(cli.files, false);
|
||||
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, overrides)?;
|
||||
|
||||
if paths.is_empty() {
|
||||
warn_user_once!("No Python files found under the given path(s)");
|
||||
|
||||
@@ -101,6 +101,19 @@ fn is_stdin(files: &[PathBuf], stdin_filename: Option<&Path>) -> bool {
|
||||
file == Path::new("-")
|
||||
}
|
||||
|
||||
/// Returns the default set of files if none are provided, otherwise returns `None`.
|
||||
fn resolve_default_files(files: Vec<PathBuf>, is_stdin: bool) -> Vec<PathBuf> {
|
||||
if files.is_empty() {
|
||||
if is_stdin {
|
||||
vec![Path::new("-").to_path_buf()]
|
||||
} else {
|
||||
vec![Path::new(".").to_path_buf()]
|
||||
}
|
||||
} else {
|
||||
files
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the actual value of the `format` desired from either `output_format`
|
||||
/// or `format`, and warn the user if they're using the deprecated form.
|
||||
fn resolve_help_output_format(output_format: HelpFormat, format: Option<HelpFormat>) -> HelpFormat {
|
||||
@@ -196,7 +209,7 @@ fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
|
||||
commands::format_stdin::format_stdin(&cli, &overrides)
|
||||
} else {
|
||||
commands::format::format(&cli, &overrides, log_level)
|
||||
commands::format::format(cli, &overrides, log_level)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,17 +235,15 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
};
|
||||
let stderr_writer = Box::new(BufWriter::new(io::stderr()));
|
||||
|
||||
let is_stdin = is_stdin(&cli.files, cli.stdin_filename.as_deref());
|
||||
let files = resolve_default_files(cli.files, is_stdin);
|
||||
|
||||
if cli.show_settings {
|
||||
commands::show_settings::show_settings(
|
||||
&cli.files,
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
&mut writer,
|
||||
)?;
|
||||
commands::show_settings::show_settings(&files, &pyproject_config, &overrides, &mut writer)?;
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
if cli.show_files {
|
||||
commands::show_files::show_files(&cli.files, &pyproject_config, &overrides, &mut writer)?;
|
||||
commands::show_files::show_files(&files, &pyproject_config, &overrides, &mut writer)?;
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
@@ -295,8 +306,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
if !fix_mode.is_generate() {
|
||||
warn_user!("--fix is incompatible with --add-noqa.");
|
||||
}
|
||||
let modifications =
|
||||
commands::add_noqa::add_noqa(&cli.files, &pyproject_config, &overrides)?;
|
||||
let modifications = commands::add_noqa::add_noqa(&files, &pyproject_config, &overrides)?;
|
||||
if modifications > 0 && log_level >= LogLevel::Default {
|
||||
let s = if modifications == 1 { "" } else { "s" };
|
||||
#[allow(clippy::print_stderr)]
|
||||
@@ -323,7 +333,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
// Configure the file watcher.
|
||||
let (tx, rx) = channel();
|
||||
let mut watcher = recommended_watcher(tx)?;
|
||||
for file in &cli.files {
|
||||
for file in &files {
|
||||
watcher.watch(file, RecursiveMode::Recursive)?;
|
||||
}
|
||||
if let Some(file) = pyproject_config.path.as_ref() {
|
||||
@@ -335,7 +345,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
printer.write_to_user("Starting linter in watch mode...\n");
|
||||
|
||||
let messages = commands::check::check(
|
||||
&cli.files,
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
cache.into(),
|
||||
@@ -368,7 +378,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
printer.write_to_user("File change detected...\n");
|
||||
|
||||
let messages = commands::check::check(
|
||||
&cli.files,
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
cache.into(),
|
||||
@@ -382,8 +392,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let is_stdin = is_stdin(&cli.files, cli.stdin_filename.as_deref());
|
||||
|
||||
// Generate lint violations.
|
||||
let diagnostics = if is_stdin {
|
||||
commands::check_stdin::check_stdin(
|
||||
@@ -395,7 +403,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
)?
|
||||
} else {
|
||||
commands::check::check(
|
||||
&cli.files,
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
cache.into(),
|
||||
|
||||
@@ -43,6 +43,53 @@ if condition:
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_files() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
fs::write(
|
||||
tempdir.path().join("foo.py"),
|
||||
r#"
|
||||
foo = "needs formatting"
|
||||
"#,
|
||||
)?;
|
||||
fs::write(
|
||||
tempdir.path().join("bar.py"),
|
||||
r#"
|
||||
bar = "needs formatting"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--no-cache", "--check"]).current_dir(tempdir.path()), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Would reformat: bar.py
|
||||
Would reformat: foo.py
|
||||
2 files would be reformatted
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_warn_stdin_filename_with_files() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "foo.py"])
|
||||
.arg("foo.py")
|
||||
.pass_stdin("foo = 1"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
foo = 1
|
||||
|
||||
----- stderr -----
|
||||
warning: Ignoring file foo.py in favor of standard input.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -396,3 +396,43 @@ if __name__ == "__main__":
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for [#8858](https://github.com/astral-sh/ruff/issues/8858)
|
||||
#[test]
|
||||
fn parent_configuration_override() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let root_ruff = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
root_ruff,
|
||||
r#"
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let sub_dir = tempdir.path().join("subdirectory");
|
||||
fs::create_dir(&sub_dir)?;
|
||||
|
||||
let subdirectory_ruff = sub_dir.join("ruff.toml");
|
||||
fs::write(
|
||||
subdirectory_ruff,
|
||||
r#"
|
||||
[lint]
|
||||
ignore = ["D203", "D212"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(sub_dir)
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: No Python files found under the given path(s)
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
101
crates/ruff_cli/tests/resolve_files.rs
Normal file
101
crates/ruff_cli/tests/resolve_files.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::str;
|
||||
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const TEST_FILTERS: &[(&str, &str)] = &[(".*/resources/test/fixtures/", "[BASEPATH]/")];
|
||||
#[cfg(target_os = "windows")]
|
||||
const TEST_FILTERS: &[(&str, &str)] = &[
|
||||
(r".*\\resources\\test\\fixtures\\", "[BASEPATH]\\"),
|
||||
(r"\\", "/"),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn check_project_include_defaults() {
|
||||
// Defaults to checking the current working directory
|
||||
//
|
||||
// The test directory includes:
|
||||
// - A pyproject.toml which specifies an include
|
||||
// - A nested pyproject.toml which has a Ruff section
|
||||
//
|
||||
// The nested project should all be checked instead of respecting the parent includes
|
||||
|
||||
insta::with_settings!({
|
||||
filters => TEST_FILTERS.to_vec()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--show-files"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[BASEPATH]/include-test/a.py
|
||||
[BASEPATH]/include-test/nested-project/e.py
|
||||
[BASEPATH]/include-test/nested-project/pyproject.toml
|
||||
[BASEPATH]/include-test/subdirectory/c.py
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_project_respects_direct_paths() {
|
||||
// Given a direct path not included in the project `includes`, it should be checked
|
||||
|
||||
insta::with_settings!({
|
||||
filters => TEST_FILTERS.to_vec()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--show-files", "b.py"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[BASEPATH]/include-test/b.py
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_project_respects_subdirectory_includes() {
|
||||
// Given a direct path to a subdirectory, the include should be respected
|
||||
|
||||
insta::with_settings!({
|
||||
filters => TEST_FILTERS.to_vec()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--show-files", "subdirectory"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[BASEPATH]/include-test/subdirectory/c.py
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_project_from_project_subdirectory_respects_includes() {
|
||||
// Run from a project subdirectory, the include specified in the parent directory should be respected
|
||||
|
||||
insta::with_settings!({
|
||||
filters => TEST_FILTERS.to_vec()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--show-files"]).current_dir(Path::new("./resources/test/fixtures/include-test/subdirectory")), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[BASEPATH]/include-test/subdirectory/c.py
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
}
|
||||
@@ -1,30 +1,34 @@
|
||||
//! Print the AST for a given Python file.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff_python_parser::{parse, Mode};
|
||||
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_parser::{parse, AsMode};
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
/// Python file for which to generate the AST.
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
/// Run in Jupyter mode i.e., allow line magics.
|
||||
#[arg(long)]
|
||||
jupyter: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
let contents = fs::read_to_string(&args.file)?;
|
||||
let mode = if args.jupyter {
|
||||
Mode::Ipython
|
||||
} else {
|
||||
Mode::Module
|
||||
};
|
||||
let python_ast = parse(&contents, mode, &args.file.to_string_lossy())?;
|
||||
let source_type = PySourceType::from(&args.file);
|
||||
let source_kind = SourceKind::from_path(&args.file, source_type)?.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Could not determine source kind for file: {}",
|
||||
args.file.display()
|
||||
)
|
||||
})?;
|
||||
let python_ast = parse(
|
||||
source_kind.source_code(),
|
||||
source_type.as_mode(),
|
||||
&args.file.to_string_lossy(),
|
||||
)?;
|
||||
println!("{python_ast:#?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
//! Print the token stream for a given Python file.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff_python_parser::{lexer, Mode};
|
||||
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_parser::{lexer, AsMode};
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
/// Python file for which to generate the AST.
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
/// Run in Jupyter mode i.e., allow line magics (`%`, `!`, `?`, `/`, `,`, `;`).
|
||||
#[arg(long)]
|
||||
jupyter: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
let contents = fs::read_to_string(&args.file)?;
|
||||
let mode = if args.jupyter {
|
||||
Mode::Ipython
|
||||
} else {
|
||||
Mode::Module
|
||||
};
|
||||
for (tok, range) in lexer::lex(&contents, mode).flatten() {
|
||||
let source_type = PySourceType::from(&args.file);
|
||||
let source_kind = SourceKind::from_path(&args.file, source_type)?.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Could not determine source kind for file: {}",
|
||||
args.file.display()
|
||||
)
|
||||
})?;
|
||||
for (tok, range) in lexer::lex(source_kind.source_code(), source_type.as_mode()).flatten() {
|
||||
println!(
|
||||
"{start:#?} {tok:#?} {end:#?}",
|
||||
start = range.start(),
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
use super::{Buffer, Format, Formatter};
|
||||
use crate::FormatResult;
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Mono-morphed type to format an object. Used by the [`crate::format`!], [`crate::format_args`!], and
|
||||
/// [`crate::write`!] macros.
|
||||
///
|
||||
/// This struct is similar to a dynamic dispatch (using `dyn Format`) because it stores a pointer to the value.
|
||||
/// However, it doesn't store the pointer to `dyn Format`'s vtable, instead it statically resolves the function
|
||||
/// pointer of `Format::format` and stores it in `formatter`.
|
||||
/// A convenience wrapper for representing a formattable argument.
|
||||
pub struct Argument<'fmt, Context> {
|
||||
/// The value to format stored as a raw pointer where `lifetime` stores the value's lifetime.
|
||||
value: *const c_void,
|
||||
|
||||
/// Stores the lifetime of the value. To get the most out of our dear borrow checker.
|
||||
lifetime: PhantomData<&'fmt ()>,
|
||||
|
||||
/// The function pointer to `value`'s `Format::format` method
|
||||
formatter: fn(*const c_void, &mut Formatter<'_, Context>) -> FormatResult<()>,
|
||||
value: &'fmt dyn Format<Context>,
|
||||
}
|
||||
|
||||
impl<Context> Clone for Argument<'_, Context> {
|
||||
@@ -28,32 +14,19 @@ impl<Context> Clone for Argument<'_, Context> {
|
||||
impl<Context> Copy for Argument<'_, Context> {}
|
||||
|
||||
impl<'fmt, Context> Argument<'fmt, Context> {
|
||||
/// Called by the [ruff_formatter::format_args] macro. Creates a mono-morphed value for formatting
|
||||
/// an object.
|
||||
/// Called by the [ruff_formatter::format_args] macro.
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn new<F: Format<Context>>(value: &'fmt F) -> Self {
|
||||
#[inline]
|
||||
fn formatter<F: Format<Context>, Context>(
|
||||
ptr: *const c_void,
|
||||
fmt: &mut Formatter<Context>,
|
||||
) -> FormatResult<()> {
|
||||
// SAFETY: Safe because the 'fmt lifetime is captured by the 'lifetime' field.
|
||||
#[allow(unsafe_code)]
|
||||
F::fmt(unsafe { &*ptr.cast::<F>() }, fmt)
|
||||
}
|
||||
|
||||
Self {
|
||||
value: (value as *const F).cast::<std::ffi::c_void>(),
|
||||
lifetime: PhantomData,
|
||||
formatter: formatter::<F, Context>,
|
||||
}
|
||||
Self { value }
|
||||
}
|
||||
|
||||
/// Formats the value stored by this argument using the given formatter.
|
||||
#[inline]
|
||||
// Seems to only be triggered on wasm32 and looks like a false positive?
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub(super) fn format(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
||||
(self.formatter)(self.value, f)
|
||||
self.value.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2555,17 +2555,17 @@ pub struct BestFitting<'a, Context> {
|
||||
}
|
||||
|
||||
impl<'a, Context> BestFitting<'a, Context> {
|
||||
/// Creates a new best fitting IR with the given variants. The method itself isn't unsafe
|
||||
/// but it is to discourage people from using it because the printer will panic if
|
||||
/// the slice doesn't contain at least the least and most expanded variants.
|
||||
/// Creates a new best fitting IR with the given variants.
|
||||
///
|
||||
/// Callers are required to ensure that the number of variants given
|
||||
/// is at least 2.
|
||||
///
|
||||
/// You're looking for a way to create a `BestFitting` object, use the `best_fitting![least_expanded, most_expanded]` macro.
|
||||
///
|
||||
/// ## Safety
|
||||
|
||||
/// The slice must contain at least two variants.
|
||||
#[allow(unsafe_code)]
|
||||
pub unsafe fn from_arguments_unchecked(variants: Arguments<'a, Context>) -> Self {
|
||||
/// # Panics
|
||||
///
|
||||
/// When the slice contains less than two variants.
|
||||
pub fn from_arguments_unchecked(variants: Arguments<'a, Context>) -> Self {
|
||||
assert!(
|
||||
variants.0.len() >= 2,
|
||||
"Requires at least the least expanded and most expanded variants"
|
||||
@@ -2696,14 +2696,12 @@ impl<Context> Format<Context> for BestFitting<'_, Context> {
|
||||
buffer.write_element(FormatElement::Tag(EndBestFittingEntry));
|
||||
}
|
||||
|
||||
// SAFETY: The constructor guarantees that there are always at least two variants. It's, therefore,
|
||||
// safe to call into the unsafe `from_vec_unchecked` function
|
||||
#[allow(unsafe_code)]
|
||||
let element = unsafe {
|
||||
FormatElement::BestFitting {
|
||||
variants: BestFittingVariants::from_vec_unchecked(buffer.into_vec()),
|
||||
mode: self.mode,
|
||||
}
|
||||
// OK because the constructor guarantees that there are always at
|
||||
// least two variants.
|
||||
let variants = BestFittingVariants::from_vec_unchecked(buffer.into_vec());
|
||||
let element = FormatElement::BestFitting {
|
||||
variants,
|
||||
mode: self.mode,
|
||||
};
|
||||
|
||||
f.write_element(element);
|
||||
|
||||
@@ -332,17 +332,14 @@ pub enum BestFittingMode {
|
||||
pub struct BestFittingVariants(Box<[FormatElement]>);
|
||||
|
||||
impl BestFittingVariants {
|
||||
/// Creates a new best fitting IR with the given variants. The method itself isn't unsafe
|
||||
/// but it is to discourage people from using it because the printer will panic if
|
||||
/// the slice doesn't contain at least the least and most expanded variants.
|
||||
/// Creates a new best fitting IR with the given variants.
|
||||
///
|
||||
/// Callers are required to ensure that the number of variants given
|
||||
/// is at least 2 when using `most_expanded` or `most_flag`.
|
||||
///
|
||||
/// You're looking for a way to create a `BestFitting` object, use the `best_fitting![least_expanded, most_expanded]` macro.
|
||||
///
|
||||
/// ## Safety
|
||||
/// The slice must contain at least two variants.
|
||||
#[doc(hidden)]
|
||||
#[allow(unsafe_code)]
|
||||
pub unsafe fn from_vec_unchecked(variants: Vec<FormatElement>) -> Self {
|
||||
pub fn from_vec_unchecked(variants: Vec<FormatElement>) -> Self {
|
||||
debug_assert!(
|
||||
variants
|
||||
.iter()
|
||||
@@ -351,12 +348,23 @@ impl BestFittingVariants {
|
||||
>= 2,
|
||||
"Requires at least the least expanded and most expanded variants"
|
||||
);
|
||||
|
||||
Self(variants.into_boxed_slice())
|
||||
}
|
||||
|
||||
/// Returns the most expanded variant
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// When the number of variants is less than two.
|
||||
pub fn most_expanded(&self) -> &[FormatElement] {
|
||||
assert!(
|
||||
self.as_slice()
|
||||
.iter()
|
||||
.filter(|element| matches!(element, FormatElement::Tag(Tag::StartBestFittingEntry)))
|
||||
.count()
|
||||
>= 2,
|
||||
"Requires at least the least expanded and most expanded variants"
|
||||
);
|
||||
self.into_iter().last().unwrap()
|
||||
}
|
||||
|
||||
@@ -365,7 +373,19 @@ impl BestFittingVariants {
|
||||
}
|
||||
|
||||
/// Returns the least expanded variant
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// When the number of variants is less than two.
|
||||
pub fn most_flat(&self) -> &[FormatElement] {
|
||||
assert!(
|
||||
self.as_slice()
|
||||
.iter()
|
||||
.filter(|element| matches!(element, FormatElement::Tag(Tag::StartBestFittingEntry)))
|
||||
.count()
|
||||
>= 2,
|
||||
"Requires at least the least expanded and most expanded variants"
|
||||
);
|
||||
self.into_iter().next().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,10 +329,8 @@ macro_rules! format {
|
||||
#[macro_export]
|
||||
macro_rules! best_fitting {
|
||||
($least_expanded:expr, $($tail:expr),+ $(,)?) => {{
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
$crate::BestFitting::from_arguments_unchecked($crate::format_args!($least_expanded, $($tail),+))
|
||||
}
|
||||
// OK because the macro syntax requires at least two variants.
|
||||
$crate::BestFitting::from_arguments_unchecked($crate::format_args!($least_expanded, $($tail),+))
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -39,3 +39,18 @@ def func():
|
||||
for i in range(1110):
|
||||
if True:
|
||||
break
|
||||
|
||||
# TODO(charlie): The `pass` here does not get properly redirected to the top of the
|
||||
# loop, unlike below.
|
||||
def func():
|
||||
for i in range(5):
|
||||
pass
|
||||
else:
|
||||
return 1
|
||||
|
||||
def func():
|
||||
for i in range(5):
|
||||
pass
|
||||
else:
|
||||
return 1
|
||||
x = 1
|
||||
|
||||
@@ -129,3 +129,11 @@ def func():
|
||||
print("Grass is green")
|
||||
case Color.BLUE:
|
||||
print("I'm feeling the blues :(")
|
||||
|
||||
|
||||
def func(point):
|
||||
match point:
|
||||
case (0, 0):
|
||||
print("Origin")
|
||||
case foo:
|
||||
raise ValueError("oops")
|
||||
|
||||
@@ -30,3 +30,120 @@ def func(x: int):
|
||||
|
||||
def func(x: int):
|
||||
return 1 + 2.5 if x > 0 else 1.5 or "str"
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if not x:
|
||||
return None
|
||||
return {"foo": 1}
|
||||
|
||||
|
||||
def func(x: int):
|
||||
return {"foo": 1}
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if not x:
|
||||
return 1
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if not x:
|
||||
return 1
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if not x:
|
||||
return 1
|
||||
elif x > 5:
|
||||
return "str"
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if x:
|
||||
return 1
|
||||
|
||||
|
||||
def func():
|
||||
x = 1
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if x > 0:
|
||||
return 1
|
||||
|
||||
|
||||
def func(x: int):
|
||||
match x:
|
||||
case [1, 2, 3]:
|
||||
return 1
|
||||
case 4 as y:
|
||||
return "foo"
|
||||
|
||||
|
||||
def func(x: int):
|
||||
for i in range(5):
|
||||
if i > 0:
|
||||
return 1
|
||||
|
||||
|
||||
def func(x: int):
|
||||
for i in range(5):
|
||||
if i > 0:
|
||||
return 1
|
||||
else:
|
||||
return 4
|
||||
|
||||
|
||||
def func(x: int):
|
||||
for i in range(5):
|
||||
if i > 0:
|
||||
break
|
||||
else:
|
||||
return 4
|
||||
|
||||
|
||||
def func(x: int):
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
return 1
|
||||
|
||||
|
||||
def func(x: int):
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
return 1
|
||||
finally:
|
||||
return 2
|
||||
|
||||
|
||||
def func(x: int):
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
return 1
|
||||
else:
|
||||
return 2
|
||||
|
||||
|
||||
def func(x: int):
|
||||
try:
|
||||
return 1
|
||||
except:
|
||||
return 2
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
def func(x: int):
|
||||
while x > 0:
|
||||
break
|
||||
return 1
|
||||
|
||||
65
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S202.py
vendored
Normal file
65
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S202.py
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
|
||||
|
||||
def unsafe_archive_handler(filename):
|
||||
tar = tarfile.open(filename)
|
||||
tar.extractall(path=tempfile.mkdtemp())
|
||||
tar.close()
|
||||
|
||||
|
||||
def managed_members_archive_handler(filename):
|
||||
tar = tarfile.open(filename)
|
||||
tar.extractall(path=tempfile.mkdtemp(), members=members_filter(tar))
|
||||
tar.close()
|
||||
|
||||
|
||||
def list_members_archive_handler(filename):
|
||||
tar = tarfile.open(filename)
|
||||
tar.extractall(path=tempfile.mkdtemp(), members=[])
|
||||
tar.close()
|
||||
|
||||
|
||||
def provided_members_archive_handler(filename):
|
||||
tar = tarfile.open(filename)
|
||||
tarfile.extractall(path=tempfile.mkdtemp(), members=tar)
|
||||
tar.close()
|
||||
|
||||
|
||||
def filter_data(filename):
|
||||
tar = tarfile.open(filename)
|
||||
tarfile.extractall(path=tempfile.mkdtemp(), filter="data")
|
||||
tar.close()
|
||||
|
||||
|
||||
def filter_fully_trusted(filename):
|
||||
tar = tarfile.open(filename)
|
||||
tarfile.extractall(path=tempfile.mkdtemp(), filter="fully_trusted")
|
||||
tar.close()
|
||||
|
||||
|
||||
def filter_tar(filename):
|
||||
tar = tarfile.open(filename)
|
||||
tarfile.extractall(path=tempfile.mkdtemp(), filter="tar")
|
||||
tar.close()
|
||||
|
||||
|
||||
def members_filter(tarfile):
|
||||
result = []
|
||||
for member in tarfile.getmembers():
|
||||
if '../' in member.name:
|
||||
print('Member name container directory traversal sequence')
|
||||
continue
|
||||
elif (member.issym() or member.islnk()) and ('../' in member.linkname):
|
||||
print('Symlink to external resource')
|
||||
continue
|
||||
result.append(member)
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
filename = sys.argv[1]
|
||||
unsafe_archive_handler(filename)
|
||||
managed_members_archive_handler(filename)
|
||||
13
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S611.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S611.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
from django.db.models.expressions import RawSQL
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
User.objects.annotate(val=RawSQL('secure', []))
|
||||
User.objects.annotate(val=RawSQL('%secure' % 'nos', []))
|
||||
User.objects.annotate(val=RawSQL('{}secure'.format('no'), []))
|
||||
raw = '"username") AS "val" FROM "auth_user" WHERE "username"="admin" --'
|
||||
User.objects.annotate(val=RawSQL(raw, []))
|
||||
raw = '"username") AS "val" FROM "auth_user"' \
|
||||
' WHERE "username"="admin" OR 1=%s --'
|
||||
User.objects.annotate(val=RawSQL(raw, [0]))
|
||||
User.objects.annotate(val=RawSQL(sql='{}secure'.format('no'), params=[]))
|
||||
User.objects.annotate(val=RawSQL(params=[], sql='{}secure'.format('no')))
|
||||
@@ -106,3 +106,11 @@ def func(x: bool | str):
|
||||
|
||||
def func(x: int | str):
|
||||
pass
|
||||
|
||||
|
||||
from typing import override
|
||||
|
||||
|
||||
@override
|
||||
def func(x: bool):
|
||||
pass
|
||||
|
||||
149
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B015.ipynb
vendored
Normal file
149
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B015.ipynb
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "33faf7ad-a3fd-4ac4-a0c3-52e507ed49df",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "481fb4bf-c1b9-47da-927f-3cfdfe4b49ec",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Simple case\n",
|
||||
"x == 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "2f0c65a5-0a0e-4080-afce-5a8ed0d706df",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Only skip the last expression\n",
|
||||
"x == 1 # B018\n",
|
||||
"x == 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "5a3fd75d-26d9-44f7-b013-1684aabfd0ae",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Nested expressions isn't relevant\n",
|
||||
"if True:\n",
|
||||
" x == 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "e00e1afa-b76c-4774-be2a-7223641579e4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Semicolons shouldn't affect the output\n",
|
||||
"x == 1;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "05eab5b9-e2ba-4954-8ef3-b035a79573fe",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Semicolons with multiple expressions\n",
|
||||
"x == 1; x == 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "9cbbddc5-83fc-4fdb-81ab-53a3912ae898",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Comments, newlines and whitespace\n",
|
||||
"x == 1 # comment\n",
|
||||
"\n",
|
||||
"# another comment"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff-playground)",
|
||||
"language": "python",
|
||||
"name": "ruff-playground"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
149
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B018.ipynb
vendored
Normal file
149
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B018.ipynb
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "33faf7ad-a3fd-4ac4-a0c3-52e507ed49df",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "481fb4bf-c1b9-47da-927f-3cfdfe4b49ec",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Simple case\n",
|
||||
"x"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "2f0c65a5-0a0e-4080-afce-5a8ed0d706df",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Only skip the last expression\n",
|
||||
"x # B018\n",
|
||||
"x"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "5a3fd75d-26d9-44f7-b013-1684aabfd0ae",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Nested expressions isn't relevant\n",
|
||||
"if True:\n",
|
||||
" x"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "e00e1afa-b76c-4774-be2a-7223641579e4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Semicolons shouldn't affect the output\n",
|
||||
"x;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "05eab5b9-e2ba-4954-8ef3-b035a79573fe",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Semicolons with multiple expressions\n",
|
||||
"x; x"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "9cbbddc5-83fc-4fdb-81ab-53a3912ae898",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Comments, newlines and whitespace\n",
|
||||
"x # comment\n",
|
||||
"\n",
|
||||
"# another comment"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff-playground)",
|
||||
"language": "python",
|
||||
"name": "ruff-playground"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -177,3 +177,33 @@ for i in range(10):
|
||||
for i in range(10):
|
||||
...
|
||||
pass
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class Repro(Protocol):
|
||||
def func(self) -> str:
|
||||
"""Docstring"""
|
||||
...
|
||||
|
||||
def impl(self) -> str:
|
||||
"""Docstring"""
|
||||
return self.func()
|
||||
|
||||
|
||||
import abc
|
||||
|
||||
|
||||
class Repro:
|
||||
@abc.abstractmethod
|
||||
def func(self) -> str:
|
||||
"""Docstring"""
|
||||
...
|
||||
|
||||
def impl(self) -> str:
|
||||
"""Docstring"""
|
||||
return self.func()
|
||||
|
||||
def stub(self) -> str:
|
||||
"""Docstring"""
|
||||
...
|
||||
|
||||
@@ -64,3 +64,9 @@ class FakeEnum10(enum.Enum):
|
||||
A = enum.auto()
|
||||
B = enum.auto()
|
||||
C = enum.auto()
|
||||
|
||||
|
||||
class FakeEnum10(enum.Enum):
|
||||
A = ...
|
||||
B = ... # PIE796
|
||||
C = ... # PIE796
|
||||
|
||||
7
crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE796.pyi
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE796.pyi
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import enum
|
||||
|
||||
|
||||
class FakeEnum1(enum.Enum):
|
||||
A = ...
|
||||
B = ...
|
||||
C = ...
|
||||
@@ -36,3 +36,54 @@ field10: (Literal[1] | str) | Literal[2] # Error
|
||||
|
||||
# Should emit for union in generic parent type.
|
||||
field11: dict[Literal[1] | Literal[2], str] # Error
|
||||
|
||||
# Should emit for unions with more than two cases
|
||||
field12: Literal[1] | Literal[2] | Literal[3] # Error
|
||||
field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
|
||||
|
||||
# Should emit for unions with more than two cases, even if not directly adjacent
|
||||
field14: Literal[1] | Literal[2] | str | Literal[3] # Error
|
||||
|
||||
# Should emit for unions with mixed literal internal types
|
||||
field15: Literal[1] | Literal["foo"] | Literal[True] # Error
|
||||
|
||||
# Shouldn't emit for duplicate field types with same value; covered by Y016
|
||||
field16: Literal[1] | Literal[1] # OK
|
||||
|
||||
# Shouldn't emit if in new parent type
|
||||
field17: Literal[1] | dict[Literal[2], str] # OK
|
||||
|
||||
# Shouldn't emit if not in a union parent
|
||||
field18: dict[Literal[1], Literal[2]] # OK
|
||||
|
||||
# Should respect name of literal type used
|
||||
field19: typing.Literal[1] | typing.Literal[2] # Error
|
||||
|
||||
# Should emit in cases with newlines
|
||||
field20: typing.Union[
|
||||
Literal[
|
||||
1 # test
|
||||
],
|
||||
Literal[2],
|
||||
] # Error, newline and comment will not be emitted in message
|
||||
|
||||
# Should handle multiple unions with multiple members
|
||||
field21: Literal[1, 2] | Literal[3, 4] # Error
|
||||
|
||||
# Should emit in cases with `typing.Union` instead of `|`
|
||||
field22: typing.Union[Literal[1], Literal[2]] # Error
|
||||
|
||||
# Should emit in cases with `typing_extensions.Literal`
|
||||
field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
|
||||
|
||||
# Should emit in cases with nested `typing.Union`
|
||||
field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
|
||||
|
||||
# Should emit in cases with mixed `typing.Union` and `|`
|
||||
field25: typing.Union[Literal[1], Literal[2] | str] # Error
|
||||
|
||||
# Should emit only once in cases with multiple nested `typing.Union`
|
||||
field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
|
||||
|
||||
# Should use the first literal subscript attribute when fixing
|
||||
field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error
|
||||
|
||||
@@ -84,3 +84,6 @@ field25: typing.Union[Literal[1], Literal[2] | str] # Error
|
||||
|
||||
# Should emit only once in cases with multiple nested `typing.Union`
|
||||
field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
|
||||
|
||||
# Should use the first literal subscript attribute when fixing
|
||||
field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error
|
||||
|
||||
@@ -22,3 +22,7 @@ Snake_case_alias: TypeAlias = int | float # PYI042, since not camel case
|
||||
|
||||
# check that this edge case doesn't crash
|
||||
_: TypeAlias = str | int
|
||||
|
||||
# PEP 695
|
||||
type foo_bar = int | str
|
||||
type FooBar = int | str
|
||||
|
||||
@@ -22,3 +22,7 @@ Snake_case_alias: TypeAlias = int | float # PYI042, since not camel case
|
||||
|
||||
# check that this edge case doesn't crash
|
||||
_: TypeAlias = str | int
|
||||
|
||||
# PEP 695
|
||||
type foo_bar = int | str
|
||||
type FooBar = int | str
|
||||
|
||||
@@ -21,3 +21,7 @@ _PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]
|
||||
|
||||
# check that this edge case doesn't crash
|
||||
_: TypeAlias = str | int
|
||||
|
||||
# PEP 695
|
||||
type _FooT = str | int
|
||||
type Foo = str | int
|
||||
|
||||
@@ -21,3 +21,7 @@ _PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]
|
||||
|
||||
# check that this edge case doesn't crash
|
||||
_: TypeAlias = str | int
|
||||
|
||||
# PEP 695
|
||||
type _FooT = str | int
|
||||
type Foo = str | int
|
||||
|
||||
@@ -10,3 +10,14 @@ def foo_no_return_typing_extensions(
|
||||
def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050
|
||||
def foo_no_return_pos_only(arg: int, /, arg2: NoReturn): ... # Error: PYI050
|
||||
def foo_never(arg: Never): ...
|
||||
def foo_args(*args: NoReturn): ... # Error: PYI050
|
||||
def foo_kwargs(**kwargs: NoReturn): ... # Error: PYI050
|
||||
def foo_args_kwargs(*args: NoReturn, **kwargs: NoReturn): ... # Error: PYI050
|
||||
def foo_int_args(*args: int): ...
|
||||
def foo_int_kwargs(**kwargs: int): ...
|
||||
def foo_int_args_kwargs(*args: int, **kwargs: int): ...
|
||||
def foo_int_args_no_return(*args: int, **kwargs: NoReturn): ... # Error: PYI050
|
||||
def foo_int_kwargs_no_return(*args: NoReturn, **kwargs: int): ... # Error: PYI050
|
||||
def foo_args_never(*args: Never): ...
|
||||
def foo_kwargs_never(**kwargs: Never): ...
|
||||
def foo_args_kwargs_never(*args: Never, **kwargs: Never): ...
|
||||
|
||||
@@ -91,3 +91,17 @@ field27 = list[str]
|
||||
field28 = builtins.str
|
||||
field29 = str
|
||||
field30 = str | bytes | None
|
||||
|
||||
# We shouldn't emit Y052 for `enum` subclasses.
|
||||
from enum import Enum
|
||||
|
||||
class Foo(Enum):
|
||||
FOO = 0
|
||||
BAR = 1
|
||||
|
||||
class Bar(Foo):
|
||||
BAZ = 2
|
||||
BOP = 3
|
||||
|
||||
class Bop:
|
||||
WIZ = 4
|
||||
|
||||
@@ -98,3 +98,17 @@ field27 = list[str]
|
||||
field28 = builtins.str
|
||||
field29 = str
|
||||
field30 = str | bytes | None
|
||||
|
||||
# We shouldn't emit Y052 for `enum` subclasses.
|
||||
from enum import Enum
|
||||
|
||||
class Foo(Enum):
|
||||
FOO = 0
|
||||
BAR = 1
|
||||
|
||||
class Bar(Foo):
|
||||
BAZ = 2
|
||||
BOP = 3
|
||||
|
||||
class Bop:
|
||||
WIZ = 4
|
||||
|
||||
@@ -134,3 +134,32 @@ with A() as a:
|
||||
f" something { my_dict["key"] } something else "
|
||||
|
||||
f"foo {f"bar {x}"} baz"
|
||||
|
||||
# Allow cascading for some statements.
|
||||
import anyio
|
||||
import asyncio
|
||||
import trio
|
||||
|
||||
async with asyncio.timeout(1):
|
||||
async with A() as a:
|
||||
pass
|
||||
|
||||
async with A():
|
||||
async with asyncio.timeout(1):
|
||||
pass
|
||||
|
||||
async with asyncio.timeout(1):
|
||||
async with asyncio.timeout_at(1):
|
||||
async with anyio.CancelScope():
|
||||
async with anyio.fail_after(1):
|
||||
async with anyio.move_on_after(1):
|
||||
async with trio.fail_after(1):
|
||||
async with trio.fail_at(1):
|
||||
async with trio.move_on_after(1):
|
||||
async with trio.move_on_at(1):
|
||||
pass
|
||||
|
||||
# Do not supress combination, if a context manager is already combined with another.
|
||||
async with asyncio.timeout(1), A():
|
||||
async with B():
|
||||
pass
|
||||
|
||||
@@ -25,3 +25,11 @@ a = {}.get(key, None)
|
||||
|
||||
# SIM910
|
||||
({}).get(key, None)
|
||||
|
||||
# SIM910
|
||||
ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
age = ages.get("Cat", None)
|
||||
|
||||
# OK
|
||||
ages = ["Tom", "Maria", "Dog"]
|
||||
age = ages.get("Cat", None)
|
||||
|
||||
@@ -26,5 +26,10 @@ def func():
|
||||
|
||||
from trio import Event, sleep
|
||||
|
||||
|
||||
def func():
|
||||
sleep(0) # TRIO115
|
||||
|
||||
|
||||
async def func():
|
||||
await sleep(seconds=0) # TRIO115
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence # TCH003
|
||||
from pandas import DataFrame
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class MyBaseClass:
|
||||
pass
|
||||
class Parent(BaseModel):
|
||||
...
|
||||
|
||||
|
||||
class Foo(MyBaseClass):
|
||||
foo: Sequence
|
||||
class Child(Parent):
|
||||
baz: DataFrame
|
||||
|
||||
34
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/singledispatch.py
vendored
Normal file
34
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/singledispatch.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
"""Test module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import singledispatch
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from numpy import asarray
|
||||
from numpy.typing import ArrayLike
|
||||
from scipy.sparse import spmatrix
|
||||
from pandas import DataFrame
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from numpy import ndarray
|
||||
|
||||
|
||||
@singledispatch
|
||||
def to_array_or_mat(a: ArrayLike | spmatrix) -> ndarray | spmatrix:
|
||||
"""Convert arg to array or leaves it as sparse matrix."""
|
||||
msg = f"Unhandled type {type(a)}"
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
|
||||
@to_array_or_mat.register
|
||||
def _(a: ArrayLike) -> ndarray:
|
||||
return asarray(a)
|
||||
|
||||
|
||||
@to_array_or_mat.register
|
||||
def _(a: spmatrix) -> spmatrix:
|
||||
return a
|
||||
|
||||
|
||||
def _(a: DataFrame) -> DataFrame:
|
||||
return a
|
||||
5
crates/ruff_linter/resources/test/fixtures/isort/from_first.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/isort/from_first.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
from __future__ import blah
|
||||
|
||||
from os import path
|
||||
|
||||
import os
|
||||
3
crates/ruff_linter/resources/test/fixtures/isort/length_sort_from_imports.py
vendored
Normal file
3
crates/ruff_linter/resources/test/fixtures/isort/length_sort_from_imports.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
from mediuuuuuuuuuuum import a
|
||||
from short import b
|
||||
from loooooooooooooooooooooog import c
|
||||
11
crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_members.py
vendored
Normal file
11
crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_members.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
from module1 import (
|
||||
loooooooooooooong,
|
||||
σηορτ,
|
||||
mediuuuuum,
|
||||
shoort,
|
||||
looooooooooooooong,
|
||||
μεδιυυυυυμ,
|
||||
short,
|
||||
mediuuuuuum,
|
||||
λοοοοοοοοοοοοοονγ,
|
||||
)
|
||||
9
crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_modules.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_modules.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import loooooooooooooong
|
||||
import mediuuuuuum
|
||||
import short
|
||||
import σηορτ
|
||||
import shoort
|
||||
import mediuuuuum
|
||||
import λοοοοοοοοοοοοοονγ
|
||||
import μεδιυυυυυμ
|
||||
import looooooooooooooong
|
||||
6
crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_and_from_imports.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_and_from_imports.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import mediuuuuuum
|
||||
import short
|
||||
import looooooooooooooooong
|
||||
from looooooooooooooong import a
|
||||
from mediuuuum import c
|
||||
from short import b
|
||||
4
crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_imports.py
vendored
Normal file
4
crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_imports.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import mediuuuuuumb
|
||||
import short
|
||||
import looooooooooooooooong
|
||||
import mediuuuuuuma
|
||||
7
crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_relative_imports.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_relative_imports.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
from ..looooooooooooooong import a
|
||||
from ...mediuuuum import b
|
||||
from .short import c
|
||||
from ....short import c
|
||||
from . import d
|
||||
from .mediuuuum import a
|
||||
from ......short import b
|
||||
3
crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_star_import.py
vendored
Normal file
3
crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_star_import.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
from looooooooooooooong import a
|
||||
from mediuuuum import *
|
||||
from short import *
|
||||
11
crates/ruff_linter/resources/test/fixtures/isort/main_first_party.py
vendored
Normal file
11
crates/ruff_linter/resources/test/fixtures/isort/main_first_party.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import os
|
||||
|
||||
import __main__
|
||||
import third_party
|
||||
|
||||
import first_party
|
||||
|
||||
os.a
|
||||
third_party.a
|
||||
__main__.a
|
||||
first_party.a
|
||||
@@ -1,6 +1,6 @@
|
||||
import collections
|
||||
from collections import namedtuple
|
||||
from typing import TypeAlias, TypeVar, NewType, NamedTuple, TypedDict
|
||||
from typing import Type, TypeAlias, TypeVar, NewType, NamedTuple, TypedDict
|
||||
|
||||
GLOBAL: str = "foo"
|
||||
|
||||
@@ -25,6 +25,8 @@ def assign():
|
||||
|
||||
IntOrStr: TypeAlias = int | str
|
||||
|
||||
type MyInt = int
|
||||
|
||||
|
||||
def aug_assign(rank, world_size):
|
||||
global CURRENT_PORT
|
||||
@@ -38,3 +40,18 @@ def loop_assign():
|
||||
global CURRENT_PORT
|
||||
for CURRENT_PORT in range(5):
|
||||
pass
|
||||
|
||||
|
||||
def model_assign() -> None:
|
||||
Bad = apps.get_model("zerver", "Stream") # N806
|
||||
Attachment = apps.get_model("zerver", "Attachment") # OK
|
||||
Recipient = apps.get_model("zerver", model_name="Recipient") # OK
|
||||
Address: Type = apps.get_model("zerver", "Address") # OK
|
||||
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
Bad = import_string("django.core.exceptions.ValidationError") # N806
|
||||
ValidationError = import_string("django.core.exceptions.ValidationError") # OK
|
||||
|
||||
Bad = apps.get_model() # N806
|
||||
Bad = apps.get_model(model_name="Stream") # N806
|
||||
|
||||
@@ -50,3 +50,16 @@ import itertools
|
||||
|
||||
for i in itertools.product(foo_int): # Ok
|
||||
pass
|
||||
|
||||
for i in list(foo_list): # Ok
|
||||
foo_list.append(i + 1)
|
||||
|
||||
for i in list(foo_list): # PERF101
|
||||
# Make sure we match the correct list
|
||||
other_list.append(i + 1)
|
||||
|
||||
for i in list(foo_tuple): # Ok
|
||||
foo_tuple.append(i + 1)
|
||||
|
||||
for i in list(foo_set): # Ok
|
||||
foo_set.append(i + 1)
|
||||
|
||||
@@ -135,3 +135,15 @@ ham[lower + offset: :upper + offset]
|
||||
|
||||
#: E203:1:20
|
||||
ham[{lower + offset : upper + offset} : upper + offset]
|
||||
|
||||
#: Okay
|
||||
ham[upper:]
|
||||
|
||||
#: Okay
|
||||
ham[upper :]
|
||||
|
||||
#: E202:1:12
|
||||
ham[upper : ]
|
||||
|
||||
#: E203:1:10
|
||||
ham[upper :]
|
||||
|
||||
113
crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.ipynb
vendored
Normal file
113
crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.ipynb
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "33faf7ad-a3fd-4ac4-a0c3-52e507ed49df",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"sys.path"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1331140f-2741-4661-9086-0764368710c9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a4113383-725d-4f04-80b8-a3080b2b8c4b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.path\n",
|
||||
"\n",
|
||||
"import pathlib"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a5d2ef63-ae60-4311-bae3-42e845afba3f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "79599475-a5ee-4f60-80d1-6efa77693da0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import a\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" import b\n",
|
||||
"except ImportError:\n",
|
||||
" pass\n",
|
||||
"else:\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"__some__magic = 1\n",
|
||||
"\n",
|
||||
"import c"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "863dcc35-5c8d-4d05-8b4a-91059e944112",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import ok\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def foo() -> None:\n",
|
||||
" import e\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"import no_ok"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6b2377d0-b814-4057-83ec-d443d8e19401",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff-playground)",
|
||||
"language": "python",
|
||||
"name": "ruff-playground"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
94
crates/ruff_linter/resources/test/fixtures/pycodestyle/E703.ipynb
vendored
Normal file
94
crates/ruff_linter/resources/test/fixtures/pycodestyle/E703.ipynb
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "33faf7ad-a3fd-4ac4-a0c3-52e507ed49df",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "481fb4bf-c1b9-47da-927f-3cfdfe4b49ec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Simple case\n",
|
||||
"x;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "2f0c65a5-0a0e-4080-afce-5a8ed0d706df",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Only skip the last expression\n",
|
||||
"x; # E703\n",
|
||||
"x;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "5a3fd75d-26d9-44f7-b013-1684aabfd0ae",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Nested expressions isn't relevant\n",
|
||||
"if True:\n",
|
||||
" x;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "05eab5b9-e2ba-4954-8ef3-b035a79573fe",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Semicolons with multiple expressions\n",
|
||||
"x; x;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "9cbbddc5-83fc-4fdb-81ab-53a3912ae898",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Comments, newlines and whitespace\n",
|
||||
"x; # comment\n",
|
||||
"\n",
|
||||
"# another comment"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff-playground)",
|
||||
"language": "python",
|
||||
"name": "ruff-playground"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -43,3 +43,6 @@ regex = '''
|
||||
''' # noqa
|
||||
|
||||
regex = '\\\_'
|
||||
|
||||
#: W605:1:7
|
||||
u'foo\ bar'
|
||||
|
||||
@@ -1,40 +1,54 @@
|
||||
# Same as `W605_0.py` but using f-strings instead.
|
||||
|
||||
#: W605:1:10
|
||||
regex = '\.png$'
|
||||
regex = f'\.png$'
|
||||
|
||||
#: W605:2:1
|
||||
regex = '''
|
||||
regex = f'''
|
||||
\.png$
|
||||
'''
|
||||
|
||||
#: W605:2:6
|
||||
f(
|
||||
'\_'
|
||||
f'\_'
|
||||
)
|
||||
|
||||
#: W605:4:6
|
||||
"""
|
||||
f"""
|
||||
multi-line
|
||||
literal
|
||||
with \_ somewhere
|
||||
in the middle
|
||||
"""
|
||||
|
||||
#: W605:1:38
|
||||
value = f'new line\nand invalid escape \_ here'
|
||||
|
||||
def f():
|
||||
#: W605:1:11
|
||||
return'\.png$'
|
||||
|
||||
#: Okay
|
||||
regex = r'\.png$'
|
||||
regex = '\\.png$'
|
||||
regex = r'''
|
||||
regex = fr'\.png$'
|
||||
regex = f'\\.png$'
|
||||
regex = fr'''
|
||||
\.png$
|
||||
'''
|
||||
regex = r'''
|
||||
regex = fr'''
|
||||
\\.png$
|
||||
'''
|
||||
s = '\\'
|
||||
regex = '\w' # noqa
|
||||
regex = '''
|
||||
s = f'\\'
|
||||
regex = f'\w' # noqa
|
||||
regex = f'''
|
||||
\w
|
||||
''' # noqa
|
||||
|
||||
regex = f'\\\_'
|
||||
value = f'\{{1}}'
|
||||
value = f'\{1}'
|
||||
value = f'{1:\}'
|
||||
value = f"{f"\{1}"}"
|
||||
value = rf"{f"\{1}"}"
|
||||
|
||||
# Okay
|
||||
value = rf'\{{1}}'
|
||||
value = rf'\{1}'
|
||||
value = rf'{1:\}'
|
||||
value = f"{rf"\{1}"}"
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Same as `W605_0.py` but using f-strings instead.
|
||||
|
||||
#: W605:1:10
|
||||
regex = f'\.png$'
|
||||
|
||||
#: W605:2:1
|
||||
regex = f'''
|
||||
\.png$
|
||||
'''
|
||||
|
||||
#: W605:2:6
|
||||
f(
|
||||
f'\_'
|
||||
)
|
||||
|
||||
#: W605:4:6
|
||||
f"""
|
||||
multi-line
|
||||
literal
|
||||
with \_ somewhere
|
||||
in the middle
|
||||
"""
|
||||
|
||||
#: W605:1:38
|
||||
value = f'new line\nand invalid escape \_ here'
|
||||
|
||||
|
||||
#: Okay
|
||||
regex = fr'\.png$'
|
||||
regex = f'\\.png$'
|
||||
regex = fr'''
|
||||
\.png$
|
||||
'''
|
||||
regex = fr'''
|
||||
\\.png$
|
||||
'''
|
||||
s = f'\\'
|
||||
regex = f'\w' # noqa
|
||||
regex = f'''
|
||||
\w
|
||||
''' # noqa
|
||||
|
||||
regex = f'\\\_'
|
||||
value = f'\{{1}}'
|
||||
value = f'\{1}'
|
||||
value = f'{1:\}'
|
||||
value = f"{f"\{1}"}"
|
||||
value = rf"{f"\{1}"}"
|
||||
|
||||
# Okay
|
||||
value = rf'\{{1}}'
|
||||
value = rf'\{1}'
|
||||
value = rf'{1:\}'
|
||||
value = f"{rf"\{1}"}"
|
||||
43
crates/ruff_linter/resources/test/fixtures/pydocstyle/D100.ipynb
vendored
Normal file
43
crates/ruff_linter/resources/test/fixtures/pydocstyle/D100.ipynb
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "33faf7ad-a3fd-4ac4-a0c3-52e507ed49df",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Hello world!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\"Hello world!\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff-playground)",
|
||||
"language": "python",
|
||||
"name": "ruff-playground"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
16
crates/ruff_linter/resources/test/fixtures/pydocstyle/D208.py
vendored
Normal file
16
crates/ruff_linter/resources/test/fixtures/pydocstyle/D208.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Author
|
||||
"""
|
||||
|
||||
|
||||
class Platform:
|
||||
""" Remove sampler
|
||||
Args:
|
||||
Returns:
|
||||
"""
|
||||
|
||||
|
||||
def memory_test():
|
||||
"""
|
||||
参数含义:precision:精确到小数点后几位
|
||||
"""
|
||||
@@ -91,3 +91,12 @@ def f(rounds: list[int], number: int) -> bool:
|
||||
bool: was the round played?
|
||||
"""
|
||||
return number in rounds
|
||||
|
||||
|
||||
def f():
|
||||
"""
|
||||
My example
|
||||
==========
|
||||
|
||||
My example explanation
|
||||
"""
|
||||
|
||||
7
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_23.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_23.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Test for type annotation parsing in `NamedTuple`."""
|
||||
|
||||
import typing
|
||||
|
||||
User = typing.NamedTuple('User', **{'name': str, 'password': bytes})
|
||||
User = typing.NamedTuple('User', name=str, password=bytes)
|
||||
User = typing.NamedTuple('User', [('name', str), ('password', bytes)])
|
||||
@@ -50,7 +50,7 @@ class Apples:
|
||||
return "Docstring"
|
||||
|
||||
# Added in Python 3.12
|
||||
def __buffer__(self):
|
||||
def __buffer__(self):
|
||||
return memoryview(b'')
|
||||
|
||||
def __release_buffer__(self, buf):
|
||||
@@ -83,6 +83,10 @@ class Apples:
|
||||
def _(self):
|
||||
pass
|
||||
|
||||
# Allow custom dunder names (via setting).
|
||||
def __special_custom_magic__(self):
|
||||
pass
|
||||
|
||||
|
||||
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
|
||||
...
|
||||
|
||||
19
crates/ruff_linter/resources/test/fixtures/pylint/no_method_decorator.py
vendored
Normal file
19
crates/ruff_linter/resources/test/fixtures/pylint/no_method_decorator.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
from random import choice
|
||||
|
||||
class Fruit:
|
||||
COLORS = []
|
||||
|
||||
def __init__(self, color):
|
||||
self.color = color
|
||||
|
||||
def pick_colors(cls, *args): # [no-classmethod-decorator]
|
||||
"""classmethod to pick fruit colors"""
|
||||
cls.COLORS = args
|
||||
|
||||
pick_colors = classmethod(pick_colors)
|
||||
|
||||
def pick_one_color(): # [no-staticmethod-decorator]
|
||||
"""staticmethod to pick one fruit color"""
|
||||
return choice(Fruit.COLORS)
|
||||
|
||||
pick_one_color = staticmethod(pick_one_color)
|
||||
20
crates/ruff_linter/resources/test/fixtures/pylint/repeated_keyword_argument.py
vendored
Normal file
20
crates/ruff_linter/resources/test/fixtures/pylint/repeated_keyword_argument.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
def func(a=10, b=20, c=30):
|
||||
pass
|
||||
|
||||
# Valid
|
||||
func(a=11, b=21, c=31)
|
||||
func(b=11, a=21, c=31)
|
||||
func(c=11, a=21, b=31)
|
||||
func(a=11, b=31, **{"c": 41})
|
||||
func(a=11, **{"b": 21}, **{"c": 41})
|
||||
func(a=11, **{"b": 21, "c": 41})
|
||||
func(**{"b": 21, "c": 41})
|
||||
func(**{"a": 11}, **{"b": 21}, **{"c": 41})
|
||||
func(**{"a": 11, "b": 21, "c": 41})
|
||||
|
||||
# Invalid
|
||||
func(a=11, c=31, **{"c": 41})
|
||||
func(a=11, c=31, **{"c": 41, "a": 51})
|
||||
func(a=11, b=21, c=31, **{"b": 22, "c": 41, "a": 51})
|
||||
func(a=11, b=21, **{"c": 31}, **{"c": 32})
|
||||
func(a=11, b=21, **{"c": 31, "c": 32})
|
||||
@@ -23,6 +23,9 @@ bar, (foo, baz) = bar, (foo, 1)
|
||||
(foo, (bar, baz)) = (foo, (bar, 1))
|
||||
foo: int = foo
|
||||
bar: int = bar
|
||||
foo = foo = bar
|
||||
(foo, bar) = (foo, bar) = baz
|
||||
(foo, bar) = baz = (foo, bar) = 1
|
||||
|
||||
# Non-errors.
|
||||
foo = bar
|
||||
|
||||
@@ -32,3 +32,16 @@ def f(x, y, z, *, u, v, w): # Too many arguments (6/5)
|
||||
|
||||
def f(x, y, z, a, b, c, *, u, v, w): # Too many arguments (9/5)
|
||||
pass
|
||||
|
||||
|
||||
from typing import override, overload
|
||||
|
||||
|
||||
@override
|
||||
def f(x, y, z, a, b, c, *, u, v, w): # OK
|
||||
pass
|
||||
|
||||
|
||||
@overload
|
||||
def f(x, y, z, a, b, c, *, u, v, w): # OK
|
||||
pass
|
||||
|
||||
30
crates/ruff_linter/resources/test/fixtures/pylint/too_many_positional.py
vendored
Normal file
30
crates/ruff_linter/resources/test/fixtures/pylint/too_many_positional.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
def f(x, y, z, t, u, v, w, r): # Too many positional arguments (8/3)
|
||||
pass
|
||||
|
||||
|
||||
def f(x): # OK
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, _t, _u, _v, _w, r): # OK (underscore-prefixed names are ignored
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, *, u=1, v=1, r=1): # OK
|
||||
pass
|
||||
|
||||
|
||||
def f(x=1, y=1, z=1): # OK
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, /, u, v, w): # Too many positional arguments (6/3)
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, *, u, v, w): # OK
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, a, b, c, *, u, v, w): # Too many positional arguments (6/3)
|
||||
pass
|
||||
10
crates/ruff_linter/resources/test/fixtures/pylint/too_many_positional_params.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pylint/too_many_positional_params.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Too many positional arguments (7/4) for max_positional=4
|
||||
# OK for dummy_variable_rgx ~ "skip_.*"
|
||||
def f(w, x, y, z, skip_t, skip_u, skip_v):
|
||||
pass
|
||||
|
||||
|
||||
# Too many positional arguments (7/4) for max_args=4
|
||||
# Too many positional arguments (7/3) for dummy_variable_rgx ~ "skip_.*"
|
||||
def f(w, x, y, z, t, u, v):
|
||||
pass
|
||||
40
crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_dict_index_lookup.py
vendored
Normal file
40
crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_dict_index_lookup.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
FRUITS = {"apple": 1, "orange": 10, "berry": 22}
|
||||
|
||||
def fix_these():
|
||||
[FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()] # PLR1733
|
||||
{FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
{fruit_name: FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
|
||||
for fruit_name, fruit_count in FRUITS.items():
|
||||
print(FRUITS[fruit_name]) # PLR1733
|
||||
blah = FRUITS[fruit_name] # PLR1733
|
||||
assert FRUITS[fruit_name] == "pear" # PLR1733
|
||||
|
||||
|
||||
def dont_fix_these():
|
||||
# once there is an assignment to the dict[index], we stop emitting diagnostics
|
||||
for fruit_name, fruit_count in FRUITS.items():
|
||||
FRUITS[fruit_name] = 0 # OK
|
||||
assert FRUITS[fruit_name] == 0 # OK
|
||||
|
||||
# once there is an assignment to the key, we stop emitting diagnostics
|
||||
for fruit_name, fruit_count in FRUITS.items():
|
||||
fruit_name = 0 # OK
|
||||
assert FRUITS[fruit_name] == 0 # OK
|
||||
|
||||
# once there is an assignment to the value, we stop emitting diagnostics
|
||||
for fruit_name, fruit_count in FRUITS.items():
|
||||
if fruit_count < 5:
|
||||
fruit_count = -fruit_count
|
||||
assert FRUITS[fruit_name] == 0 # OK
|
||||
|
||||
|
||||
def value_intentionally_unused():
|
||||
[FRUITS[fruit_name] for fruit_name, _ in FRUITS.items()] # OK
|
||||
{FRUITS[fruit_name] for fruit_name, _ in FRUITS.items()} # OK
|
||||
{fruit_name: FRUITS[fruit_name] for fruit_name, _ in FRUITS.items()} # OK
|
||||
|
||||
for fruit_name, _ in FRUITS.items():
|
||||
print(FRUITS[fruit_name]) # OK
|
||||
blah = FRUITS[fruit_name] # OK
|
||||
assert FRUITS[fruit_name] == "pear" # OK
|
||||
64
crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_list_index_lookup.py
vendored
Normal file
64
crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_list_index_lookup.py
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import builtins
|
||||
|
||||
letters = ["a", "b", "c"]
|
||||
|
||||
|
||||
def fix_these():
|
||||
[letters[index] for index, letter in enumerate(letters)] # PLR1736
|
||||
{letters[index] for index, letter in enumerate(letters)} # PLR1736
|
||||
{letter: letters[index] for index, letter in enumerate(letters)} # PLR1736
|
||||
|
||||
for index, letter in enumerate(letters):
|
||||
print(letters[index]) # PLR1736
|
||||
blah = letters[index] # PLR1736
|
||||
assert letters[index] == "d" # PLR1736
|
||||
|
||||
for index, letter in builtins.enumerate(letters):
|
||||
print(letters[index]) # PLR1736
|
||||
blah = letters[index] # PLR1736
|
||||
assert letters[index] == "d" # PLR1736
|
||||
|
||||
|
||||
def dont_fix_these():
|
||||
# once there is an assignment to the sequence[index], we stop emitting diagnostics
|
||||
for index, letter in enumerate(letters):
|
||||
letters[index] = "d" # OK
|
||||
letters[index] += "e" # OK
|
||||
assert letters[index] == "de" # OK
|
||||
|
||||
# once there is an assignment to the index, we stop emitting diagnostics
|
||||
for index, letter in enumerate(letters):
|
||||
index += 1 # OK
|
||||
print(letters[index]) # OK
|
||||
|
||||
# once there is an assignment to the sequence, we stop emitting diagnostics
|
||||
for index, letter in enumerate(letters):
|
||||
letters = ["d", "e", "f"] # OK
|
||||
print(letters[index]) # OK
|
||||
|
||||
# once there is an assignment to the value, we stop emitting diagnostics
|
||||
for index, letter in enumerate(letters):
|
||||
letter = "d"
|
||||
print(letters[index]) # OK
|
||||
|
||||
# once there is an deletion from or of the sequence or index, we stop emitting diagnostics
|
||||
for index, letter in enumerate(letters):
|
||||
del letters[index] # OK
|
||||
print(letters[index]) # OK
|
||||
for index, letter in enumerate(letters):
|
||||
del letters # OK
|
||||
print(letters[index]) # OK
|
||||
for index, letter in enumerate(letters):
|
||||
del index # OK
|
||||
print(letters[index]) # OK
|
||||
|
||||
|
||||
def value_intentionally_unused():
|
||||
[letters[index] for index, _ in enumerate(letters)] # OK
|
||||
{letters[index] for index, _ in enumerate(letters)} # OK
|
||||
{index: letters[index] for index, _ in enumerate(letters)} # OK
|
||||
|
||||
for index, _ in enumerate(letters):
|
||||
print(letters[index]) # OK
|
||||
blah = letters[index] # OK
|
||||
letters[index] = "d" # OK
|
||||
@@ -42,3 +42,30 @@ tempfile.SpooledTemporaryFile(0, "w", encoding="utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "w", -1, "utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "wb")
|
||||
tempfile.SpooledTemporaryFile(0, )
|
||||
|
||||
open("test.txt",)
|
||||
open()
|
||||
open(
|
||||
"test.txt", # comment
|
||||
)
|
||||
open(
|
||||
"test.txt",
|
||||
# comment
|
||||
)
|
||||
open(("test.txt"),)
|
||||
open(
|
||||
("test.txt"), # comment
|
||||
)
|
||||
open(
|
||||
("test.txt"),
|
||||
# comment
|
||||
)
|
||||
|
||||
open((("test.txt")),)
|
||||
open(
|
||||
(("test.txt")), # comment
|
||||
)
|
||||
open(
|
||||
(("test.txt")),
|
||||
# comment
|
||||
)
|
||||
|
||||
47
crates/ruff_linter/resources/test/fixtures/refurb/FURB163.py
vendored
Normal file
47
crates/ruff_linter/resources/test/fixtures/refurb/FURB163.py
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import math
|
||||
|
||||
from math import e as special_e
|
||||
from math import log as special_log
|
||||
|
||||
# Errors.
|
||||
math.log(1, 2)
|
||||
math.log(1, 10)
|
||||
math.log(1, math.e)
|
||||
foo = ...
|
||||
math.log(foo, 2)
|
||||
math.log(foo, 10)
|
||||
math.log(foo, math.e)
|
||||
math.log(1, special_e)
|
||||
special_log(1, 2)
|
||||
special_log(1, 10)
|
||||
special_log(1, math.e)
|
||||
special_log(1, special_e)
|
||||
|
||||
# Ok.
|
||||
math.log2(1)
|
||||
math.log10(1)
|
||||
math.log(1)
|
||||
math.log(1, 3)
|
||||
math.log(1, math.pi)
|
||||
|
||||
two = 2
|
||||
math.log(1, two)
|
||||
|
||||
ten = 10
|
||||
math.log(1, ten)
|
||||
|
||||
e = math.e
|
||||
math.log(1, e)
|
||||
|
||||
math.log2(1, 10) # math.log2 takes only one argument.
|
||||
math.log10(1, 2) # math.log10 takes only one argument.
|
||||
|
||||
math.log(1, base=2) # math.log does not accept keyword arguments.
|
||||
|
||||
def log(*args):
|
||||
print(f"Logging: {args}")
|
||||
|
||||
|
||||
log(1, 2)
|
||||
log(1, 10)
|
||||
log(1, math.e)
|
||||
@@ -48,3 +48,14 @@ class E(Struct):
|
||||
without_annotation = []
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class F(BaseSettings):
|
||||
mutable_default: list[int] = []
|
||||
immutable_annotation: Sequence[int] = []
|
||||
without_annotation = []
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
|
||||
@@ -23,3 +23,6 @@ print(a) # noqa: E501, F821 # comment
|
||||
print(a) # noqa: E501, F821 # comment
|
||||
print(a) # noqa: E501, F821 comment
|
||||
print(a) # noqa: E501, F821 comment
|
||||
|
||||
print(a) # comment with unicode µ # noqa: E501
|
||||
print(a) # comment with unicode µ # noqa: E501, F821
|
||||
|
||||
@@ -109,3 +109,13 @@ def fine():
|
||||
a = 1
|
||||
except Exception:
|
||||
error("Context message here", exc_info=sys.exc_info())
|
||||
|
||||
|
||||
def nested():
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
try:
|
||||
b = 2
|
||||
except Exception:
|
||||
error("Context message here")
|
||||
|
||||
@@ -126,3 +126,25 @@ def main_function():
|
||||
except Exception as ex:
|
||||
exception(f"Found an error: {er}")
|
||||
exception(f"Found an error: {ex.field}")
|
||||
|
||||
|
||||
def nested():
|
||||
try:
|
||||
process()
|
||||
handle()
|
||||
except Exception as ex:
|
||||
try:
|
||||
finish()
|
||||
except Exception as ex:
|
||||
logger.exception(f"Found an error: {ex}") # TRY401
|
||||
|
||||
|
||||
def nested():
|
||||
try:
|
||||
process()
|
||||
handle()
|
||||
except Exception as ex:
|
||||
try:
|
||||
finish()
|
||||
except Exception:
|
||||
logger.exception(f"Found an error: {ex}") # TRY401
|
||||
|
||||
@@ -158,21 +158,23 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
}
|
||||
|
||||
// Extract a `Docstring` from a `Definition`.
|
||||
let Some(expr) = docstring else {
|
||||
let Some(string_literal) = docstring else {
|
||||
pydocstyle::rules::not_missing(checker, definition, *visibility);
|
||||
continue;
|
||||
};
|
||||
|
||||
let contents = checker.locator().slice(expr);
|
||||
let contents = checker.locator().slice(string_literal);
|
||||
|
||||
let indentation = checker.locator().slice(TextRange::new(
|
||||
checker.locator.line_start(expr.start()),
|
||||
expr.start(),
|
||||
checker.locator.line_start(string_literal.start()),
|
||||
string_literal.start(),
|
||||
));
|
||||
|
||||
if expr.implicit_concatenated {
|
||||
if string_literal.value.is_implicit_concatenated() {
|
||||
#[allow(deprecated)]
|
||||
let location = checker.locator.compute_source_location(expr.start());
|
||||
let location = checker
|
||||
.locator
|
||||
.compute_source_location(string_literal.start());
|
||||
warn_user!(
|
||||
"Docstring at {}:{}:{} contains implicit string concatenation; ignoring...",
|
||||
relativize_path(checker.path),
|
||||
@@ -186,7 +188,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
let body_range = raw_contents_range(contents).unwrap();
|
||||
let docstring = Docstring {
|
||||
definition,
|
||||
expr,
|
||||
expr: string_literal,
|
||||
contents,
|
||||
body_range,
|
||||
indentation,
|
||||
|
||||
@@ -205,19 +205,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
ExprContext::Store => {
|
||||
if checker.enabled(Rule::NonLowercaseVariableInFunction) {
|
||||
if checker.semantic.current_scope().kind.is_function() {
|
||||
// Ignore globals.
|
||||
if !checker
|
||||
.semantic
|
||||
.current_scope()
|
||||
.get(id)
|
||||
.is_some_and(|binding_id| {
|
||||
checker.semantic.binding(binding_id).is_global()
|
||||
})
|
||||
{
|
||||
pep8_naming::rules::non_lowercase_variable_in_function(
|
||||
checker, expr, id,
|
||||
);
|
||||
}
|
||||
pep8_naming::rules::non_lowercase_variable_in_function(
|
||||
checker, expr, id,
|
||||
);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
|
||||
@@ -369,18 +359,24 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
]) {
|
||||
if let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() {
|
||||
let attr = attr.as_str();
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value: string, .. }) =
|
||||
value.as_ref()
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral {
|
||||
value: string_value,
|
||||
..
|
||||
}) = value.as_ref()
|
||||
{
|
||||
if attr == "join" {
|
||||
// "...".join(...) call
|
||||
if checker.enabled(Rule::StaticJoinToFString) {
|
||||
flynt::rules::static_join_to_fstring(checker, expr, string);
|
||||
flynt::rules::static_join_to_fstring(
|
||||
checker,
|
||||
expr,
|
||||
string_value.to_str(),
|
||||
);
|
||||
}
|
||||
} else if attr == "format" {
|
||||
// "...".format(...) call
|
||||
let location = expr.range();
|
||||
match pyflakes::format::FormatSummary::try_from(string.as_ref()) {
|
||||
match pyflakes::format::FormatSummary::try_from(string_value.to_str()) {
|
||||
Err(e) => {
|
||||
if checker.enabled(Rule::StringDotFormatInvalidFormat) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
@@ -425,7 +421,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
|
||||
if checker.enabled(Rule::BadStringFormatCharacter) {
|
||||
pylint::rules::bad_string_format_character::call(
|
||||
checker, string, location,
|
||||
checker,
|
||||
string_value.to_str(),
|
||||
location,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -612,6 +610,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
]) {
|
||||
flake8_bandit::rules::shell_injection(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::DjangoRawSql) {
|
||||
flake8_bandit::rules::django_raw_sql(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::TarfileUnsafeMembers) {
|
||||
flake8_bandit::rules::tarfile_unsafe_members(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryGeneratorList) {
|
||||
flake8_comprehensions::rules::unnecessary_generator_list(
|
||||
checker, expr, func, args, keywords,
|
||||
@@ -776,6 +780,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::NestedMinMax) {
|
||||
pylint::rules::nested_min_max(checker, expr, func, args, keywords);
|
||||
}
|
||||
if checker.enabled(Rule::RepeatedKeywordArgument) {
|
||||
pylint::rules::repeated_keyword_argument(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::PytestPatchWithLambda) {
|
||||
if let Some(diagnostic) = flake8_pytest_style::rules::patch_with_lambda(call) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
@@ -911,6 +918,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::PrintEmptyString) {
|
||||
refurb::rules::print_empty_string(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantLogBase) {
|
||||
refurb::rules::redundant_log_base(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::QuadraticListSummation) {
|
||||
ruff::rules::quadratic_list_summation(checker, call);
|
||||
}
|
||||
@@ -979,15 +989,22 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::await_outside_async(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::FString(fstring @ ast::ExprFString { values, .. }) => {
|
||||
Expr::FString(f_string_expr @ ast::ExprFString { value, .. }) => {
|
||||
if checker.enabled(Rule::FStringMissingPlaceholders) {
|
||||
pyflakes::rules::f_string_missing_placeholders(fstring, checker);
|
||||
pyflakes::rules::f_string_missing_placeholders(checker, f_string_expr);
|
||||
}
|
||||
if checker.enabled(Rule::ExplicitFStringTypeConversion) {
|
||||
for f_string in value.f_strings() {
|
||||
ruff::rules::explicit_f_string_type_conversion(checker, f_string);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::HardcodedSQLExpression) {
|
||||
flake8_bandit::rules::hardcoded_sql_expression(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::ExplicitFStringTypeConversion) {
|
||||
ruff::rules::explicit_f_string_type_conversion(checker, expr, values);
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
for string_literal in value.literals() {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, string_literal);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
@@ -1018,7 +1035,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
Rule::PercentFormatUnsupportedFormatCharacter,
|
||||
]) {
|
||||
let location = expr.range();
|
||||
match pyflakes::cformat::CFormatSummary::try_from(value.as_str()) {
|
||||
match pyflakes::cformat::CFormatSummary::try_from(value.to_str()) {
|
||||
Err(CFormatError {
|
||||
typ: CFormatErrorType::UnsupportedFormatChar(c),
|
||||
..
|
||||
@@ -1269,6 +1286,11 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::HardcodedTempFile) {
|
||||
flake8_bandit::rules::hardcoded_tmp_directory(checker, string);
|
||||
}
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
for string_part in string.value.parts() {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, string_part);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
|
||||
@@ -1308,6 +1330,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryComprehension) {
|
||||
flake8_comprehensions::rules::unnecessary_list_set_comprehension(
|
||||
checker, expr, elt, generators,
|
||||
@@ -1332,6 +1360,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryComprehension) {
|
||||
flake8_comprehensions::rules::unnecessary_list_set_comprehension(
|
||||
checker, expr, elt, generators,
|
||||
@@ -1355,6 +1389,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
generators,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryComprehension) {
|
||||
flake8_comprehensions::rules::unnecessary_dict_comprehension(
|
||||
checker, expr, key, value, generators,
|
||||
@@ -1379,6 +1419,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::FunctionUsesLoopVariable) {
|
||||
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Expr(expr));
|
||||
}
|
||||
|
||||
@@ -248,7 +248,10 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pylint::rules::property_with_parameters(checker, stmt, decorator_list, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyArguments) {
|
||||
pylint::rules::too_many_arguments(checker, parameters, stmt);
|
||||
pylint::rules::too_many_arguments(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyPositional) {
|
||||
pylint::rules::too_many_positional(checker, parameters, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyReturnStatements) {
|
||||
if let Some(diagnostic) = pylint::rules::too_many_return_statements(
|
||||
@@ -384,6 +387,12 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::NoClassmethodDecorator) {
|
||||
pylint::rules::no_classmethod_decorator(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::NoStaticmethodDecorator) {
|
||||
pylint::rules::no_staticmethod_decorator(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::DjangoNullableModelStringField) {
|
||||
flake8_django::rules::nullable_model_string_field(checker, body);
|
||||
}
|
||||
@@ -1269,7 +1278,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
perflint::rules::manual_dict_comprehension(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryListCast) {
|
||||
perflint::rules::unnecessary_list_cast(checker, iter);
|
||||
perflint::rules::unnecessary_list_cast(checker, iter, body);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
pylint::rules::unnecessary_list_index_lookup(checker, for_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup(checker, for_stmt);
|
||||
}
|
||||
if !is_async {
|
||||
if checker.enabled(Rule::ReimplementedBuiltin) {
|
||||
@@ -1355,7 +1370,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
Stmt::Assign(assign @ ast::StmtAssign { targets, value, .. }) => {
|
||||
checker.enabled(Rule::NonAsciiName);
|
||||
if checker.enabled(Rule::LambdaAssignment) {
|
||||
if let [target] = &targets[..] {
|
||||
pycodestyle::rules::lambda_assignment(checker, target, value, None, stmt);
|
||||
@@ -1407,9 +1421,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.settings.rules.enabled(Rule::SelfAssigningVariable) {
|
||||
if let [target] = targets.as_slice() {
|
||||
pylint::rules::self_assigning_variable(checker, target, value);
|
||||
}
|
||||
pylint::rules::self_assignment(checker, assign);
|
||||
}
|
||||
if checker.settings.rules.enabled(Rule::TypeParamNameMismatch) {
|
||||
pylint::rules::type_param_name_mismatch(checker, value, targets);
|
||||
@@ -1479,9 +1491,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
stmt,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::SelfAssigningVariable) {
|
||||
pylint::rules::self_assigning_variable(checker, target, value);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::SelfAssigningVariable) {
|
||||
pylint::rules::self_annotated_assignment(checker, assign_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::UnintentionalTypeAnnotation) {
|
||||
flake8_bugbear::rules::unintentional_type_annotation(
|
||||
@@ -1525,6 +1537,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::TypeAlias(ast::StmtTypeAlias { name, .. }) => {
|
||||
if checker.enabled(Rule::SnakeCaseTypeAlias) {
|
||||
flake8_pyi::rules::snake_case_type_alias(checker, name);
|
||||
}
|
||||
if checker.enabled(Rule::TSuffixedTypeAlias) {
|
||||
flake8_pyi::rules::t_suffixed_type_alias(checker, name);
|
||||
}
|
||||
}
|
||||
Stmt::Delete(delete @ ast::StmtDelete { targets, range: _ }) => {
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
for target in targets {
|
||||
|
||||
@@ -37,6 +37,7 @@ use ruff_python_ast::{
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, IsolationLevel};
|
||||
use ruff_notebook::CellOffsets;
|
||||
use ruff_python_ast::all::{extract_all_names, DunderAllFlags};
|
||||
use ruff_python_ast::helpers::{
|
||||
collect_import_from_member, extract_handled_exceptions, to_module_path,
|
||||
@@ -78,6 +79,8 @@ pub(crate) struct Checker<'a> {
|
||||
module_path: Option<&'a [String]>,
|
||||
/// The [`PySourceType`] of the current file.
|
||||
pub(crate) source_type: PySourceType,
|
||||
/// The [`CellOffsets`] for the current file, if it's a Jupyter notebook.
|
||||
cell_offsets: Option<&'a CellOffsets>,
|
||||
/// The [`flags::Noqa`] for the current analysis (i.e., whether to respect suppression
|
||||
/// comments).
|
||||
noqa: flags::Noqa,
|
||||
@@ -104,6 +107,8 @@ pub(crate) struct Checker<'a> {
|
||||
pub(crate) diagnostics: Vec<Diagnostic>,
|
||||
/// The list of names already seen by flake8-bugbear diagnostics, to avoid duplicate violations..
|
||||
pub(crate) flake8_bugbear_seen: Vec<TextRange>,
|
||||
/// The end offset of the last visited statement.
|
||||
last_stmt_end: TextSize,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -120,6 +125,7 @@ impl<'a> Checker<'a> {
|
||||
indexer: &'a Indexer,
|
||||
importer: Importer<'a>,
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&'a CellOffsets>,
|
||||
) -> Checker<'a> {
|
||||
Checker {
|
||||
settings,
|
||||
@@ -137,6 +143,8 @@ impl<'a> Checker<'a> {
|
||||
deferred: Deferred::default(),
|
||||
diagnostics: Vec::default(),
|
||||
flake8_bugbear_seen: Vec::default(),
|
||||
cell_offsets,
|
||||
last_stmt_end: TextSize::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,6 +233,11 @@ impl<'a> Checker<'a> {
|
||||
self.package
|
||||
}
|
||||
|
||||
/// The [`CellOffsets`] for the current file, if it's a Jupyter notebook.
|
||||
pub(crate) const fn cell_offsets(&self) -> Option<&'a CellOffsets> {
|
||||
self.cell_offsets
|
||||
}
|
||||
|
||||
/// Returns whether the given rule should be checked.
|
||||
#[inline]
|
||||
pub(crate) const fn enabled(&self, rule: Rule) -> bool {
|
||||
@@ -258,6 +271,18 @@ where
|
||||
// Step 0: Pre-processing
|
||||
self.semantic.push_node(stmt);
|
||||
|
||||
// For Jupyter Notebooks, we'll reset the `IMPORT_BOUNDARY` flag when
|
||||
// we encounter a cell boundary.
|
||||
if self.source_type.is_ipynb()
|
||||
&& self.semantic.at_top_level()
|
||||
&& self.semantic.seen_import_boundary()
|
||||
&& self.cell_offsets.is_some_and(|cell_offsets| {
|
||||
cell_offsets.has_cell_boundary(TextRange::new(self.last_stmt_end, stmt.start()))
|
||||
})
|
||||
{
|
||||
self.semantic.flags -= SemanticModelFlags::IMPORT_BOUNDARY;
|
||||
}
|
||||
|
||||
// Track whether we've seen docstrings, non-imports, etc.
|
||||
match stmt {
|
||||
Stmt::ImportFrom(ast::StmtImportFrom { module, names, .. }) => {
|
||||
@@ -467,6 +492,13 @@ where
|
||||
// are enabled.
|
||||
let runtime_annotation = !self.semantic.future_annotations();
|
||||
|
||||
// The first parameter may be a single dispatch.
|
||||
let mut singledispatch =
|
||||
flake8_type_checking::helpers::is_singledispatch_implementation(
|
||||
function_def,
|
||||
self.semantic(),
|
||||
);
|
||||
|
||||
self.semantic.push_scope(ScopeKind::Type);
|
||||
|
||||
if let Some(type_params) = type_params {
|
||||
@@ -480,7 +512,7 @@ where
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
if let Some(expr) = ¶meter_with_default.parameter.annotation {
|
||||
if runtime_annotation {
|
||||
if runtime_annotation || singledispatch {
|
||||
self.visit_runtime_annotation(expr);
|
||||
} else {
|
||||
self.visit_annotation(expr);
|
||||
@@ -489,6 +521,7 @@ where
|
||||
if let Some(expr) = ¶meter_with_default.default {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
singledispatch = false;
|
||||
}
|
||||
if let Some(arg) = ¶meters.vararg {
|
||||
if let Some(expr) = &arg.annotation {
|
||||
@@ -645,23 +678,24 @@ where
|
||||
// available at runtime.
|
||||
// See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements
|
||||
let runtime_annotation = if self.semantic.future_annotations() {
|
||||
if self.semantic.current_scope().kind.is_class() {
|
||||
let baseclasses = &self
|
||||
.settings
|
||||
.flake8_type_checking
|
||||
.runtime_evaluated_base_classes;
|
||||
let decorators = &self
|
||||
.settings
|
||||
.flake8_type_checking
|
||||
.runtime_evaluated_decorators;
|
||||
flake8_type_checking::helpers::runtime_evaluated(
|
||||
baseclasses,
|
||||
decorators,
|
||||
&self.semantic,
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
self.semantic
|
||||
.current_scope()
|
||||
.kind
|
||||
.as_class()
|
||||
.is_some_and(|class_def| {
|
||||
flake8_type_checking::helpers::runtime_evaluated_class(
|
||||
class_def,
|
||||
&self
|
||||
.settings
|
||||
.flake8_type_checking
|
||||
.runtime_evaluated_base_classes,
|
||||
&self
|
||||
.settings
|
||||
.flake8_type_checking
|
||||
.runtime_evaluated_decorators,
|
||||
&self.semantic,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
matches!(
|
||||
self.semantic.current_scope().kind,
|
||||
@@ -769,6 +803,7 @@ where
|
||||
|
||||
self.semantic.flags = flags_snapshot;
|
||||
self.semantic.pop_node();
|
||||
self.last_stmt_end = stmt.end();
|
||||
}
|
||||
|
||||
fn visit_annotation(&mut self, expr: &'b Expr) {
|
||||
@@ -781,7 +816,7 @@ where
|
||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
||||
// Step 0: Pre-processing
|
||||
if !self.semantic.in_f_string()
|
||||
&& !self.semantic.in_literal()
|
||||
&& !self.semantic.in_typing_literal()
|
||||
&& !self.semantic.in_deferred_type_definition()
|
||||
&& self.semantic.in_type_definition()
|
||||
&& self.semantic.future_annotations()
|
||||
@@ -789,7 +824,7 @@ where
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
||||
self.deferred.string_type_definitions.push((
|
||||
expr.range(),
|
||||
value,
|
||||
value.to_str(),
|
||||
self.semantic.snapshot(),
|
||||
));
|
||||
} else {
|
||||
@@ -1014,33 +1049,54 @@ where
|
||||
if let Some(arg) = args.next() {
|
||||
self.visit_non_type_definition(arg);
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
if let Expr::List(ast::ExprList { elts, .. })
|
||||
| Expr::Tuple(ast::ExprTuple { elts, .. }) = arg
|
||||
{
|
||||
for elt in elts {
|
||||
match elt {
|
||||
Expr::List(ast::ExprList { elts, .. })
|
||||
| Expr::Tuple(ast::ExprTuple { elts, .. })
|
||||
if elts.len() == 2 =>
|
||||
{
|
||||
self.visit_non_type_definition(&elts[0]);
|
||||
self.visit_type_definition(&elts[1]);
|
||||
}
|
||||
_ => {
|
||||
self.visit_non_type_definition(elt);
|
||||
match arg {
|
||||
// Ex) NamedTuple("a", [("a", int)])
|
||||
Expr::List(ast::ExprList { elts, .. })
|
||||
| Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
for elt in elts {
|
||||
match elt {
|
||||
Expr::List(ast::ExprList { elts, .. })
|
||||
| Expr::Tuple(ast::ExprTuple { elts, .. })
|
||||
if elts.len() == 2 =>
|
||||
{
|
||||
self.visit_non_type_definition(&elts[0]);
|
||||
self.visit_type_definition(&elts[1]);
|
||||
}
|
||||
_ => {
|
||||
self.visit_non_type_definition(elt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.visit_non_type_definition(arg);
|
||||
_ => self.visit_non_type_definition(arg),
|
||||
}
|
||||
}
|
||||
|
||||
// Ex) NamedTuple("a", a=int)
|
||||
for keyword in keywords {
|
||||
let Keyword { value, .. } = keyword;
|
||||
self.visit_type_definition(value);
|
||||
let Keyword { arg, value, .. } = keyword;
|
||||
match (arg.as_ref(), value) {
|
||||
// Ex) NamedTuple("a", **{"a": int})
|
||||
(None, Expr::Dict(ast::ExprDict { keys, values, .. })) => {
|
||||
for (key, value) in keys.iter().zip(values) {
|
||||
if let Some(key) = key.as_ref() {
|
||||
self.visit_non_type_definition(key);
|
||||
self.visit_type_definition(value);
|
||||
} else {
|
||||
self.visit_non_type_definition(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ex) NamedTuple("a", **obj)
|
||||
(None, _) => {
|
||||
self.visit_non_type_definition(value);
|
||||
}
|
||||
// Ex) NamedTuple("a", a=int)
|
||||
_ => {
|
||||
self.visit_type_definition(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(typing::Callable::TypedDict) => {
|
||||
@@ -1142,7 +1198,7 @@ where
|
||||
) {
|
||||
// Ex) Literal["Class"]
|
||||
Some(typing::SubscriptKind::Literal) => {
|
||||
self.semantic.flags |= SemanticModelFlags::LITERAL;
|
||||
self.semantic.flags |= SemanticModelFlags::TYPING_LITERAL;
|
||||
|
||||
self.visit_expr(slice);
|
||||
self.visit_expr_context(ctx);
|
||||
@@ -1183,12 +1239,12 @@ where
|
||||
}
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
if self.semantic.in_type_definition()
|
||||
&& !self.semantic.in_literal()
|
||||
&& !self.semantic.in_typing_literal()
|
||||
&& !self.semantic.in_f_string()
|
||||
{
|
||||
self.deferred.string_type_definitions.push((
|
||||
expr.range(),
|
||||
value,
|
||||
value.to_str(),
|
||||
self.semantic.snapshot(),
|
||||
));
|
||||
}
|
||||
@@ -1272,9 +1328,9 @@ where
|
||||
|
||||
fn visit_format_spec(&mut self, format_spec: &'b Expr) {
|
||||
match format_spec {
|
||||
Expr::FString(ast::ExprFString { values, .. }) => {
|
||||
for value in values {
|
||||
self.visit_expr(value);
|
||||
Expr::FString(ast::ExprFString { value, .. }) => {
|
||||
for expr in value.elements() {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Unexpected expression for format_spec"),
|
||||
@@ -1921,6 +1977,7 @@ pub(crate) fn check_ast(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&CellOffsets>,
|
||||
) -> Vec<Diagnostic> {
|
||||
let module_path = package.and_then(|package| to_module_path(package, path));
|
||||
let module = Module {
|
||||
@@ -1949,6 +2006,7 @@ pub(crate) fn check_ast(
|
||||
indexer,
|
||||
Importer::new(python_ast, locator, stylist),
|
||||
source_type,
|
||||
cell_offsets,
|
||||
);
|
||||
checker.bind_builtins();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user