Compare commits
191 Commits
charlie/ou
...
malachite
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bd83d36ee | ||
|
|
94b68f201b | ||
|
|
ef34c5cbec | ||
|
|
fdbefd777c | ||
|
|
078547adbb | ||
|
|
42a0bec146 | ||
|
|
37b7d0f921 | ||
|
|
6a4dbd622b | ||
|
|
28b48ab902 | ||
|
|
4123d074bd | ||
|
|
c6ba7dfbc6 | ||
|
|
40f6456add | ||
|
|
3e1dffab20 | ||
|
|
3336d23f48 | ||
|
|
2421805033 | ||
|
|
359f50e6dc | ||
|
|
bccba5d73f | ||
|
|
0bfdb15ecf | ||
|
|
a902d14c31 | ||
|
|
728539291f | ||
|
|
c2bd8af59a | ||
|
|
c946bf157e | ||
|
|
8ab2519717 | ||
|
|
c4d85d6fb6 | ||
|
|
70ea49bf72 | ||
|
|
8e255974bc | ||
|
|
d358604464 | ||
|
|
8243db74fe | ||
|
|
0346e781d4 | ||
|
|
b66bfa6570 | ||
|
|
28273eb00b | ||
|
|
12acd191e1 | ||
|
|
64b929bc29 | ||
|
|
26ae0a6e8d | ||
|
|
959338d39d | ||
|
|
422ff82f4a | ||
|
|
8d0a5e01bd | ||
|
|
aae02cf275 | ||
|
|
7e2eba2592 | ||
|
|
22770fb4be | ||
|
|
1880cceac1 | ||
|
|
0d1fb823d6 | ||
|
|
c907317199 | ||
|
|
916dd5b7fa | ||
|
|
2cbe1733c8 | ||
|
|
1f6e1485f9 | ||
|
|
9b43162cc4 | ||
|
|
cc9e84c144 | ||
|
|
f4d50a2aec | ||
|
|
0c030b5bf3 | ||
|
|
1b082ce67e | ||
|
|
f936d319cc | ||
|
|
85d8b6228f | ||
|
|
7594dadc1d | ||
|
|
de37fbfac9 | ||
|
|
4e2769a16c | ||
|
|
75b5c314e3 | ||
|
|
450fb9b99a | ||
|
|
6163c99551 | ||
|
|
3112202a5b | ||
|
|
64ea00048b | ||
|
|
067a4acd54 | ||
|
|
c88376f468 | ||
|
|
9b7c29853d | ||
|
|
3e21d32b79 | ||
|
|
f9e3ea23ba | ||
|
|
6856d0b44b | ||
|
|
21539f1663 | ||
|
|
b9bb6bf780 | ||
|
|
d39eae2713 | ||
|
|
5d21b9c22e | ||
|
|
ec2f229a45 | ||
|
|
34c1cb7d11 | ||
|
|
2ddea7c657 | ||
|
|
45eabdd2c3 | ||
|
|
675c86c175 | ||
|
|
1f8e2b8f14 | ||
|
|
58b3040342 | ||
|
|
6a9b8aede1 | ||
|
|
f48126ad00 | ||
|
|
11287f944f | ||
|
|
a65efcf459 | ||
|
|
04183b0299 | ||
|
|
36fa1fe359 | ||
|
|
6e625bd93d | ||
|
|
ebd1b296fd | ||
|
|
1373e1c395 | ||
|
|
4bff397318 | ||
|
|
5347df4728 | ||
|
|
ebe9c03545 | ||
|
|
b0cbcd3dfa | ||
|
|
4df9e07a79 | ||
|
|
f0f7ea7502 | ||
|
|
4f26002dd5 | ||
|
|
d1a9c198e3 | ||
|
|
7a4f699fba | ||
|
|
3fb5418c2c | ||
|
|
9fcc009a0c | ||
|
|
bf8e5a167b | ||
|
|
8a001dfc3d | ||
|
|
0823394525 | ||
|
|
e15047815c | ||
|
|
7531bb3b21 | ||
|
|
2d9b39871f | ||
|
|
e122a96d27 | ||
|
|
f4c7bff36b | ||
|
|
56440ad835 | ||
|
|
179128dc54 | ||
|
|
e7a2779402 | ||
|
|
008da95b29 | ||
|
|
5d4dd3e38e | ||
|
|
e561f5783b | ||
|
|
ee0f1270cf | ||
|
|
e7b7e4a18d | ||
|
|
b4419c34ea | ||
|
|
08f19226b9 | ||
|
|
1e6df19a35 | ||
|
|
c21b960fc7 | ||
|
|
73ad2affa1 | ||
|
|
40c936922e | ||
|
|
874db4fb86 | ||
|
|
a41bb2733f | ||
|
|
24b848a4ea | ||
|
|
773ba5f816 | ||
|
|
f5701fcc63 | ||
|
|
ff0feb191c | ||
|
|
6566d00295 | ||
|
|
7c9bbcf4e2 | ||
|
|
babf8d718e | ||
|
|
878813f277 | ||
|
|
71c8a02ebd | ||
|
|
3a2c3a7398 | ||
|
|
7440e54ec6 | ||
|
|
0357e801ed | ||
|
|
69d0caabe7 | ||
|
|
9cb5ce750e | ||
|
|
0a07a2ca62 | ||
|
|
47a253fb62 | ||
|
|
d9544a2d37 | ||
|
|
41f0aad7b3 | ||
|
|
05951dd338 | ||
|
|
c260762900 | ||
|
|
1d5c4b0a14 | ||
|
|
a352f2f092 | ||
|
|
e376c3ff7e | ||
|
|
9671922e40 | ||
|
|
45f9fca228 | ||
|
|
6661be2c30 | ||
|
|
97f945651d | ||
|
|
5cea43731e | ||
|
|
7971e0b0ee | ||
|
|
842ff0212e | ||
|
|
f1a4eb9c28 | ||
|
|
041cdb95e0 | ||
|
|
fda48afc23 | ||
|
|
c1af1c291d | ||
|
|
171b66cb43 | ||
|
|
fa0b6f4813 | ||
|
|
eab85aea1a | ||
|
|
5b31524920 | ||
|
|
6d0469638c | ||
|
|
29ba2bb943 | ||
|
|
f0ea40a68d | ||
|
|
a3a531e0d4 | ||
|
|
b3e8eca871 | ||
|
|
447b7cb0e2 | ||
|
|
e3114a144c | ||
|
|
2e58ad437e | ||
|
|
04f2842e4f | ||
|
|
31990b8d3f | ||
|
|
5f59101811 | ||
|
|
fa6bff0078 | ||
|
|
ea7c394817 | ||
|
|
264d9159f8 | ||
|
|
37dfb205b1 | ||
|
|
f8e4e1d562 | ||
|
|
89be850b73 | ||
|
|
d68041ba24 | ||
|
|
b60b37e866 | ||
|
|
5a95edab45 | ||
|
|
1adde24133 | ||
|
|
e02d76f070 | ||
|
|
7ead2c17b1 | ||
|
|
e428099e4c | ||
|
|
7a83fd9926 | ||
|
|
e8f78fa2cf | ||
|
|
955501f267 | ||
|
|
175b3702c3 | ||
|
|
40ee4909b5 | ||
|
|
10a8e4a225 | ||
|
|
0465b03282 |
@@ -10,7 +10,7 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
|
||||
[*.{rs,py}]
|
||||
[*.{rs,py,pyi}]
|
||||
indent_size = 4
|
||||
|
||||
[*.snap]
|
||||
|
||||
3
.github/release.yml
vendored
3
.github/release.yml
vendored
@@ -20,6 +20,9 @@ changelog:
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
- title: Preview
|
||||
labels:
|
||||
- preview
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
|
||||
30
.github/workflows/ci.yaml
vendored
30
.github/workflows/ci.yaml
vendored
@@ -26,11 +26,11 @@ jobs:
|
||||
linter: ${{ steps.changed.outputs.linter_any_changed }}
|
||||
formatter: ${{ steps.changed.outputs.formatter_any_changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: tj-actions/changed-files@v38
|
||||
- uses: tj-actions/changed-files@v39
|
||||
id: changed
|
||||
with:
|
||||
files_yaml: |
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
name: "cargo fmt"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
- run: cargo fmt --all --check
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
name: "cargo clippy"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: |
|
||||
rustup component add clippy
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: "cargo test | ${{ matrix.os }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo insta"
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: "cargo fuzz"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
@@ -141,7 +141,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: "cargo test (wasm)"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@v3
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
name: "test scripts"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||
if: github.event_name == 'pull_request' && needs.determine_changes.outputs.linter == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -227,7 +227,7 @@ jobs:
|
||||
name: "cargo udeps"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install nightly Rust toolchain"
|
||||
# Only pinned to make caching work, update freely
|
||||
run: rustup toolchain install nightly-2023-06-08
|
||||
@@ -241,7 +241,7 @@ jobs:
|
||||
name: "python package"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -265,7 +265,7 @@ jobs:
|
||||
name: "pre-commit"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -295,7 +295,7 @@ jobs:
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
@@ -330,7 +330,7 @@ jobs:
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Cache rust"
|
||||
@@ -346,7 +346,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
12
.github/workflows/docs.yaml
vendored
12
.github/workflows/docs.yaml
vendored
@@ -2,6 +2,11 @@ name: mkdocs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: "The commit SHA, tag, or branch to publish. Uses the default branch if not specified."
|
||||
default: ""
|
||||
type: string
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
@@ -12,7 +17,9 @@ jobs:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
- uses: actions/setup-python@v4
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
@@ -44,4 +51,5 @@ jobs:
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages publish site --project-name=ruff-docs --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}
|
||||
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
|
||||
command: pages deploy site --project-name=astral-docs --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
14
.github/workflows/flake8-to-ruff.yaml
vendored
14
.github/workflows/flake8-to-ruff.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
macos-universal:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
matrix:
|
||||
target: [x86_64, i686]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
matrix:
|
||||
target: [aarch64, armv7, s390x, ppc64le, ppc64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
- x86_64-unknown-linux-musl
|
||||
- i686-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -196,7 +196,7 @@ jobs:
|
||||
- target: armv7-unknown-linux-musleabihf
|
||||
arch: armv7
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
4
.github/workflows/playground.yaml
vendored
4
.github/workflows/playground.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
env:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@v3
|
||||
@@ -44,4 +44,4 @@ jobs:
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages publish playground/dist --project-name=ruff --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}
|
||||
command: pages deploy playground/dist --project-name=ruff-playground --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
68
.github/workflows/release.yaml
vendored
68
.github/workflows/release.yaml
vendored
@@ -7,12 +7,15 @@ on:
|
||||
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)."
|
||||
type: string
|
||||
sha:
|
||||
description: "Optionally, the full sha of the commit to be released"
|
||||
description: "The full sha of the commit to be released. If omitted, the latest commit on the default branch will be used."
|
||||
default: ""
|
||||
type: string
|
||||
pull_request:
|
||||
paths:
|
||||
# When we change pyproject.toml, we want to ensure that the maturin builds still work
|
||||
- pyproject.toml
|
||||
# And when we change this workflow itself...
|
||||
- .github/workflows/release.yaml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -30,7 +33,9 @@ jobs:
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -56,7 +61,9 @@ jobs:
|
||||
macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -94,7 +101,9 @@ jobs:
|
||||
macos-universal:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -140,7 +149,9 @@ jobs:
|
||||
- target: aarch64-pc-windows-msvc
|
||||
arch: x64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -186,7 +197,9 @@ jobs:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- i686-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -243,7 +256,9 @@ jobs:
|
||||
arch: ppc64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -296,7 +311,9 @@ jobs:
|
||||
- x86_64-unknown-linux-musl
|
||||
- i686-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -350,7 +367,9 @@ jobs:
|
||||
arch: armv7
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -398,9 +417,22 @@ jobs:
|
||||
# If you don't set an input tag, it's a dry run (no uploads).
|
||||
if: ${{ inputs.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main # We checkout the main branch to check for the commit
|
||||
- name: Check main branch
|
||||
if: ${{ inputs.sha }}
|
||||
run: |
|
||||
# Fetch the main branch since a shallow checkout is used by default
|
||||
git fetch origin main --unshallow
|
||||
if ! git branch --contains ${{ inputs.sha }} | grep -E '(^|\s)main$'; then
|
||||
echo "The specified sha is not on the main branch" >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Check tag consistency
|
||||
run: |
|
||||
# Switch to the commit we want to release
|
||||
git checkout ${{ inputs.sha }}
|
||||
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
|
||||
@@ -410,18 +442,6 @@ jobs:
|
||||
else
|
||||
echo "Releasing ${version}"
|
||||
fi
|
||||
- name: Check SHA consistency
|
||||
if: ${{ inputs.sha }}
|
||||
run: |
|
||||
git_sha=$(git rev-parse HEAD)
|
||||
if [ "${{ inputs.sha }}" != "${git_sha}" ]; then
|
||||
echo "The specified sha does not match the git checkout" >&2
|
||||
echo "${{ inputs.sha }}" >&2
|
||||
echo "${git_sha}" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "Releasing ${git_sha}"
|
||||
fi
|
||||
|
||||
upload-release:
|
||||
name: Upload to PyPI
|
||||
@@ -464,7 +484,9 @@ jobs:
|
||||
# For git tag
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- name: git tag
|
||||
run: |
|
||||
git config user.email "hey@astral.sh"
|
||||
|
||||
@@ -23,8 +23,6 @@ repos:
|
||||
- id: mdformat
|
||||
additional_dependencies:
|
||||
- mdformat-mkdocs
|
||||
- mdformat-black
|
||||
- black==23.1.0 # Must be the latest version of Black
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.33.0
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.288
|
||||
|
||||
### Remove support for emoji identifiers ([#7212](https://github.com/astral-sh/ruff/pull/7212))
|
||||
|
||||
Previously, Ruff supported the non-standard compliant emoji identifiers e.g. `📦 = 1`.
|
||||
We decided to remove this non-standard language extension, and Ruff now reports syntax errors for emoji identifiers in your code, the same as CPython.
|
||||
|
||||
### Improved GitLab fingerprints ([#7203](https://github.com/astral-sh/ruff/pull/7203))
|
||||
|
||||
GitLab uses fingerprints to identify new, existing, or fixed violations. Previously, Ruff included the violation's position in the fingerprint. Using the location has the downside that changing any code before the violation causes the fingerprint to change, resulting in GitLab reporting one fixed and one new violation even though it is a pre-existing violation.
|
||||
|
||||
Ruff now uses a more stable location-agnostic fingerprint to minimize that existing violations incorrectly get marked as fixed and re-reported as new violations.
|
||||
|
||||
Expect GitLab to report each pre-existing violation in your project as fixed and a new violation in your Ruff upgrade PR.
|
||||
|
||||
## 0.0.283 / 0.284
|
||||
|
||||
### The target Python version now defaults to 3.8 instead of 3.10 ([#6397](https://github.com/astral-sh/ruff/pull/6397))
|
||||
@@ -284,4 +299,4 @@ default.
|
||||
`pyproject.toml` files are now resolved hierarchically, such that for each Python file, we find
|
||||
the first `pyproject.toml` file in its path, and use that to determine its lint settings.
|
||||
|
||||
See the [documentation](https://beta.ruff.rs/docs/configuration/#python-file-discovery) for more.
|
||||
See the [documentation](https://docs.astral.sh/ruff/configuration/#python-file-discovery) for more.
|
||||
|
||||
@@ -719,8 +719,8 @@ Module {
|
||||
- `cargo dev generate-cli-help`, `cargo dev generate-docs` and `cargo dev generate-json-schema`:
|
||||
Update just `docs/configuration.md`, `docs/rules` and `ruff.schema.json` respectively.
|
||||
- `cargo dev generate-options`: Generate a markdown-compatible table of all `pyproject.toml`
|
||||
options. Used for <https://beta.ruff.rs/docs/settings/>
|
||||
- `cargo dev generate-rules-table`: Generate a markdown-compatible table of all rules. Used for <https://beta.ruff.rs/docs/rules/>
|
||||
options. Used for <https://docs.astral.sh/ruff/settings/>.
|
||||
- `cargo dev generate-rules-table`: Generate a markdown-compatible table of all rules. Used for <https://docs.astral.sh/ruff/rules/>.
|
||||
- `cargo dev round-trip <python file or jupyter notebook>`: Read a Python file or Jupyter Notebook,
|
||||
parse it, serialize the parsed representation and write it back. Used to check how good our
|
||||
representation is so that fixes don't rewrite irrelevant parts of a file.
|
||||
@@ -778,7 +778,7 @@ To understand Ruff's import categorization system, we first need to define two c
|
||||
- "Package root": The top-most directory defining the Python package that includes a given Python
|
||||
file. To find the package root for a given Python file, traverse up its parent directories until
|
||||
you reach a parent directory that doesn't contain an `__init__.py` file (and isn't marked as
|
||||
a [namespace package](https://beta.ruff.rs/docs/settings/#namespace-packages)); take the directory
|
||||
a [namespace package](https://docs.astral.sh/ruff/settings/#namespace-packages)); take the directory
|
||||
just before that, i.e., the first directory in the package.
|
||||
|
||||
For example, given:
|
||||
@@ -867,7 +867,7 @@ There are three ways in which an import can be categorized as "first-party":
|
||||
package (e.g., `from foo import bar` or `import foo.bar`), they'll be classified as first-party
|
||||
automatically. This check is as simple as comparing the first segment of the current file's
|
||||
module path to the first segment of the import.
|
||||
1. **Source roots**: Ruff supports a `[src](https://beta.ruff.rs/docs/settings/#src)` setting, which
|
||||
1. **Source roots**: Ruff supports a `[src](https://docs.astral.sh/ruff/settings/#src)` setting, which
|
||||
sets the directories to scan when identifying first-party imports. The algorithm is
|
||||
straightforward: given an import, like `import foo`, iterate over the directories enumerated in
|
||||
the `src` setting and, for each directory, check for the existence of a subdirectory `foo` or a
|
||||
|
||||
585
Cargo.lock
generated
585
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
42
Cargo.toml
42
Cargo.toml
@@ -5,8 +5,8 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
rust-version = "1.71"
|
||||
homepage = "https://beta.ruff.rs/docs"
|
||||
documentation = "https://beta.ruff.rs/docs"
|
||||
homepage = "https://docs.astral.sh/ruff"
|
||||
documentation = "https://docs.astral.sh/ruff"
|
||||
repository = "https://github.com/astral-sh/ruff"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
license = "MIT"
|
||||
@@ -14,47 +14,49 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.69" }
|
||||
bitflags = { version = "2.3.1" }
|
||||
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.1.8", features = ["derive"] }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.4", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
filetime = { version = "0.2.20" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.10" }
|
||||
ignore = { version = "0.4.20" }
|
||||
insta = { version = "1.31.0", feature = ["filters", "glob"] }
|
||||
is-macro = { version = "0.2.2" }
|
||||
itertools = { version = "0.10.5" }
|
||||
is-macro = { version = "0.3.0" }
|
||||
itertools = { version = "0.11.0" }
|
||||
log = { version = "0.4.17" }
|
||||
memchr = "2.5.0"
|
||||
num-bigint = { version = "0.4.3" }
|
||||
malachite = { version = "0.4.0", default-features = false, features = ["naturals_and_integers"] }
|
||||
memchr = "2.6.3"
|
||||
num-traits = { version = "0.2.15" }
|
||||
once_cell = { version = "1.17.1" }
|
||||
path-absolutize = { version = "3.0.14" }
|
||||
proc-macro2 = { version = "1.0.51" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
proc-macro2 = { version = "1.0.67" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.7.1" }
|
||||
regex = { version = "1.9.5" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.12" }
|
||||
schemars = { version = "0.8.15" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.93" }
|
||||
serde_json = { version = "1.0.107" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.2.1", features = ["inline"] }
|
||||
smallvec = { version = "1.10.0" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
syn = { version = "2.0.15" }
|
||||
test-case = { version = "3.0.0" }
|
||||
thiserror = { version = "1.0.43" }
|
||||
toml = { version = "0.7.2" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.2" }
|
||||
syn = { version = "2.0.37" }
|
||||
test-case = { version = "3.2.1" }
|
||||
thiserror = { version = "1.0.48" }
|
||||
toml = { version = "0.7.8" }
|
||||
tracing = "0.1.37"
|
||||
tracing-indicatif = "0.3.4"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
unicode-ident = "1.0.12"
|
||||
unicode-width = "0.1.10"
|
||||
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
# v1.0.1
|
||||
libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "9c263aa8977962a870ce2770d2aa18ee0dacb344", default-features = false }
|
||||
libcst = { version = "0.1.0", default-features = false }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
25
LICENSE
25
LICENSE
@@ -1224,6 +1224,31 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-logging, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Adam Johnson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- Pyright, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
27
README.md
27
README.md
@@ -8,7 +8,7 @@
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://github.com/astral-sh/ruff/actions)
|
||||
|
||||
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://beta.ruff.rs/docs/) | [**Playground**](https://play.ruff.rs/)
|
||||
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
|
||||
|
||||
An extremely fast Python linter, written in Rust.
|
||||
|
||||
@@ -30,13 +30,13 @@ An extremely fast Python linter, written in Rust.
|
||||
- 🤝 Python 3.11 compatibility
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 📏 Over [600 built-in rules](https://beta.ruff.rs/docs/rules/)
|
||||
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
|
||||
- 📏 Over [600 built-in rules](https://docs.astral.sh/ruff/rules/)
|
||||
- ⚖️ [Near-parity](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8) with the
|
||||
built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
||||
- ⌨️ First-party [editor integrations](https://beta.ruff.rs/docs/editor-integrations/) for
|
||||
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/editor-integrations/) for
|
||||
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
|
||||
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
|
||||
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://docs.astral.sh/ruff/configuration/#pyprojecttoml-discovery)
|
||||
|
||||
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
|
||||
functionality behind a single, common interface.
|
||||
@@ -98,7 +98,7 @@ developer of [Zulip](https://github.com/zulip/zulip):
|
||||
|
||||
## Table of Contents
|
||||
|
||||
For more, see the [documentation](https://beta.ruff.rs/docs/).
|
||||
For more, see the [documentation](https://docs.astral.sh/ruff/).
|
||||
|
||||
1. [Getting Started](#getting-started)
|
||||
1. [Configuration](#configuration)
|
||||
@@ -111,7 +111,7 @@ For more, see the [documentation](https://beta.ruff.rs/docs/).
|
||||
|
||||
## Getting Started
|
||||
|
||||
For more, see the [documentation](https://beta.ruff.rs/docs/).
|
||||
For more, see the [documentation](https://docs.astral.sh/ruff/).
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -122,7 +122,7 @@ pip install ruff
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
and with [a variety of other package managers](https://beta.ruff.rs/docs/installation/).
|
||||
and with [a variety of other package managers](https://docs.astral.sh/ruff/installation/).
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.287
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -165,7 +165,7 @@ jobs:
|
||||
### Configuration
|
||||
|
||||
Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (see:
|
||||
[_Configuration_](https://beta.ruff.rs/docs/configuration/), or [_Settings_](https://beta.ruff.rs/docs/settings/)
|
||||
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
|
||||
for a complete list of all configuration options).
|
||||
|
||||
If left unspecified, the default configuration is equivalent to:
|
||||
@@ -238,7 +238,7 @@ isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implement
|
||||
Rust as a first-party feature.
|
||||
|
||||
By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category,
|
||||
and a [subset](https://beta.ruff.rs/docs/rules/#error-e) of the `E` category, omitting those
|
||||
and a [subset](https://docs.astral.sh/ruff/rules/#error-e) of the `E` category, omitting those
|
||||
stylistic rules made obsolete by the use of an autoformatter, like
|
||||
[Black](https://github.com/psf/black).
|
||||
|
||||
@@ -274,6 +274,7 @@ quality tools, including:
|
||||
- [flake8-gettext](https://pypi.org/project/flake8-gettext/)
|
||||
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
|
||||
- [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
- [flake8-logging](https://pypi.org/project/flake8-logging/)
|
||||
- [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
|
||||
- [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
|
||||
- [flake8-pie](https://pypi.org/project/flake8-pie/)
|
||||
@@ -303,12 +304,12 @@ quality tools, including:
|
||||
- [tryceratops](https://pypi.org/project/tryceratops/)
|
||||
- [yesqa](https://pypi.org/project/yesqa/)
|
||||
|
||||
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
|
||||
For a complete enumeration of the supported rules, see [_Rules_](https://docs.astral.sh/ruff/rules/).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome and highly appreciated. To get started, check out the
|
||||
[**contributing guidelines**](https://beta.ruff.rs/docs/contributing/).
|
||||
[**contributing guidelines**](https://docs.astral.sh/ruff/contributing/).
|
||||
|
||||
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.287"
|
||||
version = "0.0.290"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -86,7 +86,7 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
|
||||
configuration options that don't exist in Flake8.)
|
||||
1. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
|
||||
codes from unsupported plugins. (See the
|
||||
[documentation](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) for the complete
|
||||
[documentation](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8) for the complete
|
||||
list of supported plugins.)
|
||||
|
||||
## License
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use ruff::registry::Linter;
|
||||
use ruff::settings::types::PreviewMode;
|
||||
use ruff::RuleSelector;
|
||||
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
@@ -331,7 +332,7 @@ pub(crate) fn infer_plugins_from_codes(selectors: &HashSet<RuleSelector>) -> Vec
|
||||
.filter(|plugin| {
|
||||
for selector in selectors {
|
||||
if selector
|
||||
.into_iter()
|
||||
.rules(PreviewMode::Disabled)
|
||||
.any(|rule| Linter::from(plugin).rules().any(|r| r == rule))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.287"
|
||||
version = "0.0.290"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -45,9 +45,9 @@ is-macro = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
libcst = { workspace = true }
|
||||
log = { workspace = true }
|
||||
malachite = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
natord = { version = "1.0.9" }
|
||||
num-bigint = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = [
|
||||
@@ -56,7 +56,7 @@ path-absolutize = { workspace = true, features = [
|
||||
] }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.3.1", features = ["serde"] }
|
||||
pyproject-toml = { version = "0.6.0" }
|
||||
pyproject-toml = { version = "0.7.0" }
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
|
||||
22
crates/ruff/resources/test/fixtures/flake8_bandit/S201.py
vendored
Normal file
22
crates/ruff/resources/test/fixtures/flake8_bandit/S201.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def main():
|
||||
raise
|
||||
|
||||
# OK
|
||||
app.run(debug=True)
|
||||
|
||||
# Errors
|
||||
app.run()
|
||||
app.run(debug=False)
|
||||
|
||||
# Unrelated
|
||||
run()
|
||||
run(debug=True)
|
||||
run(debug)
|
||||
foo.run(debug=True)
|
||||
app = 1
|
||||
app.run(debug=True)
|
||||
5
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_1.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_1.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Docstring followed by a newline
|
||||
|
||||
def foobar(foor, bar={}):
|
||||
"""
|
||||
"""
|
||||
6
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_2.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_2.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Docstring followed by whitespace with no newline
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/7155
|
||||
|
||||
def foobar(foor, bar={}):
|
||||
"""
|
||||
"""
|
||||
6
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_3.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_3.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Docstring with no newline
|
||||
|
||||
|
||||
def foobar(foor, bar={}):
|
||||
"""
|
||||
"""
|
||||
9
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_4.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_4.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# formfeed indent
|
||||
# https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458825
|
||||
# This is technically a stylist bug (and has a test there), but it surfaced in B006
|
||||
|
||||
|
||||
class FormFeedIndent:
|
||||
def __init__(self, a=[]):
|
||||
print(a)
|
||||
|
||||
@@ -53,3 +53,6 @@ setattr(foo, "__123abc__", None)
|
||||
setattr(foo, "abc123", None)
|
||||
setattr(foo, r"abc123", None)
|
||||
setattr(foo.bar, r"baz", None)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458885
|
||||
assert getattr(func, '_rpc')is True
|
||||
|
||||
@@ -10,3 +10,5 @@ except (ImportError, TypeError):
|
||||
pass
|
||||
except (*retriable_exceptions,):
|
||||
pass
|
||||
except(ValueError,):
|
||||
pass
|
||||
|
||||
@@ -16,3 +16,9 @@ def f(x):
|
||||
return x
|
||||
|
||||
print(f'Hello {dict((x,f(x)) for x in "abc")} World')
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7086
|
||||
dict((k,v)for k,v in d.iteritems() if k in only_args)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458940
|
||||
dict((*v, k) for k, v in enumerate(calendar.month_abbr))
|
||||
|
||||
@@ -11,3 +11,6 @@ f"{dict([(s,f(s)) for s in 'ab'])}"
|
||||
|
||||
f'{dict([(s,s) for s in "ab"]) | dict([(s,s) for s in "ab"])}'
|
||||
f'{ dict([(s,s) for s in "ab"]) | dict([(s,s) for s in "ab"]) }'
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7087
|
||||
saved.append(dict([(k, v)for k,v in list(unique_instance.__dict__.items()) if k in [f.name for f in unique_instance._meta.fields]]))
|
||||
|
||||
@@ -7,6 +7,13 @@ reversed(sorted(x, reverse=True))
|
||||
reversed(sorted(x, key=lambda e: e, reverse=True))
|
||||
reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
reversed(sorted(x, reverse=False))
|
||||
reversed(sorted(x, reverse=x))
|
||||
reversed(sorted(x, reverse=not x))
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
reversed(sorted(i for i in range(42)))
|
||||
reversed(sorted((i for i in range(42)), reverse=True))
|
||||
|
||||
|
||||
def reversed(*args, **kwargs):
|
||||
return None
|
||||
|
||||
@@ -7,6 +7,8 @@ d = {"a": 1, "b": 2, "c": 3}
|
||||
{i for i in x}
|
||||
{k: v for k, v in y}
|
||||
{k: v for k, v in d.items()}
|
||||
[(k, v) for k, v in d.items()]
|
||||
{k: (a, b) for k, (a, b) in d.items()}
|
||||
|
||||
[i for i, in z]
|
||||
[i for i, j in y]
|
||||
@@ -17,3 +19,6 @@ d = {"a": 1, "b": 2, "c": 3}
|
||||
{k.foo: k for k in y}
|
||||
{k["foo"]: k for k in y}
|
||||
{k: v if v else None for k, v in y}
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
|
||||
@@ -11,6 +11,7 @@ map(lambda _: 3.0, nums)
|
||||
_ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
all(map(lambda v: isinstance(v, dict), nums))
|
||||
filter(func, map(lambda v: v, nums))
|
||||
list(map(lambda x, y: x * y, nums))
|
||||
|
||||
# When inside f-string, then the fix should be surrounded by whitespace
|
||||
_ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
@@ -41,3 +42,8 @@ map(lambda **kwargs: len(kwargs), range(4))
|
||||
|
||||
# Ok because multiple arguments are allowed.
|
||||
dict(map(lambda k, v: (k, v), keys, values))
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7121
|
||||
map(lambda x: x, y if y else z)
|
||||
map(lambda x: x, (y if y else z))
|
||||
map(lambda x: x, (x, y, z))
|
||||
|
||||
5
crates/ruff/resources/test/fixtures/flake8_logging/LOG001.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/flake8_logging/LOG001.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import logging
|
||||
|
||||
logging.Logger(__name__)
|
||||
logging.Logger()
|
||||
logging.getLogger(__name__)
|
||||
24
crates/ruff/resources/test/fixtures/flake8_logging/LOG002.py
vendored
Normal file
24
crates/ruff/resources/test/fixtures/flake8_logging/LOG002.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import logging
|
||||
from logging import getLogger
|
||||
|
||||
# Ok
|
||||
logging.getLogger(__name__)
|
||||
logging.getLogger(name=__name__)
|
||||
logging.getLogger("custom")
|
||||
logging.getLogger(name="custom")
|
||||
|
||||
# LOG002
|
||||
getLogger(__file__)
|
||||
logging.getLogger(name=__file__)
|
||||
|
||||
logging.getLogger(__cached__)
|
||||
getLogger(name=__cached__)
|
||||
|
||||
|
||||
# Override `logging.getLogger`
|
||||
class logging:
|
||||
def getLogger(self):
|
||||
pass
|
||||
|
||||
|
||||
logging.getLogger(__file__)
|
||||
16
crates/ruff/resources/test/fixtures/flake8_logging/LOG007.py
vendored
Normal file
16
crates/ruff/resources/test/fixtures/flake8_logging/LOG007.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
logging.exception("foo") # OK
|
||||
logging.exception("foo", exc_info=False) # LOG007
|
||||
logging.exception("foo", exc_info=[]) # LOG007
|
||||
logger.exception("foo") # OK
|
||||
logger.exception("foo", exc_info=False) # LOG007
|
||||
logger.exception("foo", exc_info=[]) # LOG007
|
||||
|
||||
|
||||
from logging import exception
|
||||
|
||||
exception("foo", exc_info=False) # LOG007
|
||||
exception("foo", exc_info=True) # OK
|
||||
9
crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import logging
|
||||
|
||||
logging.WARN # LOG009
|
||||
logging.WARNING # OK
|
||||
|
||||
from logging import WARN, WARNING
|
||||
|
||||
WARN # LOG009
|
||||
WARNING # OK
|
||||
@@ -92,3 +92,9 @@ class Test(unittest.TestCase):
|
||||
|
||||
def test_fail_if_equal(self):
|
||||
self.failIfEqual(1, 2) # Error
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459517
|
||||
(self.assertTrue(
|
||||
"piAx_piAy_beta[r][x][y] = {17}".format(
|
||||
self.model.piAx_piAy_beta[r][x][y])))
|
||||
|
||||
@@ -52,3 +52,21 @@ def test_multiline():
|
||||
|
||||
x = 1; \
|
||||
assert something and something_else
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7143
|
||||
def test_parenthesized_not():
|
||||
assert not (
|
||||
self.find_graph_output(node.output[0])
|
||||
or self.find_graph_input(node.input[0])
|
||||
or self.find_graph_output(node.input[0])
|
||||
)
|
||||
|
||||
assert (not (
|
||||
self.find_graph_output(node.output[0])
|
||||
or self.find_graph_input(node.input[0])
|
||||
or self.find_graph_output(node.input[0])
|
||||
))
|
||||
|
||||
assert (not self.find_graph_output(node.output[0]) or
|
||||
self.find_graph_input(node.input[0]))
|
||||
|
||||
10
crates/ruff/resources/test/fixtures/flake8_self/SLF001_extended.py
vendored
Normal file
10
crates/ruff/resources/test/fixtures/flake8_self/SLF001_extended.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
def foo(obj):
|
||||
obj._meta # OK
|
||||
|
||||
|
||||
def foo(obj):
|
||||
obj._asdict # SLF001
|
||||
|
||||
|
||||
def foo(obj):
|
||||
obj._bar # SLF001
|
||||
@@ -8,6 +8,7 @@ try:
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
# SIM105
|
||||
try:
|
||||
foo()
|
||||
@@ -110,3 +111,20 @@ try:
|
||||
print()
|
||||
except "not an exception":
|
||||
pass
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7123
|
||||
def write_models(directory, Models):
|
||||
try:
|
||||
os.makedirs(model_dir);
|
||||
except OSError:
|
||||
pass;
|
||||
|
||||
try: os.makedirs(model_dir);
|
||||
except OSError:
|
||||
pass;
|
||||
|
||||
try: os.makedirs(model_dir);
|
||||
except OSError:
|
||||
pass; \
|
||||
\
|
||||
#
|
||||
|
||||
@@ -42,3 +42,17 @@ class Foo:
|
||||
|
||||
def __contains__(self, key: object) -> bool:
|
||||
return key in self.keys() # OK
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
key in obj.keys()and foo
|
||||
(key in obj.keys())and foo
|
||||
key in (obj.keys())and foo
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
for key in (
|
||||
self.experiment.surveys[0]
|
||||
.stations[0]
|
||||
.keys()
|
||||
):
|
||||
continue
|
||||
|
||||
@@ -14,6 +14,8 @@ YODA >= age # SIM300
|
||||
JediOrder.YODA == age # SIM300
|
||||
0 < (number - 100) # SIM300
|
||||
SomeClass().settings.SOME_CONSTANT_VALUE > (60 * 60) # SIM300
|
||||
B<A[0][0]or B
|
||||
B or(B)<A[0][0]
|
||||
|
||||
# OK
|
||||
compare == "yoda"
|
||||
|
||||
@@ -16,3 +16,8 @@ nok4 = "a".join([a, a, *a]) # Not OK (not a static length)
|
||||
nok5 = "a".join([choice("flarp")]) # Not OK (not a simple call)
|
||||
nok6 = "a".join(x for x in "feefoofum") # Not OK (generator)
|
||||
nok7 = "a".join([f"foo{8}", "bar"]) # Not OK (contains an f-string)
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
||||
def create_file_public_url(url, filename):
|
||||
return''.join([url, filename])
|
||||
|
||||
@@ -18,3 +18,8 @@ pdf = pd.DataFrame(
|
||||
)
|
||||
|
||||
_ = arr.astype(np.int)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/6952
|
||||
from numpy import float
|
||||
|
||||
float(1)
|
||||
|
||||
@@ -41,9 +41,13 @@ class Test(unittest.TestCase):
|
||||
assert True
|
||||
|
||||
|
||||
from typing import override
|
||||
from typing import override, overload
|
||||
|
||||
|
||||
@override
|
||||
def BAD_FUNC():
|
||||
pass
|
||||
|
||||
@overload
|
||||
def BAD_FUNC():
|
||||
pass
|
||||
|
||||
83
crates/ruff/resources/test/fixtures/perflint/PERF403.py
vendored
Normal file
83
crates/ruff/resources/test/fixtures/perflint/PERF403.py
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # Ok (false negative: edge case where `else` is same as `if`)
|
||||
else:
|
||||
result[idx] = name
|
||||
|
||||
|
||||
def foo():
|
||||
result = {}
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = []
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # OK (result is not a dictionary)
|
||||
else:
|
||||
result[idx] = name
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # OK (if/elif/else isn't replaceable)
|
||||
elif idx % 3:
|
||||
result[idx] = name
|
||||
else:
|
||||
result[idx] = name
|
||||
|
||||
|
||||
def foo():
|
||||
result = {1: "banana"}
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx in result:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for name in fruit:
|
||||
result[name] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
result[name] = idx # PERF403
|
||||
@@ -37,3 +37,4 @@ assert (not foo) in bar
|
||||
assert {"x": not foo} in bar
|
||||
assert [42, not foo] in bar
|
||||
assert not (re.search(r"^.:\\Users\\[^\\]*\\Downloads\\.*") is None)
|
||||
assert not('name' in request)or not request['name']
|
||||
|
||||
@@ -138,3 +138,12 @@ def scope():
|
||||
class TemperatureScales(Enum):
|
||||
CELSIUS = (lambda deg_c: deg_c)
|
||||
FAHRENHEIT = (lambda deg_c: deg_c * 9 / 5 + 32)
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7141
|
||||
def scope():
|
||||
# E731
|
||||
|
||||
f = lambda: (
|
||||
i := 1,
|
||||
)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
'''File starts with a tab
|
||||
multiline string with tab in it'''
|
||||
|
||||
#: W191
|
||||
if False:
|
||||
print # indented with 1 tab
|
||||
|
||||
@@ -639,3 +639,27 @@ def starts_with_space_then_this():
|
||||
class SameLine: """This is a docstring on the same line"""
|
||||
|
||||
def same_line(): """This is a docstring on the same line"""
|
||||
|
||||
|
||||
def single_line_docstring_with_an_escaped_backslash():
|
||||
"\
|
||||
"
|
||||
|
||||
class StatementOnSameLineAsDocstring:
|
||||
"After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
|
||||
def sort_services(self):
|
||||
pass
|
||||
|
||||
class StatementOnSameLineAsDocstring:
|
||||
"After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
|
||||
|
||||
|
||||
class CommentAfterDocstring:
|
||||
"After this docstring there's a comment." # priorities=1
|
||||
def sort_services(self):
|
||||
pass
|
||||
|
||||
|
||||
def newline_after_closing_quote(self):
|
||||
"We enforce a newline after the closing quote for a multi-line docstring \
|
||||
but continuations shouldn't be considered multi-line"
|
||||
|
||||
@@ -128,6 +128,19 @@ def f(x, *, y, z):
|
||||
"""
|
||||
return x, y, z
|
||||
|
||||
def f(x):
|
||||
"""Do something with valid description.
|
||||
|
||||
Args:
|
||||
----
|
||||
x: the value
|
||||
|
||||
Returns:
|
||||
-------
|
||||
the value
|
||||
"""
|
||||
return x
|
||||
|
||||
|
||||
class Test:
|
||||
def f(self, /, arg1: int) -> None:
|
||||
|
||||
@@ -529,3 +529,16 @@ def replace_equals_with_dash2():
|
||||
Parameters
|
||||
===========
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
def non_empty_blank_line_before_section(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
The function's description.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
@@ -112,3 +112,11 @@ match *0, 1, *2:
|
||||
import b1
|
||||
|
||||
import b2
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7244
|
||||
from datameta_client_lib.model_utils import ( # noqa: F401
|
||||
noqa )
|
||||
|
||||
from datameta_client_lib.model_helpers import (
|
||||
noqa )
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from typing import override
|
||||
|
||||
|
||||
class Apples:
|
||||
def _init_(self): # [bad-dunder-name]
|
||||
pass
|
||||
@@ -21,6 +24,11 @@ class Apples:
|
||||
# author likely meant to call the invert dunder method
|
||||
pass
|
||||
|
||||
@override
|
||||
def _ignore__(self): # [bad-dunder-name]
|
||||
# overridden dunder methods should be ignored
|
||||
pass
|
||||
|
||||
def hello(self):
|
||||
print("hello")
|
||||
|
||||
@@ -41,6 +49,20 @@ class Apples:
|
||||
def __doc__(self):
|
||||
return "Docstring"
|
||||
|
||||
# Allow dunder methods recommended by attrs.
|
||||
def __attrs_post_init__(self):
|
||||
pass
|
||||
|
||||
def __attrs_pre_init__(self):
|
||||
pass
|
||||
|
||||
def __attrs_init__(self):
|
||||
pass
|
||||
|
||||
# Allow __html__, used by Jinja2 and Django.
|
||||
def __html__(self):
|
||||
pass
|
||||
|
||||
|
||||
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
|
||||
...
|
||||
|
||||
@@ -36,3 +36,6 @@ tuples_list = [
|
||||
|
||||
min(min(tuples_list))
|
||||
max(max(tuples_list))
|
||||
|
||||
# Starred argument should be copied as it is.
|
||||
max(1, max(*a))
|
||||
|
||||
@@ -36,3 +36,8 @@ def isinstances():
|
||||
result = isinstance(var[6], int) or isinstance(var[7], int)
|
||||
result = isinstance(var[6], (float, int)) or False
|
||||
return result
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722460483
|
||||
if(isinstance(self.k, int)) or (isinstance(self.k, float)):
|
||||
...
|
||||
|
||||
60
crates/ruff/resources/test/fixtures/pylint/too_many_public_methods.py
vendored
Normal file
60
crates/ruff/resources/test/fixtures/pylint/too_many_public_methods.py
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
class Everything:
|
||||
foo = 1
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _private(self):
|
||||
pass
|
||||
|
||||
def method1(self):
|
||||
pass
|
||||
|
||||
def method2(self):
|
||||
pass
|
||||
|
||||
def method3(self):
|
||||
pass
|
||||
|
||||
def method4(self):
|
||||
pass
|
||||
|
||||
def method5(self):
|
||||
pass
|
||||
|
||||
def method6(self):
|
||||
pass
|
||||
|
||||
def method7(self):
|
||||
pass
|
||||
|
||||
def method8(self):
|
||||
pass
|
||||
|
||||
def method9(self):
|
||||
pass
|
||||
|
||||
class Small:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _private(self):
|
||||
pass
|
||||
|
||||
def method1(self):
|
||||
pass
|
||||
|
||||
def method2(self):
|
||||
pass
|
||||
|
||||
def method3(self):
|
||||
pass
|
||||
|
||||
def method4(self):
|
||||
pass
|
||||
|
||||
def method5(self):
|
||||
pass
|
||||
|
||||
def method6(self):
|
||||
pass
|
||||
@@ -10,6 +10,5 @@ type(arg)(" ")
|
||||
# OK
|
||||
y = x.dtype.type(0.0)
|
||||
|
||||
# OK
|
||||
type = lambda *args, **kwargs: None
|
||||
type("")
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459841
|
||||
assert isinstance(fullname, type("")is not True)
|
||||
|
||||
@@ -95,3 +95,22 @@ def f(x: Optional[str, int : float]) -> None:
|
||||
|
||||
def f(x: Optional[int, float]) -> None:
|
||||
...
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7131
|
||||
class ServiceRefOrValue:
|
||||
service_specification: Optional[
|
||||
list[ServiceSpecificationRef]
|
||||
| list[ServiceSpecification]
|
||||
] = None
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7201
|
||||
class ServiceRefOrValue:
|
||||
service_specification: Optional[str]is not True = None
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7452
|
||||
class Collection(Protocol[*_B0]):
|
||||
def __iter__(self) -> Iterator[Union[*_B0]]:
|
||||
...
|
||||
|
||||
@@ -75,3 +75,8 @@ print("foo".encode()) # print(b"foo")
|
||||
(f"foo{bar}").encode(encoding="utf-8")
|
||||
("unicode text©").encode("utf-8")
|
||||
("unicode text©").encode(encoding="utf-8")
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459882
|
||||
def _match_ignore(line):
|
||||
input=stdin and'\n'.encode()or None
|
||||
|
||||
@@ -43,7 +43,11 @@ output = subprocess.run(
|
||||
["foo"], stdout=subprocess.PIPE, capture_output=False, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
# Examples that should NOT trigger the rule
|
||||
output = subprocess.run(
|
||||
["foo"], capture_output=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
# OK
|
||||
from foo import PIPE
|
||||
subprocess.run(["foo"], stdout=PIPE, stderr=PIPE)
|
||||
run(["foo"], stdout=None, stderr=PIPE)
|
||||
|
||||
@@ -96,3 +96,11 @@ try:
|
||||
pass
|
||||
except (OSError, KeyError):
|
||||
pass
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7101
|
||||
def get_owner_id_from_mac_address():
|
||||
try:
|
||||
mac_address = get_primary_mac_address()
|
||||
except(IOError, OSError) as ex:
|
||||
msg = 'Unable to query URL to get Owner ID: {u}\n{e}'.format(u=owner_id_url, e=ex)
|
||||
|
||||
@@ -74,6 +74,8 @@ from typing import Collection
|
||||
from typing import AsyncGenerator
|
||||
from typing import Reversible
|
||||
from typing import Generator
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
|
||||
# OK
|
||||
from a import b
|
||||
|
||||
@@ -178,3 +178,9 @@ if True:
|
||||
if True:
|
||||
if sys.version_info > (3, 0): \
|
||||
expected_error = []
|
||||
|
||||
if sys.version_info < (3,12):
|
||||
print("py3")
|
||||
|
||||
if sys.version_info <= (3,12):
|
||||
print("py3")
|
||||
|
||||
@@ -13,18 +13,19 @@ x: typing.TypeAlias = list[T]
|
||||
T = typing.TypeVar("T")
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 bounded generic (todo)
|
||||
# UP040 bounded generic
|
||||
T = typing.TypeVar("T", bound=int)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 constrained generic
|
||||
T = typing.TypeVar("T", int, str)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 contravariant generic (todo)
|
||||
# UP040 contravariant generic
|
||||
T = typing.TypeVar("T", contravariant=True)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 covariant generic (todo)
|
||||
# UP040 covariant generic
|
||||
T = typing.TypeVar("T", covariant=True)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
|
||||
43
crates/ruff/resources/test/fixtures/refurb/FURB140.py
vendored
Normal file
43
crates/ruff/resources/test/fixtures/refurb/FURB140.py
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
def zipped():
|
||||
return zip([1, 2, 3], "ABC")
|
||||
|
||||
# Errors.
|
||||
|
||||
# FURB140
|
||||
[print(x, y) for x, y in zipped()]
|
||||
|
||||
# FURB140
|
||||
(print(x, y) for x, y in zipped())
|
||||
|
||||
# FURB140
|
||||
{print(x, y) for x, y in zipped()}
|
||||
|
||||
|
||||
from itertools import starmap as sm
|
||||
|
||||
# FURB140
|
||||
[print(x, y) for x, y in zipped()]
|
||||
|
||||
# FURB140
|
||||
(print(x, y) for x, y in zipped())
|
||||
|
||||
# FURB140
|
||||
{print(x, y) for x, y in zipped()}
|
||||
|
||||
# Non-errors.
|
||||
|
||||
[print(x, int) for x, _ in zipped()]
|
||||
|
||||
[print(x, *y) for x, y in zipped()]
|
||||
|
||||
[print(x, y, 1) for x, y in zipped()]
|
||||
|
||||
[print(y, x) for x, y in zipped()]
|
||||
|
||||
[print(x + 1, y) for x, y in zipped()]
|
||||
|
||||
[print(x) for x in range(100)]
|
||||
|
||||
[print() for x, y in zipped()]
|
||||
|
||||
[print(x, end=y) for x, y in zipped()]
|
||||
21
crates/ruff/resources/test/fixtures/refurb/FURB145.py
vendored
Normal file
21
crates/ruff/resources/test/fixtures/refurb/FURB145.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
l = [1, 2, 3, 4, 5]
|
||||
|
||||
# Errors.
|
||||
a = l[:]
|
||||
b, c = 1, l[:]
|
||||
d, e = l[:], 1
|
||||
m = l[::]
|
||||
l[:]
|
||||
print(l[:])
|
||||
|
||||
# False negatives.
|
||||
aa = a[:] # Type inference.
|
||||
|
||||
# OK.
|
||||
t = (1, 2, 3, 4, 5)
|
||||
f = t[:] # t.copy() is not supported.
|
||||
g = l[1:3]
|
||||
h = l[1:]
|
||||
i = l[:3]
|
||||
j = l[1:3:2]
|
||||
k = l[::2]
|
||||
@@ -3,14 +3,14 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_trivia::{
|
||||
has_leading_content, is_python_whitespace, PythonWhitespace, SimpleTokenKind, SimpleTokenizer,
|
||||
};
|
||||
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
|
||||
use ruff_text_size::{Ranged, TextLen, TextSize};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::autofix::codemods;
|
||||
|
||||
@@ -92,10 +92,8 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
) -> Result<Edit> {
|
||||
// Partition into arguments before and after the argument to remove.
|
||||
let (before, after): (Vec<_>, Vec<_>) = arguments
|
||||
.args
|
||||
.iter()
|
||||
.map(Expr::range)
|
||||
.chain(arguments.keywords.iter().map(Keyword::range))
|
||||
.arguments_source_order()
|
||||
.map(|arg| arg.range())
|
||||
.filter(|range| argument.range() != *range)
|
||||
.partition(|range| range.start() < argument.start());
|
||||
|
||||
@@ -249,6 +247,44 @@ fn next_stmt_break(semicolon: TextSize, locator: &Locator) -> TextSize {
|
||||
locator.line_end(start_location)
|
||||
}
|
||||
|
||||
/// Add leading whitespace to a snippet, if it's immediately preceded an identifier or keyword.
|
||||
pub(crate) fn pad_start(mut content: String, start: TextSize, locator: &Locator) -> String {
|
||||
// Ex) When converting `except(ValueError,)` from a tuple to a single argument, we need to
|
||||
// insert a space before the fix, to achieve `except ValueError`.
|
||||
if locator
|
||||
.up_to(start)
|
||||
.chars()
|
||||
.last()
|
||||
.is_some_and(|char| char.is_ascii_alphabetic())
|
||||
{
|
||||
content.insert(0, ' ');
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
/// Add trailing whitespace to a snippet, if it's immediately followed by an identifier or keyword.
|
||||
pub(crate) fn pad_end(mut content: String, end: TextSize, locator: &Locator) -> String {
|
||||
if locator
|
||||
.after(end)
|
||||
.chars()
|
||||
.next()
|
||||
.is_some_and(|char| char.is_ascii_alphabetic())
|
||||
{
|
||||
content.push(' ');
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
/// Add leading or trailing whitespace to a snippet, if it's immediately preceded or followed by
|
||||
/// an identifier or keyword.
|
||||
pub(crate) fn pad(content: String, range: TextRange, locator: &Locator) -> String {
|
||||
pad_start(
|
||||
pad_end(content, range.end(), locator),
|
||||
range.start(),
|
||||
locator,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -13,10 +13,10 @@ use crate::registry::Rule;
|
||||
use crate::rules::{
|
||||
flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins,
|
||||
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
|
||||
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging_format,
|
||||
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, flake8_simplify,
|
||||
flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle,
|
||||
pyflakes, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
|
||||
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
|
||||
flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet,
|
||||
pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
@@ -113,10 +113,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryIterableAllocationForFirstElement) {
|
||||
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, subscript);
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::InvalidIndexType) {
|
||||
ruff::rules::invalid_index_type(checker, subscript);
|
||||
}
|
||||
if checker.settings.rules.enabled(Rule::SliceCopy) {
|
||||
refurb::rules::slice_copy(checker, subscript);
|
||||
}
|
||||
|
||||
pandas_vet::rules::subscript(checker, value, expr);
|
||||
}
|
||||
@@ -258,6 +260,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::SixPY3) {
|
||||
flake8_2020::rules::name_or_attribute(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UndocumentedWarn) {
|
||||
flake8_logging::rules::undocumented_warn(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::LoadBeforeGlobalDeclaration) {
|
||||
pylint::rules::load_before_global_declaration(checker, id, expr);
|
||||
}
|
||||
@@ -324,6 +329,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::CollectionsNamedTuple) {
|
||||
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UndocumentedWarn) {
|
||||
flake8_logging::rules::undocumented_warn(checker, expr);
|
||||
}
|
||||
pandas_vet::rules::attr(checker, attribute);
|
||||
}
|
||||
Expr::Call(
|
||||
@@ -578,6 +586,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::LoggingConfigInsecureListen) {
|
||||
flake8_bandit::rules::logging_config_insecure_listen(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::FlaskDebugTrue) {
|
||||
flake8_bandit::rules::flask_debug_true(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::SubprocessWithoutShellEqualsTrue,
|
||||
Rule::SubprocessPopenWithShellEqualsTrue,
|
||||
@@ -884,6 +895,15 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::QuadraticListSummation) {
|
||||
ruff::rules::quadratic_list_summation(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::DirectLoggerInstantiation) {
|
||||
flake8_logging::rules::direct_logger_instantiation(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidGetLoggerArgument) {
|
||||
flake8_logging::rules::invalid_get_logger_argument(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::ExceptionWithoutExcInfo) {
|
||||
flake8_logging::rules::exception_without_exc_info(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(ast::ExprDict {
|
||||
keys,
|
||||
@@ -1195,7 +1215,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. },
|
||||
kind: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.source_type.is_stub() && checker.enabled(Rule::NumericLiteralTooLong) {
|
||||
@@ -1204,7 +1223,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bytes(_),
|
||||
kind: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.source_type.is_stub() && checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
@@ -1213,7 +1231,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
kind,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::HardcodedBindAllInterfaces) {
|
||||
@@ -1227,7 +1244,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_bandit::rules::hardcoded_tmp_directory(checker, expr, value);
|
||||
}
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, kind.as_deref());
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, value.unicode);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
@@ -1262,16 +1279,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_simplify::rules::twisted_arms_in_ifexpr(checker, expr, test, body, orelse);
|
||||
}
|
||||
}
|
||||
Expr::ListComp(ast::ExprListComp {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
})
|
||||
| Expr::SetComp(ast::ExprSetComp {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::ListComp(
|
||||
comp @ ast::ExprListComp {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryComprehension) {
|
||||
flake8_comprehensions::rules::unnecessary_list_set_comprehension(
|
||||
checker, expr, elt, generators,
|
||||
@@ -1285,6 +1299,33 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::iteration_over_set(checker, &generator.iter);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::ReimplementedStarmap) {
|
||||
refurb::rules::reimplemented_starmap(checker, &comp.into());
|
||||
}
|
||||
}
|
||||
Expr::SetComp(
|
||||
comp @ ast::ExprSetComp {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryComprehension) {
|
||||
flake8_comprehensions::rules::unnecessary_list_set_comprehension(
|
||||
checker, expr, elt, generators,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::FunctionUsesLoopVariable) {
|
||||
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Expr(expr));
|
||||
}
|
||||
if checker.enabled(Rule::IterationOverSet) {
|
||||
for generator in generators {
|
||||
pylint::rules::iteration_over_set(checker, &generator.iter);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::ReimplementedStarmap) {
|
||||
refurb::rules::reimplemented_starmap(checker, &comp.into());
|
||||
}
|
||||
}
|
||||
Expr::DictComp(ast::ExprDictComp {
|
||||
key,
|
||||
@@ -1309,11 +1350,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
ruff::rules::static_key_dict_comprehension(checker, key);
|
||||
}
|
||||
}
|
||||
Expr::GeneratorExp(ast::ExprGeneratorExp {
|
||||
generators,
|
||||
elt: _,
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::GeneratorExp(
|
||||
generator @ ast::ExprGeneratorExp {
|
||||
generators,
|
||||
elt: _,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::FunctionUsesLoopVariable) {
|
||||
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Expr(expr));
|
||||
}
|
||||
@@ -1322,6 +1365,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::iteration_over_set(checker, &generator.iter);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::ReimplementedStarmap) {
|
||||
refurb::rules::reimplemented_starmap(checker, &generator.into());
|
||||
}
|
||||
}
|
||||
Expr::BoolOp(
|
||||
bool_op @ ast::ExprBoolOp {
|
||||
@@ -1354,8 +1400,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ExprAndFalse) {
|
||||
flake8_simplify::rules::expr_and_false(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::RepeatedEqualityComparisonTarget) {
|
||||
pylint::rules::repeated_equality_comparison_target(checker, bool_op);
|
||||
if checker.enabled(Rule::RepeatedEqualityComparison) {
|
||||
pylint::rules::repeated_equality_comparison(checker, bool_op);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -411,6 +411,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::EqWithoutHash) {
|
||||
pylint::rules::object_without_hash_method(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyPublicMethods) {
|
||||
pylint::rules::too_many_public_methods(
|
||||
checker,
|
||||
class_def,
|
||||
checker.settings.pylint.max_public_methods,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
pylint::rules::global_statement(checker, name);
|
||||
}
|
||||
@@ -1177,7 +1184,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
iter,
|
||||
orelse,
|
||||
is_async,
|
||||
..
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[
|
||||
@@ -1211,6 +1218,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ManualListCopy) {
|
||||
perflint::rules::manual_list_copy(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::ManualDictComprehension) {
|
||||
perflint::rules::manual_dict_comprehension(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryListCast) {
|
||||
perflint::rules::unnecessary_list_cast(checker, iter);
|
||||
}
|
||||
|
||||
@@ -528,11 +528,7 @@ where
|
||||
&self.semantic.definitions,
|
||||
);
|
||||
self.semantic.push_definition(definition);
|
||||
|
||||
self.semantic.push_scope(match &stmt {
|
||||
Stmt::FunctionDef(stmt) => ScopeKind::Function(stmt),
|
||||
_ => unreachable!("Expected Stmt::FunctionDef"),
|
||||
});
|
||||
self.semantic.push_scope(ScopeKind::Function(function_def));
|
||||
|
||||
self.deferred.functions.push(self.semantic.snapshot());
|
||||
|
||||
@@ -1192,7 +1188,6 @@ where
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
kind: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if self.semantic.in_type_definition()
|
||||
|
||||
@@ -6,6 +6,7 @@ use itertools::Itertools;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::noqa;
|
||||
@@ -19,7 +20,7 @@ pub(crate) fn check_noqa(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
path: &Path,
|
||||
locator: &Locator,
|
||||
comment_ranges: &[TextRange],
|
||||
comment_ranges: &CommentRanges,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
analyze_directives: bool,
|
||||
settings: &Settings,
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::registry::Rule;
|
||||
use crate::rules::flake8_copyright::rules::missing_copyright_notice;
|
||||
use crate::rules::pycodestyle::rules::{
|
||||
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
|
||||
tab_indentation, trailing_whitespace,
|
||||
trailing_whitespace,
|
||||
};
|
||||
use crate::rules::pylint;
|
||||
use crate::settings::Settings;
|
||||
@@ -31,7 +31,6 @@ pub(crate) fn check_physical_lines(
|
||||
let enforce_trailing_whitespace = settings.rules.enabled(Rule::TrailingWhitespace);
|
||||
let enforce_blank_line_contains_whitespace =
|
||||
settings.rules.enabled(Rule::BlankLineWithWhitespace);
|
||||
let enforce_tab_indentation = settings.rules.enabled(Rule::TabIndentation);
|
||||
let enforce_copyright_notice = settings.rules.enabled(Rule::MissingCopyrightNotice);
|
||||
|
||||
let mut doc_lines_iter = doc_lines.iter().peekable();
|
||||
@@ -69,12 +68,6 @@ pub(crate) fn check_physical_lines(
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_tab_indentation {
|
||||
if let Some(diagnostic) = tab_indentation(&line, indexer) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_no_newline_at_end_of_file {
|
||||
|
||||
@@ -86,6 +86,10 @@ pub(crate) fn check_tokens(
|
||||
}
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::TabIndentation) {
|
||||
pycodestyle::rules::tab_indentation(&mut diagnostics, tokens, locator, indexer);
|
||||
}
|
||||
|
||||
if settings.rules.any_enabled(&[
|
||||
Rule::InvalidCharacterBackspace,
|
||||
Rule::InvalidCharacterSub,
|
||||
|
||||
@@ -9,6 +9,7 @@ use strum_macros::{AsRefStr, EnumIter};
|
||||
use ruff_diagnostics::Violation;
|
||||
|
||||
use crate::registry::{AsRule, Linter};
|
||||
use crate::rule_selector::is_single_rule_selector;
|
||||
use crate::rules;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -51,7 +52,10 @@ impl PartialEq<&str> for NoqaCode {
|
||||
pub enum RuleGroup {
|
||||
/// The rule has not been assigned to any specific group.
|
||||
Unspecified,
|
||||
/// The rule is still under development, and must be enabled explicitly.
|
||||
/// The rule is unstable, and preview mode must be enabled for usage.
|
||||
Preview,
|
||||
/// Legacy category for unstable rules, supports backwards compatible selection.
|
||||
#[deprecated(note = "Use `RuleGroup::Preview` for new rules instead")]
|
||||
Nursery,
|
||||
}
|
||||
|
||||
@@ -64,38 +68,71 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
Some(match (linter, code) {
|
||||
// pycodestyle errors
|
||||
(Pycodestyle, "E101") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MixedSpacesAndTabs),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E111") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultiple),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E112") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlock),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E113") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentation),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E114") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultipleComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E115") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E116") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentationComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E117") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::OverIndented),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E201") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E202") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E203") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E211") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E221") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E222") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E223") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E224") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E225") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E226") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundArithmeticOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E227") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E228") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E231") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E241") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E242") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterComma),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E251") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E252") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E261") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E262") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterInlineComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E265") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E266") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleLeadingHashesForBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E271") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E272") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E273") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E274") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E275") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword),
|
||||
(Pycodestyle, "E401") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MultipleImportsOnOneLine),
|
||||
(Pycodestyle, "E402") => (RuleGroup::Unspecified, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile),
|
||||
@@ -176,6 +213,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "C0205") => (RuleGroup::Unspecified, rules::pylint::rules::SingleStringSlots),
|
||||
(Pylint, "C0208") => (RuleGroup::Unspecified, rules::pylint::rules::IterationOverSet),
|
||||
(Pylint, "C0414") => (RuleGroup::Unspecified, rules::pylint::rules::UselessImportAlias),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C3002") => (RuleGroup::Unspecified, rules::pylint::rules::UnnecessaryDirectLambdaCall),
|
||||
(Pylint, "E0100") => (RuleGroup::Unspecified, rules::pylint::rules::YieldInInit),
|
||||
@@ -212,10 +250,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R0915") => (RuleGroup::Unspecified, rules::pylint::rules::TooManyStatements),
|
||||
(Pylint, "R1701") => (RuleGroup::Unspecified, rules::pylint::rules::RepeatedIsinstanceCalls),
|
||||
(Pylint, "R1711") => (RuleGroup::Unspecified, rules::pylint::rules::UselessReturn),
|
||||
(Pylint, "R1714") => (RuleGroup::Unspecified, rules::pylint::rules::RepeatedEqualityComparisonTarget),
|
||||
(Pylint, "R1714") => (RuleGroup::Unspecified, rules::pylint::rules::RepeatedEqualityComparison),
|
||||
(Pylint, "R1722") => (RuleGroup::Unspecified, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R2004") => (RuleGroup::Unspecified, rules::pylint::rules::MagicValueComparison),
|
||||
(Pylint, "R5501") => (RuleGroup::Unspecified, rules::pylint::rules::CollapsibleElseIf),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
|
||||
(Pylint, "W0120") => (RuleGroup::Unspecified, rules::pylint::rules::UselessElseOnLoop),
|
||||
(Pylint, "W0127") => (RuleGroup::Unspecified, rules::pylint::rules::SelfAssigningVariable),
|
||||
@@ -228,8 +267,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "W1508") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarDefault),
|
||||
(Pylint, "W1509") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessPopenPreexecFn),
|
||||
(Pylint, "W1510") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessRunWithoutCheck),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
|
||||
(Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
|
||||
(Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax),
|
||||
|
||||
@@ -403,6 +445,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Simplify, "910") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::DictGetWithNoneDefault),
|
||||
|
||||
// flake8-copyright
|
||||
#[allow(deprecated)]
|
||||
(Flake8Copyright, "001") => (RuleGroup::Nursery, rules::flake8_copyright::rules::MissingCopyrightNotice),
|
||||
|
||||
// pyupgrade
|
||||
@@ -530,6 +573,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bandit, "110") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::TryExceptPass),
|
||||
(Flake8Bandit, "112") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::TryExceptContinue),
|
||||
(Flake8Bandit, "113") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::RequestWithoutTimeout),
|
||||
(Flake8Bandit, "201") => (RuleGroup::Preview, rules::flake8_bandit::rules::FlaskDebugTrue),
|
||||
(Flake8Bandit, "301") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousPickleUsage),
|
||||
(Flake8Bandit, "302") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousMarshalUsage),
|
||||
(Flake8Bandit, "303") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousInsecureHashUsage),
|
||||
@@ -815,9 +859,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "012") => (RuleGroup::Unspecified, rules::ruff::rules::MutableClassDefault),
|
||||
(Ruff, "013") => (RuleGroup::Unspecified, rules::ruff::rules::ImplicitOptional),
|
||||
#[cfg(feature = "unreachable-code")] // When removing this feature gate, also update rules_selector.rs
|
||||
#[allow(deprecated)]
|
||||
(Ruff, "014") => (RuleGroup::Nursery, rules::ruff::rules::UnreachableCode),
|
||||
(Ruff, "015") => (RuleGroup::Unspecified, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement),
|
||||
(Ruff, "016") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidIndexType),
|
||||
#[allow(deprecated)]
|
||||
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
|
||||
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
|
||||
@@ -853,6 +899,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Perflint, "203") => (RuleGroup::Unspecified, rules::perflint::rules::TryExceptInLoop),
|
||||
(Perflint, "401") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListComprehension),
|
||||
(Perflint, "402") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListCopy),
|
||||
(Perflint, "403") => (RuleGroup::Preview, rules::perflint::rules::ManualDictComprehension),
|
||||
|
||||
// flake8-fixme
|
||||
(Flake8Fixme, "001") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsFixme),
|
||||
@@ -866,9 +913,20 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Slots, "002") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInNamedtupleSubclass),
|
||||
|
||||
// refurb
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
(Refurb, "140") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedStarmap),
|
||||
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
|
||||
|
||||
// flake8-logging
|
||||
(Flake8Logging, "001") => (RuleGroup::Preview, rules::flake8_logging::rules::DirectLoggerInstantiation),
|
||||
(Flake8Logging, "002") => (RuleGroup::Preview, rules::flake8_logging::rules::InvalidGetLoggerArgument),
|
||||
(Flake8Logging, "007") => (RuleGroup::Preview, rules::flake8_logging::rules::ExceptionWithoutExcInfo),
|
||||
(Flake8Logging, "009") => (RuleGroup::Preview, rules::flake8_logging::rules::UndocumentedWarn),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use libcst_native::{Expression, NameOrAttribute};
|
||||
use libcst_native::{
|
||||
Expression, Name, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace, UnaryOperation,
|
||||
};
|
||||
|
||||
fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
|
||||
match expr {
|
||||
@@ -36,3 +38,55 @@ pub(crate) fn compose_module_path(module: &NameOrAttribute) -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a [`ParenthesizableWhitespace`] containing a single space.
|
||||
pub(crate) fn space() -> ParenthesizableWhitespace<'static> {
|
||||
ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" "))
|
||||
}
|
||||
|
||||
/// Ensure that a [`ParenthesizableWhitespace`] contains at least one space.
|
||||
pub(crate) fn or_space(whitespace: ParenthesizableWhitespace) -> ParenthesizableWhitespace {
|
||||
if whitespace == ParenthesizableWhitespace::default() {
|
||||
space()
|
||||
} else {
|
||||
whitespace
|
||||
}
|
||||
}
|
||||
|
||||
/// Negate a condition, i.e., `a` => `not a` and `not a` => `a`.
|
||||
pub(crate) fn negate<'a>(expression: &Expression<'a>) -> Expression<'a> {
|
||||
if let Expression::UnaryOperation(ref expression) = expression {
|
||||
if matches!(expression.operator, libcst_native::UnaryOp::Not { .. }) {
|
||||
return *expression.expression.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if let Expression::Name(ref expression) = expression {
|
||||
match expression.value {
|
||||
"True" => {
|
||||
return Expression::Name(Box::new(Name {
|
||||
value: "False",
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
"False" => {
|
||||
return Expression::Name(Box::new(Name {
|
||||
value: "True",
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Expression::UnaryOperation(Box::new(UnaryOperation {
|
||||
operator: libcst_native::UnaryOp::Not {
|
||||
whitespace_after: space(),
|
||||
},
|
||||
expression: Box::new(expression.clone()),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@ impl<'a> Docstring<'a> {
|
||||
pub(crate) fn leading_quote(&self) -> &'a str {
|
||||
&self.contents[TextRange::up_to(self.body_range.start())]
|
||||
}
|
||||
|
||||
pub(crate) fn triple_quoted(&self) -> bool {
|
||||
let leading_quote = self.leading_quote();
|
||||
leading_quote.ends_with("\"\"\"") || leading_quote.ends_with("'''")
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for Docstring<'_> {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
//!
|
||||
//! [Ruff]: https://github.com/astral-sh/ruff
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
pub use rule_selector::clap_completion::RuleSelectorParser;
|
||||
pub use rule_selector::RuleSelector;
|
||||
pub use rules::pycodestyle::rules::{IOError, SyntaxError};
|
||||
|
||||
|
||||
@@ -36,9 +36,6 @@ use crate::settings::{flags, Settings};
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::{directives, fs};
|
||||
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
|
||||
|
||||
/// A [`Result`]-like type that returns both data and an error. Used to return
|
||||
/// diagnostics even in the face of parse errors, since many diagnostics can be
|
||||
/// generated without a full AST.
|
||||
@@ -543,8 +540,9 @@ fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics:
|
||||
let codes = collect_rule_codes(diagnostics.iter().map(|diagnostic| diagnostic.kind.rule()));
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!(
|
||||
"{}: Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
|
||||
"{}{} Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
|
||||
"debug error".red().bold(),
|
||||
":".bold(),
|
||||
MAX_ITERATIONS,
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
@@ -553,18 +551,17 @@ fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics:
|
||||
} else {
|
||||
eprintln!(
|
||||
r#"
|
||||
{}: Failed to converge after {} iterations.
|
||||
{}{} Failed to converge after {} iterations.
|
||||
|
||||
This indicates a bug in `{}`. If you could open an issue at:
|
||||
This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
{}/issues/new?title=%5BInfinite%20loop%5D
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BInfinite%20loop%5D
|
||||
|
||||
...quoting the contents of `{}`, the rule codes {}, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
|
||||
"#,
|
||||
"error".red().bold(),
|
||||
":".bold(),
|
||||
MAX_ITERATIONS,
|
||||
CARGO_PKG_NAME,
|
||||
CARGO_PKG_REPOSITORY,
|
||||
fs::relativize_path(path),
|
||||
codes
|
||||
);
|
||||
@@ -581,8 +578,9 @@ fn report_autofix_syntax_error(
|
||||
let codes = collect_rule_codes(rules);
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!(
|
||||
"{}: Autofix introduced a syntax error in `{}` with rule codes {}: {}\n---\n{}\n---",
|
||||
"{}{} Autofix introduced a syntax error in `{}` with rule codes {}: {}\n---\n{}\n---",
|
||||
"error".red().bold(),
|
||||
":".bold(),
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
error,
|
||||
@@ -591,17 +589,16 @@ fn report_autofix_syntax_error(
|
||||
} else {
|
||||
eprintln!(
|
||||
r#"
|
||||
{}: Autofix introduced a syntax error. Reverting all changes.
|
||||
{}{} Autofix introduced a syntax error. Reverting all changes.
|
||||
|
||||
This indicates a bug in `{}`. If you could open an issue at:
|
||||
This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
{}/issues/new?title=%5BAutofix%20error%5D
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BAutofix%20error%5D
|
||||
|
||||
...quoting the contents of `{}`, the rule codes {}, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
|
||||
"#,
|
||||
"error".red().bold(),
|
||||
CARGO_PKG_NAME,
|
||||
CARGO_PKG_REPOSITORY,
|
||||
":".bold(),
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Write;
|
||||
|
||||
@@ -6,8 +7,6 @@ use serde::ser::SerializeSeq;
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde_json::json;
|
||||
|
||||
use ruff_source_file::SourceLocation;
|
||||
|
||||
use crate::fs::{relativize_path, relativize_path_to};
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
@@ -58,6 +57,7 @@ impl Serialize for SerializedMessages<'_> {
|
||||
S: Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_seq(Some(self.messages.len()))?;
|
||||
let mut fingerprints = HashSet::<u64>::with_capacity(self.messages.len());
|
||||
|
||||
for message in self.messages {
|
||||
let start_location = message.compute_start_location();
|
||||
@@ -82,10 +82,19 @@ impl Serialize for SerializedMessages<'_> {
|
||||
|project_dir| relativize_path_to(message.filename(), project_dir),
|
||||
);
|
||||
|
||||
let mut message_fingerprint = fingerprint(message, 0);
|
||||
|
||||
// Make sure that we do not get a fingerprint that is already in use
|
||||
// by adding in the previously generated one.
|
||||
while fingerprints.contains(&message_fingerprint) {
|
||||
message_fingerprint = fingerprint(message, message_fingerprint);
|
||||
}
|
||||
fingerprints.insert(message_fingerprint);
|
||||
|
||||
let value = json!({
|
||||
"description": format!("({}) {}", message.kind.rule().noqa_code(), message.kind.body),
|
||||
"severity": "major",
|
||||
"fingerprint": fingerprint(message, &start_location, &end_location),
|
||||
"fingerprint": format!("{:x}", message_fingerprint),
|
||||
"location": {
|
||||
"path": path,
|
||||
"lines": lines
|
||||
@@ -100,11 +109,7 @@ impl Serialize for SerializedMessages<'_> {
|
||||
}
|
||||
|
||||
/// Generate a unique fingerprint to identify a violation.
|
||||
fn fingerprint(
|
||||
message: &Message,
|
||||
start_location: &SourceLocation,
|
||||
end_location: &SourceLocation,
|
||||
) -> String {
|
||||
fn fingerprint(message: &Message, salt: u64) -> u64 {
|
||||
let Message {
|
||||
kind,
|
||||
range: _,
|
||||
@@ -115,12 +120,11 @@ fn fingerprint(
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
||||
kind.rule().hash(&mut hasher);
|
||||
start_location.hash(&mut hasher);
|
||||
end_location.hash(&mut hasher);
|
||||
salt.hash(&mut hasher);
|
||||
kind.name.hash(&mut hasher);
|
||||
file.name().hash(&mut hasher);
|
||||
|
||||
format!("{:x}", hasher.finish())
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::num::NonZeroUsize;
|
||||
|
||||
use colored::Colorize;
|
||||
|
||||
use ruff_notebook::{Notebook, NotebookIndex};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::OneIndexed;
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
@@ -65,7 +65,7 @@ impl Emitter for GroupedEmitter {
|
||||
writer,
|
||||
"{}",
|
||||
DisplayGroupedMessage {
|
||||
jupyter_index: context.notebook(message.filename()).map(Notebook::index),
|
||||
notebook_index: context.notebook_index(message.filename()),
|
||||
message,
|
||||
show_fix_status: self.show_fix_status,
|
||||
show_source: self.show_source,
|
||||
@@ -92,7 +92,7 @@ struct DisplayGroupedMessage<'a> {
|
||||
show_source: bool,
|
||||
row_length: NonZeroUsize,
|
||||
column_length: NonZeroUsize,
|
||||
jupyter_index: Option<&'a NotebookIndex>,
|
||||
notebook_index: Option<&'a NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Display for DisplayGroupedMessage<'_> {
|
||||
@@ -110,7 +110,7 @@ impl Display for DisplayGroupedMessage<'_> {
|
||||
)?;
|
||||
|
||||
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
|
||||
let (row, col) = if let Some(jupyter_index) = self.jupyter_index {
|
||||
let (row, col) = if let Some(jupyter_index) = self.notebook_index {
|
||||
write!(
|
||||
f,
|
||||
"cell {cell}{sep}",
|
||||
@@ -150,7 +150,7 @@ impl Display for DisplayGroupedMessage<'_> {
|
||||
"{}",
|
||||
MessageCodeFrame {
|
||||
message,
|
||||
jupyter_index: self.jupyter_index
|
||||
notebook_index: self.notebook_index
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ pub use json_lines::JsonLinesEmitter;
|
||||
pub use junit::JunitEmitter;
|
||||
pub use pylint::PylintEmitter;
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
|
||||
use ruff_notebook::Notebook;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{SourceFile, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
pub use text::TextEmitter;
|
||||
@@ -127,21 +127,21 @@ pub trait Emitter {
|
||||
|
||||
/// Context passed to [`Emitter`].
|
||||
pub struct EmitterContext<'a> {
|
||||
notebooks: &'a FxHashMap<String, Notebook>,
|
||||
notebook_indexes: &'a FxHashMap<String, NotebookIndex>,
|
||||
}
|
||||
|
||||
impl<'a> EmitterContext<'a> {
|
||||
pub fn new(notebooks: &'a FxHashMap<String, Notebook>) -> Self {
|
||||
Self { notebooks }
|
||||
pub fn new(notebook_indexes: &'a FxHashMap<String, NotebookIndex>) -> Self {
|
||||
Self { notebook_indexes }
|
||||
}
|
||||
|
||||
/// Tests if the file with `name` is a jupyter notebook.
|
||||
pub fn is_notebook(&self, name: &str) -> bool {
|
||||
self.notebooks.contains_key(name)
|
||||
self.notebook_indexes.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn notebook(&self, name: &str) -> Option<&Notebook> {
|
||||
self.notebooks.get(name)
|
||||
pub fn notebook_index(&self, name: &str) -> Option<&NotebookIndex> {
|
||||
self.notebook_indexes.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,8 +225,8 @@ def fibonacci(n):
|
||||
emitter: &mut dyn Emitter,
|
||||
messages: &[Message],
|
||||
) -> String {
|
||||
let source_kinds = FxHashMap::default();
|
||||
let context = EmitterContext::new(&source_kinds);
|
||||
let notebook_indexes = FxHashMap::default();
|
||||
let context = EmitterContext::new(¬ebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
emitter.emit(&mut output, messages, &context).unwrap();
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ expression: content
|
||||
},
|
||||
"message": "`os` imported but unused",
|
||||
"noqa_row": 1,
|
||||
"url": "https://beta.ruff.rs/docs/rules/unused-import"
|
||||
"url": "https://docs.astral.sh/ruff/rules/unused-import"
|
||||
},
|
||||
{
|
||||
"code": "F841",
|
||||
@@ -65,7 +65,7 @@ expression: content
|
||||
},
|
||||
"message": "Local variable `x` is assigned to but never used",
|
||||
"noqa_row": 6,
|
||||
"url": "https://beta.ruff.rs/docs/rules/unused-variable"
|
||||
"url": "https://docs.astral.sh/ruff/rules/unused-variable"
|
||||
},
|
||||
{
|
||||
"code": "F821",
|
||||
@@ -81,6 +81,6 @@ expression: content
|
||||
},
|
||||
"message": "Undefined name `a`",
|
||||
"noqa_row": 1,
|
||||
"url": "https://beta.ruff.rs/docs/rules/undefined-name"
|
||||
"url": "https://docs.astral.sh/ruff/rules/undefined-name"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
source: crates/ruff/src/message/json_lines.rs
|
||||
expression: content
|
||||
---
|
||||
{"code":"F401","end_location":{"column":10,"row":1},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://beta.ruff.rs/docs/rules/unused-import"}
|
||||
{"code":"F841","end_location":{"column":6,"row":6},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://beta.ruff.rs/docs/rules/unused-variable"}
|
||||
{"code":"F821","end_location":{"column":5,"row":1},"filename":"undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://beta.ruff.rs/docs/rules/undefined-name"}
|
||||
{"code":"F401","end_location":{"column":10,"row":1},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"code":"F841","end_location":{"column":6,"row":6},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
|
||||
{"code":"F821","end_location":{"column":5,"row":1},"filename":"undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
|
||||
use ruff_notebook::{Notebook, NotebookIndex};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{OneIndexed, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -71,14 +71,14 @@ impl Emitter for TextEmitter {
|
||||
)?;
|
||||
|
||||
let start_location = message.compute_start_location();
|
||||
let jupyter_index = context.notebook(message.filename()).map(Notebook::index);
|
||||
let notebook_index = context.notebook_index(message.filename());
|
||||
|
||||
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
|
||||
let diagnostic_location = if let Some(jupyter_index) = jupyter_index {
|
||||
let diagnostic_location = if let Some(notebook_index) = notebook_index {
|
||||
write!(
|
||||
writer,
|
||||
"cell {cell}{sep}",
|
||||
cell = jupyter_index
|
||||
cell = notebook_index
|
||||
.cell(start_location.row.get())
|
||||
.unwrap_or_default(),
|
||||
sep = ":".cyan(),
|
||||
@@ -86,7 +86,7 @@ impl Emitter for TextEmitter {
|
||||
|
||||
SourceLocation {
|
||||
row: OneIndexed::new(
|
||||
jupyter_index
|
||||
notebook_index
|
||||
.cell_row(start_location.row.get())
|
||||
.unwrap_or(1) as usize,
|
||||
)
|
||||
@@ -115,7 +115,7 @@ impl Emitter for TextEmitter {
|
||||
"{}",
|
||||
MessageCodeFrame {
|
||||
message,
|
||||
jupyter_index
|
||||
notebook_index
|
||||
}
|
||||
)?;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
|
||||
pub(super) struct MessageCodeFrame<'a> {
|
||||
pub(crate) message: &'a Message,
|
||||
pub(crate) jupyter_index: Option<&'a NotebookIndex>,
|
||||
pub(crate) notebook_index: Option<&'a NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Display for MessageCodeFrame<'_> {
|
||||
@@ -186,14 +186,12 @@ impl Display for MessageCodeFrame<'_> {
|
||||
let content_start_index = source_code.line_index(range.start());
|
||||
let mut start_index = content_start_index.saturating_sub(2);
|
||||
|
||||
// If we're working on a jupyter notebook, skip the lines which are
|
||||
// If we're working with a Jupyter Notebook, skip the lines which are
|
||||
// outside of the cell containing the diagnostic.
|
||||
if let Some(jupyter_index) = self.jupyter_index {
|
||||
let content_start_cell = jupyter_index
|
||||
.cell(content_start_index.get())
|
||||
.unwrap_or_default();
|
||||
if let Some(index) = self.notebook_index {
|
||||
let content_start_cell = index.cell(content_start_index.get()).unwrap_or_default();
|
||||
while start_index < content_start_index {
|
||||
if jupyter_index.cell(start_index.get()).unwrap_or_default() == content_start_cell {
|
||||
if index.cell(start_index.get()).unwrap_or_default() == content_start_cell {
|
||||
break;
|
||||
}
|
||||
start_index = start_index.saturating_add(1);
|
||||
@@ -213,14 +211,12 @@ impl Display for MessageCodeFrame<'_> {
|
||||
.saturating_add(2)
|
||||
.min(OneIndexed::from_zero_indexed(source_code.line_count()));
|
||||
|
||||
// If we're working on a jupyter notebook, skip the lines which are
|
||||
// If we're working with a Jupyter Notebook, skip the lines which are
|
||||
// outside of the cell containing the diagnostic.
|
||||
if let Some(jupyter_index) = self.jupyter_index {
|
||||
let content_end_cell = jupyter_index
|
||||
.cell(content_end_index.get())
|
||||
.unwrap_or_default();
|
||||
if let Some(index) = self.notebook_index {
|
||||
let content_end_cell = index.cell(content_end_index.get()).unwrap_or_default();
|
||||
while end_index > content_end_index {
|
||||
if jupyter_index.cell(end_index.get()).unwrap_or_default() == content_end_cell {
|
||||
if index.cell(end_index.get()).unwrap_or_default() == content_end_cell {
|
||||
break;
|
||||
}
|
||||
end_index = end_index.saturating_sub(1);
|
||||
@@ -256,10 +252,10 @@ impl Display for MessageCodeFrame<'_> {
|
||||
title: None,
|
||||
slices: vec![Slice {
|
||||
source: &source.text,
|
||||
line_start: self.jupyter_index.map_or_else(
|
||||
line_start: self.notebook_index.map_or_else(
|
||||
|| start_index.get(),
|
||||
|jupyter_index| {
|
||||
jupyter_index
|
||||
|notebook_index| {
|
||||
notebook_index
|
||||
.cell_row(start_index.get())
|
||||
.unwrap_or_default() as usize
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ use log::warn;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_trivia::indentation_at_offset;
|
||||
use ruff_python_trivia::{indentation_at_offset, CommentRanges};
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
|
||||
use crate::codes::NoqaCode;
|
||||
@@ -59,7 +59,7 @@ impl<'a> Directive<'a> {
|
||||
if text[..comment_start]
|
||||
.chars()
|
||||
.last()
|
||||
.is_some_and(|c| c != '#')
|
||||
.map_or(true, |c| c != '#')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -234,7 +234,7 @@ impl FileExemption {
|
||||
/// globally ignored within the file.
|
||||
pub(crate) fn try_extract(
|
||||
contents: &str,
|
||||
comment_ranges: &[TextRange],
|
||||
comment_ranges: &CommentRanges,
|
||||
path: &Path,
|
||||
locator: &Locator,
|
||||
) -> Option<Self> {
|
||||
@@ -457,7 +457,7 @@ pub(crate) fn add_noqa(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
locator: &Locator,
|
||||
commented_lines: &[TextRange],
|
||||
comment_ranges: &CommentRanges,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
) -> Result<usize> {
|
||||
@@ -465,7 +465,7 @@ pub(crate) fn add_noqa(
|
||||
path,
|
||||
diagnostics,
|
||||
locator,
|
||||
commented_lines,
|
||||
comment_ranges,
|
||||
noqa_line_for,
|
||||
line_ending,
|
||||
);
|
||||
@@ -477,7 +477,7 @@ fn add_noqa_inner(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
locator: &Locator,
|
||||
commented_ranges: &[TextRange],
|
||||
comment_ranges: &CommentRanges,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
) -> (usize, String) {
|
||||
@@ -487,8 +487,8 @@ fn add_noqa_inner(
|
||||
|
||||
// Whether the file is exempted from all checks.
|
||||
// Codes that are globally exempted (within the current file).
|
||||
let exemption = FileExemption::try_extract(locator.contents(), commented_ranges, path, locator);
|
||||
let directives = NoqaDirectives::from_commented_ranges(commented_ranges, path, locator);
|
||||
let exemption = FileExemption::try_extract(locator.contents(), comment_ranges, path, locator);
|
||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator);
|
||||
|
||||
// Mark any non-ignored diagnostics.
|
||||
for diagnostic in diagnostics {
|
||||
@@ -658,7 +658,7 @@ pub(crate) struct NoqaDirectives<'a> {
|
||||
|
||||
impl<'a> NoqaDirectives<'a> {
|
||||
pub(crate) fn from_commented_ranges(
|
||||
comment_ranges: &[TextRange],
|
||||
comment_ranges: &CommentRanges,
|
||||
path: &Path,
|
||||
locator: &'a Locator<'a>,
|
||||
) -> Self {
|
||||
@@ -800,6 +800,7 @@ mod tests {
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
|
||||
use crate::noqa::{add_noqa_inner, Directive, NoqaMapping, ParsedFileExemption};
|
||||
@@ -997,7 +998,7 @@ mod tests {
|
||||
path,
|
||||
&[],
|
||||
&Locator::new(contents),
|
||||
&[],
|
||||
&CommentRanges::default(),
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
@@ -1017,7 +1018,7 @@ mod tests {
|
||||
path,
|
||||
&diagnostics,
|
||||
&Locator::new(contents),
|
||||
&[],
|
||||
&CommentRanges::default(),
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
@@ -1038,11 +1039,13 @@ mod tests {
|
||||
];
|
||||
let contents = "x = 1 # noqa: E741\n";
|
||||
let noqa_line_for = NoqaMapping::default();
|
||||
let comment_ranges =
|
||||
CommentRanges::new(vec![TextRange::new(TextSize::from(7), TextSize::from(19))]);
|
||||
let (count, output) = add_noqa_inner(
|
||||
path,
|
||||
&diagnostics,
|
||||
&Locator::new(contents),
|
||||
&[TextRange::new(TextSize::from(7), TextSize::from(19))],
|
||||
&comment_ranges,
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
@@ -1063,11 +1066,13 @@ mod tests {
|
||||
];
|
||||
let contents = "x = 1 # noqa";
|
||||
let noqa_line_for = NoqaMapping::default();
|
||||
let comment_ranges =
|
||||
CommentRanges::new(vec![TextRange::new(TextSize::from(7), TextSize::from(13))]);
|
||||
let (count, output) = add_noqa_inner(
|
||||
path,
|
||||
&diagnostics,
|
||||
&Locator::new(contents),
|
||||
&[TextRange::new(TextSize::from(7), TextSize::from(13))],
|
||||
&comment_ranges,
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
|
||||
@@ -199,6 +199,9 @@ pub enum Linter {
|
||||
/// [refurb](https://pypi.org/project/refurb/)
|
||||
#[prefix = "FURB"]
|
||||
Refurb,
|
||||
/// [flake8-logging](https://pypi.org/project/flake8-logging/)
|
||||
#[prefix = "LOG"]
|
||||
Flake8Logging,
|
||||
/// Ruff-specific rules
|
||||
#[prefix = "RUF"]
|
||||
Ruff,
|
||||
@@ -248,7 +251,6 @@ impl Rule {
|
||||
| Rule::MissingCopyrightNotice
|
||||
| Rule::MissingNewlineAtEndOfFile
|
||||
| Rule::MixedSpacesAndTabs
|
||||
| Rule::TabIndentation
|
||||
| Rule::TrailingWhitespace => LintSource::PhysicalLines,
|
||||
Rule::AmbiguousUnicodeCharacterComment
|
||||
| Rule::AmbiguousUnicodeCharacterDocstring
|
||||
@@ -289,6 +291,7 @@ impl Rule {
|
||||
| Rule::ShebangNotExecutable
|
||||
| Rule::ShebangNotFirstLine
|
||||
| Rule::SingleLineImplicitStringConcatenation
|
||||
| Rule::TabIndentation
|
||||
| Rule::TrailingCommaOnBareTuple
|
||||
| Rule::TypeCommentInStub
|
||||
| Rule::UselessSemicolon
|
||||
|
||||
@@ -9,12 +9,14 @@ use crate::codes::RuleCodePrefix;
|
||||
use crate::codes::RuleIter;
|
||||
use crate::registry::{Linter, Rule, RuleNamespace};
|
||||
use crate::rule_redirects::get_redirect;
|
||||
use crate::settings::types::PreviewMode;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum RuleSelector {
|
||||
/// Select all stable rules.
|
||||
/// Select all rules (includes rules in preview if enabled)
|
||||
All,
|
||||
/// Select all nursery rules.
|
||||
/// Legacy category to select all rules in the "nursery" which predated preview mode
|
||||
#[deprecated(note = "The nursery was replaced with 'preview mode' which has no selector")]
|
||||
Nursery,
|
||||
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
|
||||
/// via a single selector.
|
||||
@@ -29,6 +31,11 @@ pub enum RuleSelector {
|
||||
prefix: RuleCodePrefix,
|
||||
redirected_from: Option<&'static str>,
|
||||
},
|
||||
/// Select an individual rule with a given prefix.
|
||||
Rule {
|
||||
prefix: RuleCodePrefix,
|
||||
redirected_from: Option<&'static str>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Linter> for RuleSelector {
|
||||
@@ -43,6 +50,7 @@ impl FromStr for RuleSelector {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"ALL" => Ok(Self::All),
|
||||
#[allow(deprecated)]
|
||||
"NURSERY" => Ok(Self::Nursery),
|
||||
"C" => Ok(Self::C),
|
||||
"T" => Ok(Self::T),
|
||||
@@ -59,16 +67,43 @@ impl FromStr for RuleSelector {
|
||||
return Ok(Self::Linter(linter));
|
||||
}
|
||||
|
||||
Ok(Self::Prefix {
|
||||
prefix: RuleCodePrefix::parse(&linter, code)
|
||||
.map_err(|_| ParseError::Unknown(s.to_string()))?,
|
||||
redirected_from,
|
||||
})
|
||||
// Does the selector select a single rule?
|
||||
let prefix = RuleCodePrefix::parse(&linter, code)
|
||||
.map_err(|_| ParseError::Unknown(s.to_string()))?;
|
||||
|
||||
if is_single_rule_selector(&prefix) {
|
||||
Ok(Self::Rule {
|
||||
prefix,
|
||||
redirected_from,
|
||||
})
|
||||
} else {
|
||||
Ok(Self::Prefix {
|
||||
prefix,
|
||||
redirected_from,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`RuleCodePrefix`] matches a single rule exactly
|
||||
/// (e.g., `E225`, as opposed to `E2`).
|
||||
pub(crate) fn is_single_rule_selector(prefix: &RuleCodePrefix) -> bool {
|
||||
let mut rules = prefix.rules();
|
||||
|
||||
// The selector must match a single rule.
|
||||
let Some(rule) = rules.next() else {
|
||||
return false;
|
||||
};
|
||||
if rules.next().is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The rule must match the selector exactly.
|
||||
rule.noqa_code().suffix() == prefix.short_code()
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ParseError {
|
||||
#[error("Unknown rule selector: `{0}`")]
|
||||
@@ -81,10 +116,11 @@ impl RuleSelector {
|
||||
pub fn prefix_and_code(&self) -> (&'static str, &'static str) {
|
||||
match self {
|
||||
RuleSelector::All => ("", "ALL"),
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => ("", "NURSERY"),
|
||||
RuleSelector::C => ("", "C"),
|
||||
RuleSelector::T => ("", "T"),
|
||||
RuleSelector::Prefix { prefix, .. } => {
|
||||
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
|
||||
(prefix.linter().common_prefix(), prefix.short_code())
|
||||
}
|
||||
RuleSelector::Linter(l) => (l.common_prefix(), ""),
|
||||
@@ -135,24 +171,13 @@ impl Visitor<'_> for SelectorVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RuleCodePrefix> for RuleSelector {
|
||||
fn from(prefix: RuleCodePrefix) -> Self {
|
||||
Self::Prefix {
|
||||
prefix,
|
||||
redirected_from: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &RuleSelector {
|
||||
type IntoIter = RuleSelectorIter;
|
||||
type Item = Rule;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
impl RuleSelector {
|
||||
/// Return all matching rules, regardless of whether they're in preview.
|
||||
pub fn all_rules(&self) -> impl Iterator<Item = Rule> + '_ {
|
||||
match self {
|
||||
RuleSelector::All => {
|
||||
RuleSelectorIter::All(Rule::iter().filter(|rule| !rule.is_nursery()))
|
||||
}
|
||||
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
|
||||
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => {
|
||||
RuleSelectorIter::Nursery(Rule::iter().filter(Rule::is_nursery))
|
||||
}
|
||||
@@ -167,13 +192,28 @@ impl IntoIterator for &RuleSelector {
|
||||
.chain(Linter::Flake8Print.rules()),
|
||||
),
|
||||
RuleSelector::Linter(linter) => RuleSelectorIter::Vec(linter.rules()),
|
||||
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Vec(prefix.clone().rules()),
|
||||
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
|
||||
RuleSelectorIter::Vec(prefix.clone().rules())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns rules matching the selector, taking into account whether preview mode is enabled.
|
||||
pub fn rules(&self, preview: PreviewMode) -> impl Iterator<Item = Rule> + '_ {
|
||||
#[allow(deprecated)]
|
||||
self.all_rules().filter(move |rule| {
|
||||
// Always include rules that are not in preview or the nursery
|
||||
!(rule.is_preview() || rule.is_nursery())
|
||||
// Backwards compatibility allows selection of nursery rules by exact code or dedicated group
|
||||
|| ((matches!(self, RuleSelector::Rule { .. }) || matches!(self, RuleSelector::Nursery { .. })) && rule.is_nursery())
|
||||
// Enabling preview includes all preview or nursery rules
|
||||
|| preview.is_enabled()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RuleSelectorIter {
|
||||
All(std::iter::Filter<RuleIter, fn(&Rule) -> bool>),
|
||||
All(RuleIter),
|
||||
Nursery(std::iter::Filter<RuleIter, fn(&Rule) -> bool>),
|
||||
Chain(std::iter::Chain<std::vec::IntoIter<Rule>, std::vec::IntoIter<Rule>>),
|
||||
Vec(std::vec::IntoIter<Rule>),
|
||||
@@ -192,18 +232,6 @@ impl Iterator for RuleSelectorIter {
|
||||
}
|
||||
}
|
||||
|
||||
/// A const alternative to the `impl From<RuleCodePrefix> for RuleSelector`
|
||||
/// to let us keep the fields of [`RuleSelector`] private.
|
||||
// Note that Rust doesn't yet support `impl const From<RuleCodePrefix> for
|
||||
// RuleSelector` (see https://github.com/rust-lang/rust/issues/67792).
|
||||
// TODO(martin): Remove once RuleSelector is an enum with Linter & Rule variants
|
||||
pub(crate) const fn prefix_to_selector(prefix: RuleCodePrefix) -> RuleSelector {
|
||||
RuleSelector::Prefix {
|
||||
prefix,
|
||||
redirected_from: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schema {
|
||||
use itertools::Itertools;
|
||||
@@ -226,8 +254,9 @@ mod schema {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(
|
||||
[
|
||||
// Include the non-standard "ALL" selector.
|
||||
// Include the non-standard "ALL" and "NURSERY" selectors.
|
||||
"ALL".to_string(),
|
||||
"NURSERY".to_string(),
|
||||
// Include the legacy "C" and "T" selectors.
|
||||
"C".to_string(),
|
||||
"T".to_string(),
|
||||
@@ -266,18 +295,19 @@ impl RuleSelector {
|
||||
pub fn specificity(&self) -> Specificity {
|
||||
match self {
|
||||
RuleSelector::All => Specificity::All,
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => Specificity::All,
|
||||
RuleSelector::T => Specificity::LinterGroup,
|
||||
RuleSelector::C => Specificity::LinterGroup,
|
||||
RuleSelector::Linter(..) => Specificity::Linter,
|
||||
RuleSelector::Rule { .. } => Specificity::Rule,
|
||||
RuleSelector::Prefix { prefix, .. } => {
|
||||
let prefix: &'static str = prefix.short_code();
|
||||
match prefix.len() {
|
||||
1 => Specificity::Code1Char,
|
||||
2 => Specificity::Code2Chars,
|
||||
3 => Specificity::Code3Chars,
|
||||
4 => Specificity::Code4Chars,
|
||||
5 => Specificity::Code5Chars,
|
||||
1 => Specificity::Prefix1Char,
|
||||
2 => Specificity::Prefix2Chars,
|
||||
3 => Specificity::Prefix3Chars,
|
||||
4 => Specificity::Prefix4Chars,
|
||||
_ => panic!("RuleSelector::specificity doesn't yet support codes with so many characters"),
|
||||
}
|
||||
}
|
||||
@@ -285,26 +315,35 @@ impl RuleSelector {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
|
||||
pub enum Specificity {
|
||||
/// The specificity when selecting all rules (e.g., `--select ALL`).
|
||||
All,
|
||||
/// The specificity when selecting a legacy linter group (e.g., `--select C` or `--select T`).
|
||||
LinterGroup,
|
||||
/// The specificity when selecting a linter (e.g., `--select PLE` or `--select UP`).
|
||||
Linter,
|
||||
Code1Char,
|
||||
Code2Chars,
|
||||
Code3Chars,
|
||||
Code4Chars,
|
||||
Code5Chars,
|
||||
/// The specificity when selecting via a rule prefix with a one-character code (e.g., `--select PLE1`).
|
||||
Prefix1Char,
|
||||
/// The specificity when selecting via a rule prefix with a two-character code (e.g., `--select PLE12`).
|
||||
Prefix2Chars,
|
||||
/// The specificity when selecting via a rule prefix with a three-character code (e.g., `--select PLE123`).
|
||||
Prefix3Chars,
|
||||
/// The specificity when selecting via a rule prefix with a four-character code (e.g., `--select PLE1234`).
|
||||
Prefix4Chars,
|
||||
/// The specificity when selecting an individual rule (e.g., `--select PLE1205`).
|
||||
Rule,
|
||||
}
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
mod clap_completion {
|
||||
pub mod clap_completion {
|
||||
use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{
|
||||
codes::RuleCodePrefix,
|
||||
registry::{Linter, RuleNamespace},
|
||||
rule_selector::is_single_rule_selector,
|
||||
RuleSelector,
|
||||
};
|
||||
|
||||
@@ -324,17 +363,29 @@ mod clap_completion {
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
_cmd: &clap::Command,
|
||||
_arg: Option<&clap::Arg>,
|
||||
cmd: &clap::Command,
|
||||
arg: Option<&clap::Arg>,
|
||||
value: &std::ffi::OsStr,
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
let value = value
|
||||
.to_str()
|
||||
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
|
||||
|
||||
value
|
||||
.parse()
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e))
|
||||
value.parse().map_err(|_| {
|
||||
let mut error =
|
||||
clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
|
||||
if let Some(arg) = arg {
|
||||
error.insert(
|
||||
clap::error::ContextKind::InvalidArg,
|
||||
clap::error::ContextValue::String(arg.to_string()),
|
||||
);
|
||||
}
|
||||
error.insert(
|
||||
clap::error::ContextKind::InvalidValue,
|
||||
clap::error::ContextValue::String(value.to_string()),
|
||||
);
|
||||
error
|
||||
})
|
||||
}
|
||||
|
||||
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
|
||||
@@ -349,27 +400,34 @@ mod clap_completion {
|
||||
RuleCodePrefix::iter()
|
||||
// Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is
|
||||
// off-by-default
|
||||
.filter(|p| {
|
||||
format!("{}{}", p.linter().common_prefix(), p.short_code())
|
||||
!= "RUF014"
|
||||
.filter(|prefix| {
|
||||
format!(
|
||||
"{}{}",
|
||||
prefix.linter().common_prefix(),
|
||||
prefix.short_code()
|
||||
) != "RUF014"
|
||||
})
|
||||
.map(|p| {
|
||||
let prefix = p.linter().common_prefix();
|
||||
let code = p.short_code();
|
||||
|
||||
let mut rules_iter = p.rules();
|
||||
let rule1 = rules_iter.next();
|
||||
let rule2 = rules_iter.next();
|
||||
|
||||
let value = PossibleValue::new(format!("{prefix}{code}"));
|
||||
|
||||
if rule2.is_none() {
|
||||
let rule1 = rule1.unwrap();
|
||||
let name: &'static str = rule1.into();
|
||||
value.help(name)
|
||||
} else {
|
||||
value
|
||||
.filter_map(|prefix| {
|
||||
// Ex) `UP`
|
||||
if prefix.short_code().is_empty() {
|
||||
let code = prefix.linter().common_prefix();
|
||||
let name = prefix.linter().name();
|
||||
return Some(PossibleValue::new(code).help(name));
|
||||
}
|
||||
|
||||
// Ex) `UP004`
|
||||
if is_single_rule_selector(&prefix) {
|
||||
let rule = prefix.rules().next()?;
|
||||
let code = format!(
|
||||
"{}{}",
|
||||
prefix.linter().common_prefix(),
|
||||
prefix.short_code()
|
||||
);
|
||||
let name: &'static str = rule.into();
|
||||
return Some(PossibleValue::new(code).help(name));
|
||||
}
|
||||
|
||||
None
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use num_bigint::BigInt;
|
||||
use ruff_python_ast::{self as ast, CmpOp, Constant, Expr};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Constant, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -237,7 +235,7 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
|
||||
..
|
||||
}) = slice.as_ref()
|
||||
{
|
||||
if *i == BigInt::from(0) {
|
||||
if *i == 0 {
|
||||
if let (
|
||||
[CmpOp::Eq | CmpOp::NotEq],
|
||||
[Expr::Constant(ast::ExprConstant {
|
||||
@@ -246,13 +244,13 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
|
||||
})],
|
||||
) = (ops, comparators)
|
||||
{
|
||||
if *n == BigInt::from(3) && checker.enabled(Rule::SysVersionInfo0Eq3) {
|
||||
if *n == 3 && checker.enabled(Rule::SysVersionInfo0Eq3) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SysVersionInfo0Eq3, left.range()));
|
||||
}
|
||||
}
|
||||
} else if *i == BigInt::from(1) {
|
||||
} else if *i == 1 {
|
||||
if let (
|
||||
[CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE],
|
||||
[Expr::Constant(ast::ExprConstant {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use num_bigint::BigInt;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -184,11 +183,11 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
..
|
||||
}) = upper.as_ref()
|
||||
{
|
||||
if *i == BigInt::from(1) && checker.enabled(Rule::SysVersionSlice1) {
|
||||
if *i == 1 && checker.enabled(Rule::SysVersionSlice1) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SysVersionSlice1, value.range()));
|
||||
} else if *i == BigInt::from(3) && checker.enabled(Rule::SysVersionSlice3) {
|
||||
} else if *i == 3 && checker.enabled(Rule::SysVersionSlice3) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SysVersionSlice3, value.range()));
|
||||
@@ -200,11 +199,11 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
value: Constant::Int(i),
|
||||
..
|
||||
}) => {
|
||||
if *i == BigInt::from(2) && checker.enabled(Rule::SysVersion2) {
|
||||
if *i == 2 && checker.enabled(Rule::SysVersion2) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SysVersion2, value.range()));
|
||||
} else if *i == BigInt::from(0) && checker.enabled(Rule::SysVersion0) {
|
||||
} else if *i == 0 && checker.enabled(Rule::SysVersion0) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SysVersion0, value.range()));
|
||||
|
||||
@@ -454,7 +454,6 @@ fn check_dynamically_typed<F>(
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
range,
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
}) = annotation
|
||||
{
|
||||
// Quoted annotations
|
||||
|
||||
@@ -19,6 +19,7 @@ mod tests {
|
||||
#[test_case(Rule::BadFilePermissions, Path::new("S103.py"))]
|
||||
#[test_case(Rule::CallWithShellEqualsTrue, Path::new("S604.py"))]
|
||||
#[test_case(Rule::ExecBuiltin, Path::new("S102.py"))]
|
||||
#[test_case(Rule::FlaskDebugTrue, Path::new("S201.py"))]
|
||||
#[test_case(Rule::HardcodedBindAllInterfaces, Path::new("S104.py"))]
|
||||
#[test_case(Rule::HardcodedPasswordDefault, Path::new("S107.py"))]
|
||||
#[test_case(Rule::HardcodedPasswordFuncArg, Path::new("S106.py"))]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use num_traits::ToPrimitive;
|
||||
use malachite::Integer;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -36,7 +36,7 @@ use crate::checkers::ast::Checker;
|
||||
/// - [Common Weakness Enumeration: CWE-732](https://cwe.mitre.org/data/definitions/732.html)
|
||||
#[violation]
|
||||
pub struct BadFilePermissions {
|
||||
mask: u16,
|
||||
mask: Integer,
|
||||
}
|
||||
|
||||
impl Violation for BadFilePermissions {
|
||||
@@ -56,7 +56,9 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
|
||||
{
|
||||
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
|
||||
if let Some(int_value) = int_value(mode_arg, checker.semantic()) {
|
||||
if (int_value & WRITE_WORLD > 0) || (int_value & EXECUTE_GROUP > 0) {
|
||||
if (int_value.clone() & Integer::from(WRITE_WORLD) > 0)
|
||||
|| (int_value.clone() & Integer::from(EXECUTE_GROUP) > 0)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BadFilePermissions { mask: int_value },
|
||||
mode_arg.range(),
|
||||
@@ -113,13 +115,17 @@ fn py_stat(call_path: &CallPath) -> Option<u16> {
|
||||
}
|
||||
}
|
||||
|
||||
fn int_value(expr: &Expr, semantic: &SemanticModel) -> Option<u16> {
|
||||
fn int_value(expr: &Expr, semantic: &SemanticModel) -> Option<Integer> {
|
||||
match expr {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) => value.to_u16(),
|
||||
Expr::Attribute(_) => semantic.resolve_call_path(expr).as_ref().and_then(py_stat),
|
||||
}) => Some(value.clone()),
|
||||
Expr::Attribute(_) => semantic
|
||||
.resolve_call_path(expr)
|
||||
.as_ref()
|
||||
.and_then(py_stat)
|
||||
.map(Integer::from),
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
left,
|
||||
op,
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_true;
|
||||
use ruff_python_ast::{Expr, ExprAttribute, ExprCall, Stmt, StmtAssign};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `debug=True` in Flask.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Enabling debug mode shows an interactive debugger in the browser if an
|
||||
/// error occurs, and allows running arbitrary Python code from the browser.
|
||||
/// This could leak sensitive information, or allow an attacker to run
|
||||
/// arbitrary code.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import flask
|
||||
///
|
||||
/// app = Flask()
|
||||
///
|
||||
/// app.run(debug=True)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import flask
|
||||
///
|
||||
/// app = Flask()
|
||||
///
|
||||
/// app.run(debug=os.environ["ENV"] == "dev")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Flask documentation: Debug Mode](https://flask.palletsprojects.com/en/latest/quickstart/#debug-mode)
|
||||
#[violation]
|
||||
pub struct FlaskDebugTrue;
|
||||
|
||||
impl Violation for FlaskDebugTrue {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of `debug=True` in Flask app detected")
|
||||
}
|
||||
}
|
||||
|
||||
/// S201
|
||||
pub(crate) fn flask_debug_true(checker: &mut Checker, call: &ExprCall) {
|
||||
let Expr::Attribute(ExprAttribute { attr, value, .. }) = call.func.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if attr.as_str() != "run" {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(debug_argument) = call.arguments.find_keyword("debug") else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !is_const_true(&debug_argument.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Name(name) = value.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(binding_id) = checker.semantic().resolve_name(name) {
|
||||
if let Some(Stmt::Assign(StmtAssign { value, .. })) = checker
|
||||
.semantic()
|
||||
.binding(binding_id)
|
||||
.statement(checker.semantic())
|
||||
{
|
||||
if let Expr::Call(ExprCall { func, .. }) = value.as_ref() {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["flask", "Flask"]))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(FlaskDebugTrue, debug_argument.range()));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pub(crate) use assert_used::*;
|
||||
pub(crate) use bad_file_permissions::*;
|
||||
pub(crate) use exec_used::*;
|
||||
pub(crate) use flask_debug_true::*;
|
||||
pub(crate) use hardcoded_bind_all_interfaces::*;
|
||||
pub(crate) use hardcoded_password_default::*;
|
||||
pub(crate) use hardcoded_password_func_arg::*;
|
||||
@@ -24,6 +25,7 @@ pub(crate) use unsafe_yaml_load::*;
|
||||
mod assert_used;
|
||||
mod bad_file_permissions;
|
||||
mod exec_used;
|
||||
mod flask_debug_true;
|
||||
mod hardcoded_bind_all_interfaces;
|
||||
mod hardcoded_password_default;
|
||||
mod hardcoded_password_func_arg;
|
||||
|
||||
@@ -148,7 +148,7 @@ impl Violation for StartProcessWithNoShell {
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `subprocess.Popen()`](https://docs.python.org/3/library/subprocess.html#subprocess.Popen)
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
/// - [Common Weakness Enumeration: CWE-426](https://cwe.mitre.org/data/definitions/426.html)
|
||||
#[violation]
|
||||
pub struct StartProcessWithPartialPath;
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use num_traits::{One, Zero};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
@@ -57,7 +55,7 @@ pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall)
|
||||
..
|
||||
}) = &keyword.value
|
||||
{
|
||||
if value.is_zero() || value.is_one() {
|
||||
if *value == 0 || *value == 1 {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SnmpInsecureVersion, keyword.range()));
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S201.py:10:9: S201 Use of `debug=True` in Flask app detected
|
||||
|
|
||||
9 | # OK
|
||||
10 | app.run(debug=True)
|
||||
| ^^^^^^^^^^ S201
|
||||
11 |
|
||||
12 | # Errors
|
||||
|
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ mod tests {
|
||||
#[test_case(Rule::JumpStatementInFinally, Path::new("B012.py"))]
|
||||
#[test_case(Rule::LoopVariableOverridesIterator, Path::new("B020.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_2.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_3.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_4.py"))]
|
||||
#[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"))]
|
||||
#[test_case(Rule::RaiseLiteral, Path::new("B016.py"))]
|
||||
#[test_case(Rule::RaiseWithoutFromInsideExcept, Path::new("B904.py"))]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::autofix::edits::pad;
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
@@ -80,17 +81,21 @@ pub(crate) fn getattr_with_constant(
|
||||
let mut diagnostic = Diagnostic::new(GetAttrWithConstant, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
if matches!(
|
||||
obj,
|
||||
Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_)
|
||||
) {
|
||||
format!("{}.{}", checker.locator().slice(obj), value)
|
||||
} else {
|
||||
// Defensively parenthesize any other expressions. For example, attribute accesses
|
||||
// on `int` literals must be parenthesized, e.g., `getattr(1, "real")` becomes
|
||||
// `(1).real`. The same is true for named expressions and others.
|
||||
format!("({}).{}", checker.locator().slice(obj), value)
|
||||
},
|
||||
pad(
|
||||
if matches!(
|
||||
obj,
|
||||
Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_)
|
||||
) {
|
||||
format!("{}.{}", checker.locator().slice(obj), value)
|
||||
} else {
|
||||
// Defensively parenthesize any other expressions. For example, attribute accesses
|
||||
// on `int` literals must be parenthesized, e.g., `getattr(1, "real")` becomes
|
||||
// `(1).real`. The same is true for named expressions and others.
|
||||
format!("({}).{}", checker.locator().slice(obj), value)
|
||||
},
|
||||
expr.range(),
|
||||
checker.locator(),
|
||||
),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ impl Violation for MutableArgumentDefault {
|
||||
fn message(&self) -> String {
|
||||
format!("Do not use mutable data structures for argument defaults")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(format!("Replace with `None`; initialize within function"))
|
||||
}
|
||||
@@ -175,7 +176,7 @@ fn move_initialization(
|
||||
return None;
|
||||
}
|
||||
Edit::insertion(content, locator.line_start(statement.start()))
|
||||
} else if statement.end() == locator.text_len() {
|
||||
} else if locator.full_line_end(statement.end()) == locator.text_len() {
|
||||
// If the statement is at the end of the file, without a trailing newline, insert
|
||||
// _after_ it with an extra newline.
|
||||
Edit::insertion(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user