Compare commits
154 Commits
dhruv/stri
...
pythonplus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f97547b5f | ||
|
|
e944c16c46 | ||
|
|
5f40371ffc | ||
|
|
f7802ad5de | ||
|
|
e832327a56 | ||
|
|
324390607c | ||
|
|
4db5c29f19 | ||
|
|
e9d3f71c90 | ||
|
|
7b3ee2daff | ||
|
|
c2e15f38ee | ||
|
|
d59433b12e | ||
|
|
2bf1882398 | ||
|
|
c269c1a706 | ||
|
|
32d6f84e3d | ||
|
|
93d582d734 | ||
|
|
05b406080a | ||
|
|
3ed707f245 | ||
|
|
c56fb6e15a | ||
|
|
dbf82233b8 | ||
|
|
87afe36c87 | ||
|
|
704fefc7ab | ||
|
|
dacec7377c | ||
|
|
b669306c87 | ||
|
|
b117f33075 | ||
|
|
c746912b9e | ||
|
|
fc7139d9a5 | ||
|
|
f8f56186b3 | ||
|
|
02fc521369 | ||
|
|
4b0666919b | ||
|
|
06284c3700 | ||
|
|
8d73866f70 | ||
|
|
bc693ea13a | ||
|
|
ad84eedc18 | ||
|
|
96a4f95a44 | ||
|
|
bae26b49a6 | ||
|
|
3d7adbc0ed | ||
|
|
c6456b882c | ||
|
|
49eb97879a | ||
|
|
0c84fbb6db | ||
|
|
a892fc755d | ||
|
|
a067d87ccc | ||
|
|
b64f2ea401 | ||
|
|
4bce801065 | ||
|
|
a56d42f183 | ||
|
|
1d97f27335 | ||
|
|
965adbed4b | ||
|
|
c504d7ab11 | ||
|
|
72c9f7e4c9 | ||
|
|
57be3fce90 | ||
|
|
7a675cd822 | ||
|
|
7b4a73d421 | ||
|
|
91af5a4b74 | ||
|
|
461cdad53a | ||
|
|
b9264a5a11 | ||
|
|
ea79f616bc | ||
|
|
f999b1b617 | ||
|
|
fe6afbe406 | ||
|
|
cbd927f346 | ||
|
|
6159a8e532 | ||
|
|
8ea5b08700 | ||
|
|
4c05c258de | ||
|
|
af6ea2f5e4 | ||
|
|
46ab9dec18 | ||
|
|
d441338358 | ||
|
|
72599dafb6 | ||
|
|
7eaec300dd | ||
|
|
184241f99a | ||
|
|
8b749e1d4d | ||
|
|
8dde81a905 | ||
|
|
00300c0d9d | ||
|
|
a6d892b1f4 | ||
|
|
ba4328226d | ||
|
|
64f66cd8fe | ||
|
|
4eac9baf43 | ||
|
|
c27e048ff2 | ||
|
|
737fcfd79e | ||
|
|
84bf333031 | ||
|
|
db25a563f7 | ||
|
|
e725b6fdaf | ||
|
|
fb05d218c3 | ||
|
|
ba7f6783e9 | ||
|
|
7515196245 | ||
|
|
c7431828a7 | ||
|
|
39a3031898 | ||
|
|
c007b175ba | ||
|
|
0cd3b07efa | ||
|
|
c59d82a22e | ||
|
|
8b5daaec7d | ||
|
|
0373b51823 | ||
|
|
b82e87790e | ||
|
|
56d445add9 | ||
|
|
8ecdf5369a | ||
|
|
c9931a548f | ||
|
|
8e0a70cfa3 | ||
|
|
cbafae022d | ||
|
|
cea59b4425 | ||
|
|
40186a26ef | ||
|
|
b53118ed00 | ||
|
|
52f4c1e41b | ||
|
|
eceffe74a0 | ||
|
|
c73c497477 | ||
|
|
c9c98c4fe3 | ||
|
|
72ccb34ba6 | ||
|
|
dcc92f50cf | ||
|
|
a6f32ddc5e | ||
|
|
0293908b71 | ||
|
|
36bc725eaa | ||
|
|
8f92da8b6c | ||
|
|
a1905172a8 | ||
|
|
1791e7d73b | ||
|
|
317d2e4c75 | ||
|
|
8044c24c7e | ||
|
|
a1e8784207 | ||
|
|
8dc22d5793 | ||
|
|
15b87ea8be | ||
|
|
c25f1cd12a | ||
|
|
f5904a20d5 | ||
|
|
77c5561646 | ||
|
|
ab4bd71755 | ||
|
|
5abf662365 | ||
|
|
14fa1c5b52 | ||
|
|
0421c41ff7 | ||
|
|
ad4695d3eb | ||
|
|
8c58ebee37 | ||
|
|
5023874355 | ||
|
|
5554510597 | ||
|
|
1341e064a7 | ||
|
|
bd98d6884b | ||
|
|
761d4d42f1 | ||
|
|
fc8738f52a | ||
|
|
1711bca4a0 | ||
|
|
51ce88bb23 | ||
|
|
36d8b03b5f | ||
|
|
a284c711bf | ||
|
|
8c20f14e62 | ||
|
|
946028e358 | ||
|
|
6fe15e7289 | ||
|
|
7d9ce5049a | ||
|
|
ecd5a7035d | ||
|
|
175c266de3 | ||
|
|
4997c681f1 | ||
|
|
7eafba2a4d | ||
|
|
0f70c99c42 | ||
|
|
ee4efdba96 | ||
|
|
0d363ab239 | ||
|
|
68b8abf9c6 | ||
|
|
1c8851e5fb | ||
|
|
4ac19993cf | ||
|
|
5cd3c6ef07 | ||
|
|
e94a2615a8 | ||
|
|
77f577cba7 | ||
|
|
49a46c2880 | ||
|
|
67e17e2750 | ||
|
|
e1928be36e |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -2,6 +2,8 @@
|
||||
|
||||
crates/ruff_linter/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
|
||||
crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
|
||||
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_2.py text eol=crlf
|
||||
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py text eol=crlf
|
||||
|
||||
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
|
||||
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf
|
||||
|
||||
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@@ -7,3 +7,9 @@
|
||||
|
||||
# Jupyter
|
||||
/crates/ruff_linter/src/jupyter/ @dhruvmanila
|
||||
/crates/ruff_formatter/ @MichaReiser
|
||||
/crates/ruff_python_formatter/ @MichaReiser
|
||||
/crates/ruff_python_parser/ @MichaReiser
|
||||
|
||||
# flake8-pyi
|
||||
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood
|
||||
|
||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -3,6 +3,8 @@ Thank you for taking the time to report an issue! We're glad to have you involve
|
||||
|
||||
If you're filing a bug report, please consider including the following information:
|
||||
|
||||
* List of keywords you searched for before creating this issue. Write them down here so that others can find this issue more easily and help provide feedback.
|
||||
e.g. "RUF001", "unused variable", "Jupyter notebook"
|
||||
* A minimal code snippet that reproduces the bug.
|
||||
* The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
|
||||
* The current Ruff settings (any relevant sections from your `pyproject.toml`).
|
||||
|
||||
13
.github/workflows/ci.yaml
vendored
13
.github/workflows/ci.yaml
vendored
@@ -133,7 +133,7 @@ jobs:
|
||||
env:
|
||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ruff
|
||||
path: target/debug/ruff
|
||||
@@ -238,7 +238,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Download comparison Ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
@@ -250,6 +250,7 @@ jobs:
|
||||
with:
|
||||
name: ruff
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
|
||||
- name: Install ruff-ecosystem
|
||||
@@ -324,13 +325,13 @@ jobs:
|
||||
run: |
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload PR Number
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload Results
|
||||
with:
|
||||
name: ecosystem-result
|
||||
@@ -471,7 +472,7 @@ jobs:
|
||||
- determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
steps:
|
||||
- uses: extractions/setup-just@v1
|
||||
- uses: extractions/setup-just@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -484,7 +485,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Download development ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
|
||||
76
.github/workflows/release.yaml
vendored
76
.github/workflows/release.yaml
vendored
@@ -52,9 +52,9 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload sdist"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-sdist
|
||||
path: dist
|
||||
|
||||
macos-x86_64:
|
||||
@@ -80,9 +80,9 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-macos-x86_64
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -90,9 +90,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/x86_64-apple-darwin/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-macos-x86_64
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -119,9 +119,9 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-aarch64-apple-darwin
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -129,9 +129,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/aarch64-apple-darwin/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-aarch64-apple-darwin
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -170,9 +170,9 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
shell: bash
|
||||
@@ -181,9 +181,9 @@ jobs:
|
||||
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
|
||||
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-${{ matrix.platform.target }}
|
||||
path: |
|
||||
*.zip
|
||||
*.sha256
|
||||
@@ -218,9 +218,9 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -228,9 +228,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-${{ matrix.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -251,8 +251,12 @@ jobs:
|
||||
arch: s390x
|
||||
- target: powerpc64le-unknown-linux-gnu
|
||||
arch: ppc64le
|
||||
# see https://github.com/astral-sh/ruff/issues/10073
|
||||
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
|
||||
- target: powerpc64-unknown-linux-gnu
|
||||
arch: ppc64
|
||||
# see https://github.com/astral-sh/ruff/issues/10073
|
||||
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -285,9 +289,9 @@ jobs:
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -295,9 +299,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-${{ matrix.platform.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -337,9 +341,9 @@ jobs:
|
||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
.venv/bin/ruff check --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -347,9 +351,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-${{ matrix.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -394,9 +398,9 @@ jobs:
|
||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
.venv/bin/ruff check --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -404,9 +408,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-${{ matrix.platform.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -463,10 +467,11 @@ jobs:
|
||||
# For pypi trusted publishing
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
pattern: wheels-*
|
||||
path: wheels
|
||||
merge-multiple: true
|
||||
- name: Publish to PyPi
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
@@ -506,12 +511,13 @@ jobs:
|
||||
# For GitHub release publishing
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
pattern: binaries-*
|
||||
path: binaries
|
||||
merge-multiple: true
|
||||
- name: "Publish to GitHub"
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: true
|
||||
files: binaries/*
|
||||
|
||||
@@ -1,5 +1,56 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Ruff 2024.2 style
|
||||
|
||||
The formatter now formats code according to the Ruff 2024.2 style guide. Read the [changelog](./CHANGELOG.md#030) for a detailed list of stabilized style changes.
|
||||
|
||||
### `isort`: Use one blank line after imports in typing stub files ([#9971](https://github.com/astral-sh/ruff/pull/9971))
|
||||
|
||||
Previously, Ruff used one or two blank lines (or the number configured by `isort.lines-after-imports`) after imports in typing stub files (`.pyi` files).
|
||||
The [typing style guide for stubs](https://typing.readthedocs.io/en/latest/source/stubs.html#style-guide) recommends using at most 1 blank line for grouping.
|
||||
As of this release, `isort` now always uses one blank line after imports in stub files, the same as the formatter.
|
||||
|
||||
### `build` is no longer excluded by default ([#10093](https://github.com/astral-sh/ruff/pull/10093))
|
||||
|
||||
Ruff maintains a list of directories and files that are excluded by default. This list now consists of the following patterns:
|
||||
|
||||
- `.bzr`
|
||||
- `.direnv`
|
||||
- `.eggs`
|
||||
- `.git`
|
||||
- `.git-rewrite`
|
||||
- `.hg`
|
||||
- `.ipynb_checkpoints`
|
||||
- `.mypy_cache`
|
||||
- `.nox`
|
||||
- `.pants.d`
|
||||
- `.pyenv`
|
||||
- `.pytest_cache`
|
||||
- `.pytype`
|
||||
- `.ruff_cache`
|
||||
- `.svn`
|
||||
- `.tox`
|
||||
- `.venv`
|
||||
- `.vscode`
|
||||
- `__pypackages__`
|
||||
- `_build`
|
||||
- `buck-out`
|
||||
- `dist`
|
||||
- `node_modules`
|
||||
- `site-packages`
|
||||
- `venv`
|
||||
|
||||
Previously, the `build` directory was included in this list. However, the `build` directory tends to be a not-unpopular directory
|
||||
name, and excluding it by default caused confusion. Ruff now no longer excludes `build` except if it is excluded by a `.gitignore` file
|
||||
or because it is listed in `extend-exclude`.
|
||||
|
||||
### `--format` is no longer a valid `rule` or `linter` command option
|
||||
|
||||
Previously, `ruff rule` and `ruff linter` accepted the `--format <FORMAT>` option as an alias for `--output-format`. Ruff no longer
|
||||
supports this alias. Please use `ruff rule --output-format <FORMAT>` and `ruff linter --output-format <FORMAT>` instead.
|
||||
|
||||
## 0.1.9
|
||||
|
||||
### `site-packages` is now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))
|
||||
|
||||
120
CHANGELOG.md
120
CHANGELOG.md
@@ -1,5 +1,125 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Preview features
|
||||
|
||||
- Improve single-`with` item formatting for Python 3.8 or older ([#10276](https://github.com/astral-sh/ruff/pull/10276))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pyupgrade`\] Allow fixes for f-string rule regardless of line length (`UP032`) ([#10263](https://github.com/astral-sh/ruff/pull/10263))
|
||||
- \[`pycodestyle`\] Include actual conditions in E712 diagnostics ([#10254](https://github.com/astral-sh/ruff/pull/10254))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix trailing kwargs end of line comment after slash ([#10297](https://github.com/astral-sh/ruff/pull/10297))
|
||||
- Fix unstable `with` items formatting ([#10274](https://github.com/astral-sh/ruff/pull/10274))
|
||||
- Avoid repeating function calls in f-string conversions ([#10265](https://github.com/astral-sh/ruff/pull/10265))
|
||||
- Fix E203 false positive for slices in format strings ([#10280](https://github.com/astral-sh/ruff/pull/10280))
|
||||
- Fix incorrect `Parameter` range for `*args` and `**kwargs` ([#10283](https://github.com/astral-sh/ruff/pull/10283))
|
||||
- Treat `typing.Annotated` subscripts as type definitions ([#10285](https://github.com/astral-sh/ruff/pull/10285))
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`pycodestyle`\] Fix E301 not triggering on decorated methods. ([#10117](https://github.com/astral-sh/ruff/pull/10117))
|
||||
- \[`pycodestyle`\] Respect `isort` settings in blank line rules (`E3*`) ([#10096](https://github.com/astral-sh/ruff/pull/10096))
|
||||
- \[`pycodestyle`\] Make blank lines in typing stub files optional (`E3*`) ([#10098](https://github.com/astral-sh/ruff/pull/10098))
|
||||
- \[`pylint`\] Implement `singledispatch-method` (`E1519`) ([#10140](https://github.com/astral-sh/ruff/pull/10140))
|
||||
- \[`pylint`\] Implement `useless-exception-statement` (`W0133`) ([#10176](https://github.com/astral-sh/ruff/pull/10176))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-debugger`\] Check for use of `debugpy` and `ptvsd` debug modules (#10177) ([#10194](https://github.com/astral-sh/ruff/pull/10194))
|
||||
- \[`pyupgrade`\] Generate diagnostic for all valid f-string conversions regardless of line length (`UP032`) ([#10238](https://github.com/astral-sh/ruff/pull/10238))
|
||||
- \[`pep8_naming`\] Add fixes for `N804` and `N805` ([#10215](https://github.com/astral-sh/ruff/pull/10215))
|
||||
|
||||
### CLI
|
||||
|
||||
- Colorize the output of `ruff format --diff` ([#10110](https://github.com/astral-sh/ruff/pull/10110))
|
||||
- Make `--config` and `--isolated` global flags ([#10150](https://github.com/astral-sh/ruff/pull/10150))
|
||||
- Correctly expand tildes and environment variables in paths passed to `--config` ([#10219](https://github.com/astral-sh/ruff/pull/10219))
|
||||
|
||||
### Configuration
|
||||
|
||||
- Accept a PEP 440 version specifier for `required-version` ([#10216](https://github.com/astral-sh/ruff/pull/10216))
|
||||
- Implement isort's `default-section` setting ([#10149](https://github.com/astral-sh/ruff/pull/10149))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Remove trailing space from `CapWords` message ([#10220](https://github.com/astral-sh/ruff/pull/10220))
|
||||
- Respect external codes in file-level exemptions ([#10203](https://github.com/astral-sh/ruff/pull/10203))
|
||||
- \[`flake8-raise`\] Avoid false-positives for parens-on-raise with `future.exception()` (`RSE102`) ([#10206](https://github.com/astral-sh/ruff/pull/10206))
|
||||
- \[`pylint`\] Add fix for unary expressions in `PLC2801` ([#9587](https://github.com/astral-sh/ruff/pull/9587))
|
||||
- \[`ruff`\] Fix RUF028 not allowing `# fmt: skip` on match cases ([#10178](https://github.com/astral-sh/ruff/pull/10178))
|
||||
|
||||
## 0.3.0
|
||||
|
||||
This release introduces the new Ruff formatter 2024.2 style and adds a new lint rule to
|
||||
detect invalid formatter suppression comments.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bandit`\] Remove suspicious-lxml-import (`S410`) ([#10154](https://github.com/astral-sh/ruff/pull/10154))
|
||||
- \[`pycodestyle`\] Allow `os.environ` modifications between imports (`E402`) ([#10066](https://github.com/astral-sh/ruff/pull/10066))
|
||||
- \[`pycodestyle`\] Don't warn about a single whitespace character before a comma in a tuple (`E203`) ([#10094](https://github.com/astral-sh/ruff/pull/10094))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`eradicate`\] Detect commented out `case` statements (`ERA001`) ([#10055](https://github.com/astral-sh/ruff/pull/10055))
|
||||
- \[`eradicate`\] Detect single-line code for `try:`, `except:`, etc. (`ERA001`) ([#10057](https://github.com/astral-sh/ruff/pull/10057))
|
||||
- \[`flake8-boolean-trap`\] Allow boolean positionals in `__post_init__` ([#10027](https://github.com/astral-sh/ruff/pull/10027))
|
||||
- \[`flake8-copyright`\] Allow © in copyright notices ([#10065](https://github.com/astral-sh/ruff/pull/10065))
|
||||
- \[`isort`\]: Use one blank line after imports in typing stub files ([#9971](https://github.com/astral-sh/ruff/pull/9971))
|
||||
- \[`pylint`\] New Rule `dict-iter-missing-items` (`PLE1141`) ([#9845](https://github.com/astral-sh/ruff/pull/9845))
|
||||
- \[`pylint`\] Ignore `sys.version` and `sys.platform` (`PLR1714`) ([#10054](https://github.com/astral-sh/ruff/pull/10054))
|
||||
- \[`pyupgrade`\] Detect literals with unary operators (`UP018`) ([#10060](https://github.com/astral-sh/ruff/pull/10060))
|
||||
- \[`ruff`\] Expand rule for `list(iterable).pop(0)` idiom (`RUF015`) ([#10148](https://github.com/astral-sh/ruff/pull/10148))
|
||||
|
||||
### Formatter
|
||||
|
||||
This release introduces the Ruff 2024.2 style, stabilizing the following changes:
|
||||
|
||||
- Prefer splitting the assignment's value over the target or type annotation ([#8943](https://github.com/astral-sh/ruff/pull/8943))
|
||||
- Remove blank lines before class docstrings ([#9154](https://github.com/astral-sh/ruff/pull/9154))
|
||||
- Wrap multiple context managers in `with` parentheses when targeting Python 3.9 or newer ([#9222](https://github.com/astral-sh/ruff/pull/9222))
|
||||
- Add a blank line after nested classes with a dummy body (`...`) in typing stub files ([#9155](https://github.com/astral-sh/ruff/pull/9155))
|
||||
- Reduce vertical spacing for classes and functions with a dummy (`...`) body ([#7440](https://github.com/astral-sh/ruff/issues/7440), [#9240](https://github.com/astral-sh/ruff/pull/9240))
|
||||
- Add a blank line after the module docstring ([#8283](https://github.com/astral-sh/ruff/pull/8283))
|
||||
- Parenthesize long type hints in assignments ([#9210](https://github.com/astral-sh/ruff/pull/9210))
|
||||
- Preserve indent for single multiline-string call-expressions ([#9673](https://github.com/astral-sh/ruff/pull/9637))
|
||||
- Normalize hex escape and unicode escape sequences ([#9280](https://github.com/astral-sh/ruff/pull/9280))
|
||||
- Format module docstrings ([#9725](https://github.com/astral-sh/ruff/pull/9725))
|
||||
|
||||
### CLI
|
||||
|
||||
- Explicitly disallow `extend` as part of a `--config` flag ([#10135](https://github.com/astral-sh/ruff/pull/10135))
|
||||
- Remove `build` from the default exclusion list ([#10093](https://github.com/astral-sh/ruff/pull/10093))
|
||||
- Deprecate `ruff <path>`, `ruff --explain`, `ruff --clean`, and `ruff --generate-shell-completion` in favor of `ruff check <path>`, `ruff rule`, `ruff clean`, and `ruff generate-shell-completion` ([#10169](https://github.com/astral-sh/ruff/pull/10169))
|
||||
- Remove the deprecated CLI option `--format` from `ruff rule` and `ruff linter` ([#10170](https://github.com/astral-sh/ruff/pull/10170))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-bugbear`\] Avoid adding default initializers to stubs (`B006`) ([#10152](https://github.com/astral-sh/ruff/pull/10152))
|
||||
- \[`flake8-type-checking`\] Respect runtime-required decorators for function signatures ([#10091](https://github.com/astral-sh/ruff/pull/10091))
|
||||
- \[`pycodestyle`\] Mark fixes overlapping with a multiline string as unsafe (`W293`) ([#10049](https://github.com/astral-sh/ruff/pull/10049))
|
||||
- \[`pydocstyle`\] Trim whitespace when removing blank lines after section (`D413`) ([#10162](https://github.com/astral-sh/ruff/pull/10162))
|
||||
- \[`pylint`\] Delete entire statement, including semicolons (`PLR0203`) ([#10074](https://github.com/astral-sh/ruff/pull/10074))
|
||||
- \[`ruff`\] Avoid f-string false positives in `gettext` calls (`RUF027`) ([#10118](https://github.com/astral-sh/ruff/pull/10118))
|
||||
- Fix `ruff` crashing on PowerPC systems because of too small page size ([#10080](https://github.com/astral-sh/ruff/pull/10080))
|
||||
|
||||
### Performance
|
||||
|
||||
- Add cold attribute to less likely printer queue branches in the formatter ([#10121](https://github.com/astral-sh/ruff/pull/10121))
|
||||
- Skip unnecessary string normalization in the formatter ([#10116](https://github.com/astral-sh/ruff/pull/10116))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Remove "Beta" Label from formatter documentation ([#10144](https://github.com/astral-sh/ruff/pull/10144))
|
||||
- `line-length` option: fix link to `pycodestyle.max-line-length` ([#10136](https://github.com/astral-sh/ruff/pull/10136))
|
||||
|
||||
## 0.2.2
|
||||
|
||||
Highlights include:
|
||||
|
||||
@@ -316,7 +316,7 @@ To preview any changes to the documentation locally:
|
||||
```
|
||||
|
||||
The documentation should then be available locally at
|
||||
[http://127.0.0.1:8000/docs/](http://127.0.0.1:8000/docs/).
|
||||
[http://127.0.0.1:8000/ruff/](http://127.0.0.1:8000/ruff/).
|
||||
|
||||
## Release Process
|
||||
|
||||
@@ -329,13 +329,13 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
||||
|
||||
### Creating a new release
|
||||
|
||||
We use an experimental in-house tool for managing releases.
|
||||
|
||||
1. Install `rooster`: `pip install git+https://github.com/zanieb/rooster@main`
|
||||
1. Run `rooster release`; this command will:
|
||||
1. Install `uv`: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
||||
1. Run `./scripts/release/bump.sh`; this command will:
|
||||
- Generate a temporary virtual environment with `rooster`
|
||||
- Generate a changelog entry in `CHANGELOG.md`
|
||||
- Update versions in `pyproject.toml` and `Cargo.toml`
|
||||
- Update references to versions in the `README.md` and documentation
|
||||
- Display contributors for the release
|
||||
1. The changelog should then be editorialized for consistency
|
||||
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
|
||||
- Changes should be edited to be user-facing descriptions, avoiding internal details
|
||||
@@ -359,7 +359,7 @@ We use an experimental in-house tool for managing releases.
|
||||
1. Open the draft release in the GitHub release section
|
||||
1. Copy the changelog for the release into the GitHub release
|
||||
- See previous releases for formatting of section headers
|
||||
1. Generate the contributor list with `rooster contributors` and add to the release notes
|
||||
1. Append the contributors from the `bump.sh` script
|
||||
1. If needed, [update the schemastore](https://github.com/astral-sh/ruff/blob/main/scripts/update_schemastore.py).
|
||||
1. One can determine if an update is needed when
|
||||
`git diff old-version-tag new-version-tag -- ruff.schema.json` returns a non-empty diff.
|
||||
|
||||
528
Cargo.lock
generated
528
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
59
Cargo.toml
59
Cargo.toml
@@ -14,50 +14,55 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
aho-corasick = { version = "1.1.2" }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anyhow = { version = "1.0.79" }
|
||||
anyhow = { version = "1.0.80" }
|
||||
argfile = { version = "0.1.6" }
|
||||
assert_cmd = { version = "2.0.13" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
bstr = { version = "1.9.0" }
|
||||
bstr = { version = "1.9.1" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.18", features = ["derive"] }
|
||||
chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.5.2", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.5.1" }
|
||||
clearscreen = { version = "2.0.0" }
|
||||
codspeed-criterion-compat = { version = "2.3.3", default-features = false }
|
||||
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
|
||||
colored = { version = "2.1.0" }
|
||||
configparser = { version = "3.0.3" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version ="3.0.1"}
|
||||
countme = { version = "3.0.1" }
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dirs = { version = "5.0.0" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
env_logger = { version ="0.10.1"}
|
||||
env_logger = { version = "0.10.1" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.23" }
|
||||
fs-err = { version ="2.11.0"}
|
||||
fs-err = { version = "2.11.0" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.14" }
|
||||
hexf-parse = { version ="0.2.1"}
|
||||
hexf-parse = { version = "0.2.1" }
|
||||
ignore = { version = "0.4.22" }
|
||||
imara-diff ={ version = "0.1.5"}
|
||||
imara-diff = { version = "0.1.5" }
|
||||
imperative = { version = "1.0.4" }
|
||||
indicatif ={ version = "0.17.8"}
|
||||
indoc ={ version = "2.0.4"}
|
||||
insta = { version = "1.34.0", feature = ["filters", "glob"] }
|
||||
indicatif = { version = "0.17.8" }
|
||||
indoc = { version = "2.0.4" }
|
||||
insta = { version = "1.35.1", feature = ["filters", "glob"] }
|
||||
insta-cmd = { version = "0.4.0" }
|
||||
is-macro = { version = "0.3.5" }
|
||||
is-wsl = { version = "0.4.0" }
|
||||
itertools = { version = "0.12.1" }
|
||||
js-sys = { version = "0.3.67" }
|
||||
js-sys = { version = "0.3.69" }
|
||||
jod-thread = { version = "0.1.2" }
|
||||
lalrpop-util = { version = "0.20.0", default-features = false }
|
||||
lexical-parse-float = { version = "0.8.0", features = ["format"] }
|
||||
libc = { version = "0.2.153" }
|
||||
libcst = { version = "1.1.0", default-features = false }
|
||||
log = { version = "0.4.17" }
|
||||
lsp-server = { version = "0.7.6" }
|
||||
lsp-types = { version = "0.95.0", features = ["proposed"] }
|
||||
memchr = { version = "2.7.1" }
|
||||
mimalloc = { version ="0.1.39"}
|
||||
mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "6.1.1" }
|
||||
once_cell = { version = "1.19.0" }
|
||||
@@ -75,39 +80,39 @@ regex = { version = "1.10.2" }
|
||||
result-like = { version = "0.5.0" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version ="4.1.0"}
|
||||
semver = { version = "1.0.21" }
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde-wasm-bindgen = { version = "0.6.3" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde-wasm-bindgen = { version = "0.6.4" }
|
||||
serde_json = { version = "1.0.113" }
|
||||
serde_test = { version = "1.0.152" }
|
||||
serde_with = { version = "3.6.0", default-features = false, features = ["macros"] }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
shlex = { version ="1.3.0"}
|
||||
shlex = { version = "1.3.0" }
|
||||
similar = { version = "2.4.0", features = ["inline"] }
|
||||
smallvec = { version = "1.13.1" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.3" }
|
||||
syn = { version = "2.0.40" }
|
||||
tempfile = { version ="3.9.0"}
|
||||
syn = { version = "2.0.51" }
|
||||
tempfile = { version = "3.9.0" }
|
||||
test-case = { version = "3.3.1" }
|
||||
thiserror = { version = "1.0.57" }
|
||||
tikv-jemallocator = { version ="0.5.0"}
|
||||
tikv-jemallocator = { version = "0.5.0" }
|
||||
toml = { version = "0.8.9" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-indicatif = { version = "0.3.6" }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
tracing-tree = { version = "0.2.4" }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unic-ucd-category = { version ="0.9"}
|
||||
unic-ucd-category = { version = "0.9" }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
unicode-width = { version = "0.1.11" }
|
||||
unicode_names2 = { version = "1.2.1" }
|
||||
ureq = { version = "2.9.1" }
|
||||
unicode_names2 = { version = "1.2.2" }
|
||||
ureq = { version = "2.9.6" }
|
||||
url = { version = "2.5.0" }
|
||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
walkdir = { version = "2.3.2" }
|
||||
wasm-bindgen = { version = "0.2.84" }
|
||||
wasm-bindgen = { version = "0.2.92" }
|
||||
wasm-bindgen-test = { version = "0.3.40" }
|
||||
wild = { version = "2" }
|
||||
|
||||
|
||||
35
README.md
35
README.md
@@ -7,8 +7,9 @@
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://github.com/astral-sh/ruff/actions)
|
||||
[](https://discord.com/invite/astral-sh)
|
||||
|
||||
[**Discord**](https://discord.com/invite/astral-sh) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
|
||||
[**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
|
||||
|
||||
An extremely fast Python linter and code formatter, written in Rust.
|
||||
|
||||
@@ -128,7 +129,7 @@ and with [a variety of other package managers](https://docs.astral.sh/ruff/insta
|
||||
To run Ruff as a linter, try any of the following:
|
||||
|
||||
```shell
|
||||
ruff check . # Lint all files in the current directory (and any subdirectories).
|
||||
ruff check # Lint all files in the current directory (and any subdirectories).
|
||||
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories).
|
||||
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`.
|
||||
ruff check path/to/code/to/file.py # Lint `file.py`.
|
||||
@@ -138,7 +139,7 @@ ruff check @arguments.txt # Lint using an input file, treating its con
|
||||
Or, to run Ruff as a formatter:
|
||||
|
||||
```shell
|
||||
ruff format . # Format all files in the current directory (and any subdirectories).
|
||||
ruff format # Format all files in the current directory (and any subdirectories).
|
||||
ruff format path/to/code/ # Format all files in `/path/to/code` (and any subdirectories).
|
||||
ruff format path/to/code/*.py # Format all `.py` files in `/path/to/code`.
|
||||
ruff format path/to/code/to/file.py # Format `file.py`.
|
||||
@@ -150,7 +151,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.2.2
|
||||
rev: v0.3.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -172,7 +173,7 @@ jobs:
|
||||
ruff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: chartboost/ruff-action@v1
|
||||
```
|
||||
|
||||
@@ -182,10 +183,9 @@ Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml`
|
||||
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
|
||||
for a complete list of all configuration options).
|
||||
|
||||
If left unspecified, Ruff's default configuration is equivalent to:
|
||||
If left unspecified, Ruff's default configuration is equivalent to the following `ruff.toml` file:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
exclude = [
|
||||
".bzr",
|
||||
@@ -223,7 +223,7 @@ indent-width = 4
|
||||
# Assume Python 3.8
|
||||
target-version = "py38"
|
||||
|
||||
[tool.ruff.lint]
|
||||
[lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
ignore = []
|
||||
@@ -235,7 +235,7 @@ unfixable = []
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
[tool.ruff.format]
|
||||
[format]
|
||||
# Like Black, use double quotes for strings.
|
||||
quote-style = "double"
|
||||
|
||||
@@ -249,11 +249,20 @@ skip-magic-trailing-comma = false
|
||||
line-ending = "auto"
|
||||
```
|
||||
|
||||
Some configuration options can be provided via the command-line, such as those related to
|
||||
rule enablement and disablement, file discovery, and logging level:
|
||||
Note that, in a `pyproject.toml`, each section header should be prefixed with `tool.ruff`. For
|
||||
example, `[lint]` should be replaced with `[tool.ruff.lint]`.
|
||||
|
||||
Some configuration options can be provided via dedicated command-line arguments, such as those
|
||||
related to rule enablement and disablement, file discovery, and logging level:
|
||||
|
||||
```shell
|
||||
ruff check path/to/code/ --select F401 --select F403 --quiet
|
||||
ruff check --select F401 --select F403 --quiet
|
||||
```
|
||||
|
||||
The remaining configuration options can be provided through a catch-all `--config` argument:
|
||||
|
||||
```shell
|
||||
ruff check --config "lint.per-file-ignores = {'some_file.py' = ['F841']}"
|
||||
```
|
||||
|
||||
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format`
|
||||
@@ -378,6 +387,7 @@ Ruff is released under the MIT license.
|
||||
|
||||
Ruff is used by a number of major open-source projects and companies, including:
|
||||
|
||||
- [Albumentations](https://github.com/albumentations-team/albumentations)
|
||||
- Amazon ([AWS SAM](https://github.com/aws/serverless-application-model))
|
||||
- Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python))
|
||||
- [Apache Airflow](https://github.com/apache/airflow)
|
||||
@@ -404,6 +414,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Ibis](https://github.com/ibis-project/ibis)
|
||||
- [ivy](https://github.com/unifyai/ivy)
|
||||
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
||||
- [Kraken Tech](https://kraken.tech/)
|
||||
- [LangChain](https://github.com/hwchase17/langchain)
|
||||
- [Litestar](https://litestar.dev/)
|
||||
- [LlamaIndex](https://github.com/jerryjliu/llama_index)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.2.2"
|
||||
version = "0.3.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -20,6 +20,7 @@ ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
||||
ruff_server = { path = "../ruff_server" }
|
||||
ruff_source_file = { path = "../ruff_source_file" }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
ruff_workspace = { path = "../ruff_workspace" }
|
||||
@@ -52,6 +53,8 @@ tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
tracing-subscriber = { workspace = true, features = ["registry"]}
|
||||
tracing-tree = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
wild = { workspace = true }
|
||||
|
||||
|
||||
@@ -28,6 +28,52 @@ use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
use ruff_workspace::options::{Options, PycodestyleOptions};
|
||||
use ruff_workspace::resolver::ConfigurationTransformer;
|
||||
|
||||
/// All configuration options that can be passed "globally",
|
||||
/// i.e., can be passed to all subcommands
|
||||
#[derive(Debug, Default, Clone, clap::Args)]
|
||||
pub struct GlobalConfigArgs {
|
||||
#[clap(flatten)]
|
||||
log_level_args: LogLevelArgs,
|
||||
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
|
||||
/// or a TOML `<KEY> = <VALUE>` pair
|
||||
/// (such as you might find in a `ruff.toml` configuration file)
|
||||
/// overriding a specific configuration option.
|
||||
/// Overrides of individual settings using this option always take precedence
|
||||
/// over all configuration files, including configuration files that were also
|
||||
/// specified using `--config`.
|
||||
#[arg(
|
||||
long,
|
||||
action = clap::ArgAction::Append,
|
||||
value_name = "CONFIG_OPTION",
|
||||
value_parser = ConfigArgumentParser,
|
||||
global = true,
|
||||
help_heading = "Global options",
|
||||
)]
|
||||
pub config: Vec<SingleConfigArgument>,
|
||||
/// Ignore all configuration files.
|
||||
//
|
||||
// Note: We can't mark this as conflicting with `--config` here
|
||||
// as `--config` can be used for specifying configuration overrides
|
||||
// as well as configuration files.
|
||||
// Specifying a configuration file conflicts with `--isolated`;
|
||||
// specifying a configuration override does not.
|
||||
// If a user specifies `ruff check --isolated --config=ruff.toml`,
|
||||
// we emit an error later on, after the initial parsing by clap.
|
||||
#[arg(long, help_heading = "Global options", global = true)]
|
||||
pub isolated: bool,
|
||||
}
|
||||
|
||||
impl GlobalConfigArgs {
|
||||
pub fn log_level(&self) -> LogLevel {
|
||||
LogLevel::from(&self.log_level_args)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn partition(self) -> (LogLevel, Vec<SingleConfigArgument>, bool) {
|
||||
(self.log_level(), self.config, self.isolated)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
author,
|
||||
@@ -38,9 +84,9 @@ use ruff_workspace::resolver::ConfigurationTransformer;
|
||||
#[command(version)]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
pub(crate) command: Command,
|
||||
#[clap(flatten)]
|
||||
pub log_level_args: LogLevelArgs,
|
||||
pub(crate) global_options: GlobalConfigArgs,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
@@ -63,10 +109,6 @@ pub enum Command {
|
||||
/// Output format
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
output_format: HelpFormat,
|
||||
|
||||
/// Output format (Deprecated: Use `--output-format` instead).
|
||||
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
|
||||
format: Option<HelpFormat>,
|
||||
},
|
||||
/// List or describe the available configuration options.
|
||||
Config { option: Option<String> },
|
||||
@@ -75,10 +117,6 @@ pub enum Command {
|
||||
/// Output format
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
output_format: HelpFormat,
|
||||
|
||||
/// Output format (Deprecated: Use `--output-format` instead).
|
||||
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
|
||||
format: Option<HelpFormat>,
|
||||
},
|
||||
/// Clear any caches in the current directory and any subdirectories.
|
||||
#[clap(alias = "--clean")]
|
||||
@@ -88,6 +126,8 @@ pub enum Command {
|
||||
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
||||
/// Run the Ruff formatter on the given files or directories.
|
||||
Format(FormatCommand),
|
||||
/// Run the language server.
|
||||
Server(ServerCommand),
|
||||
/// Display Ruff's version
|
||||
Version {
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
@@ -161,20 +201,6 @@ pub struct CheckCommand {
|
||||
preview: bool,
|
||||
#[clap(long, overrides_with("preview"), hide = true)]
|
||||
no_preview: bool,
|
||||
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
|
||||
/// or a TOML `<KEY> = <VALUE>` pair
|
||||
/// (such as you might find in a `ruff.toml` configuration file)
|
||||
/// overriding a specific configuration option.
|
||||
/// Overrides of individual settings using this option always take precedence
|
||||
/// over all configuration files, including configuration files that were also
|
||||
/// specified using `--config`.
|
||||
#[arg(
|
||||
long,
|
||||
action = clap::ArgAction::Append,
|
||||
value_name = "CONFIG_OPTION",
|
||||
value_parser = ConfigArgumentParser,
|
||||
)]
|
||||
pub config: Vec<SingleConfigArgument>,
|
||||
/// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
|
||||
#[arg(
|
||||
long,
|
||||
@@ -306,17 +332,6 @@ pub struct CheckCommand {
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||
pub no_cache: bool,
|
||||
/// Ignore all configuration files.
|
||||
//
|
||||
// Note: We can't mark this as conflicting with `--config` here
|
||||
// as `--config` can be used for specifying configuration overrides
|
||||
// as well as configuration files.
|
||||
// Specifying a configuration file conflicts with `--isolated`;
|
||||
// specifying a configuration override does not.
|
||||
// If a user specifies `ruff check --isolated --config=ruff.toml`,
|
||||
// we emit an error later on, after the initial parsing by clap.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub isolated: bool,
|
||||
/// Path to the cache directory.
|
||||
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
@@ -408,20 +423,6 @@ pub struct FormatCommand {
|
||||
/// difference between the current file and how the formatted file would look like.
|
||||
#[arg(long)]
|
||||
pub diff: bool,
|
||||
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
|
||||
/// or a TOML `<KEY> = <VALUE>` pair
|
||||
/// (such as you might find in a `ruff.toml` configuration file)
|
||||
/// overriding a specific configuration option.
|
||||
/// Overrides of individual settings using this option always take precedence
|
||||
/// over all configuration files, including configuration files that were also
|
||||
/// specified using `--config`.
|
||||
#[arg(
|
||||
long,
|
||||
action = clap::ArgAction::Append,
|
||||
value_name = "CONFIG_OPTION",
|
||||
value_parser = ConfigArgumentParser,
|
||||
)]
|
||||
pub config: Vec<SingleConfigArgument>,
|
||||
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||
@@ -462,17 +463,6 @@ pub struct FormatCommand {
|
||||
/// Set the line-length.
|
||||
#[arg(long, help_heading = "Format configuration")]
|
||||
pub line_length: Option<LineLength>,
|
||||
/// Ignore all configuration files.
|
||||
//
|
||||
// Note: We can't mark this as conflicting with `--config` here
|
||||
// as `--config` can be used for specifying configuration overrides
|
||||
// as well as configuration files.
|
||||
// Specifying a configuration file conflicts with `--isolated`;
|
||||
// specifying a configuration override does not.
|
||||
// If a user specifies `ruff check --isolated --config=ruff.toml`,
|
||||
// we emit an error later on, after the initial parsing by clap.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub isolated: bool,
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
@@ -506,6 +496,13 @@ pub struct FormatCommand {
|
||||
pub range: Option<FormatRange>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, clap::Parser)]
|
||||
pub struct ServerCommand {
|
||||
/// Enable preview mode; required for regular operation
|
||||
#[arg(long)]
|
||||
pub(crate) preview: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||
pub enum HelpFormat {
|
||||
Text,
|
||||
@@ -513,7 +510,7 @@ pub enum HelpFormat {
|
||||
}
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
#[derive(Debug, clap::Args)]
|
||||
#[derive(Debug, Default, Clone, clap::Args)]
|
||||
pub struct LogLevelArgs {
|
||||
/// Enable verbose logging.
|
||||
#[arg(
|
||||
@@ -561,6 +558,10 @@ impl From<&LogLevelArgs> for LogLevel {
|
||||
/// Configuration-related arguments passed via the CLI.
|
||||
#[derive(Default)]
|
||||
pub struct ConfigArguments {
|
||||
/// Whether the user specified --isolated on the command line
|
||||
pub(crate) isolated: bool,
|
||||
/// The logging level to be used, derived from command-line arguments passed
|
||||
pub(crate) log_level: LogLevel,
|
||||
/// Path to a pyproject.toml or ruff.toml configuration file (etc.).
|
||||
/// Either 0 or 1 configuration file paths may be provided on the command line.
|
||||
config_file: Option<PathBuf>,
|
||||
@@ -581,21 +582,19 @@ impl ConfigArguments {
|
||||
}
|
||||
|
||||
fn from_cli_arguments(
|
||||
config_options: Vec<SingleConfigArgument>,
|
||||
global_options: GlobalConfigArgs,
|
||||
per_flag_overrides: ExplicitConfigOverrides,
|
||||
isolated: bool,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut new = Self {
|
||||
per_flag_overrides,
|
||||
..Self::default()
|
||||
};
|
||||
let (log_level, config_options, isolated) = global_options.partition();
|
||||
let mut config_file: Option<PathBuf> = None;
|
||||
let mut overrides = Configuration::default();
|
||||
|
||||
for option in config_options {
|
||||
match option {
|
||||
SingleConfigArgument::SettingsOverride(overridden_option) => {
|
||||
let overridden_option = Arc::try_unwrap(overridden_option)
|
||||
.unwrap_or_else(|option| option.deref().clone());
|
||||
new.overrides = new.overrides.combine(Configuration::from_options(
|
||||
overrides = overrides.combine(Configuration::from_options(
|
||||
overridden_option,
|
||||
None,
|
||||
&path_dedot::CWD,
|
||||
@@ -614,7 +613,7 @@ The argument `--config={}` cannot be used with `--isolated`
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
if let Some(ref config_file) = new.config_file {
|
||||
if let Some(ref config_file) = config_file {
|
||||
let (first, second) = (config_file.display(), path.display());
|
||||
bail!(
|
||||
"\
|
||||
@@ -625,11 +624,17 @@ You cannot specify more than one configuration file on the command line.
|
||||
"
|
||||
);
|
||||
}
|
||||
new.config_file = Some(path);
|
||||
config_file = Some(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(new)
|
||||
Ok(Self {
|
||||
isolated,
|
||||
log_level,
|
||||
config_file,
|
||||
overrides,
|
||||
per_flag_overrides,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,7 +648,10 @@ impl ConfigurationTransformer for ConfigArguments {
|
||||
impl CheckCommand {
|
||||
/// Partition the CLI into command-line arguments and configuration
|
||||
/// overrides.
|
||||
pub fn partition(self) -> anyhow::Result<(CheckArguments, ConfigArguments)> {
|
||||
pub fn partition(
|
||||
self,
|
||||
global_options: GlobalConfigArgs,
|
||||
) -> anyhow::Result<(CheckArguments, ConfigArguments)> {
|
||||
let check_arguments = CheckArguments {
|
||||
add_noqa: self.add_noqa,
|
||||
diff: self.diff,
|
||||
@@ -652,7 +660,6 @@ impl CheckCommand {
|
||||
exit_zero: self.exit_zero,
|
||||
files: self.files,
|
||||
ignore_noqa: self.ignore_noqa,
|
||||
isolated: self.isolated,
|
||||
no_cache: self.no_cache,
|
||||
output_file: self.output_file,
|
||||
show_files: self.show_files,
|
||||
@@ -696,8 +703,7 @@ impl CheckCommand {
|
||||
extension: self.extension,
|
||||
};
|
||||
|
||||
let config_args =
|
||||
ConfigArguments::from_cli_arguments(self.config, cli_overrides, self.isolated)?;
|
||||
let config_args = ConfigArguments::from_cli_arguments(global_options, cli_overrides)?;
|
||||
Ok((check_arguments, config_args))
|
||||
}
|
||||
}
|
||||
@@ -705,12 +711,14 @@ impl CheckCommand {
|
||||
impl FormatCommand {
|
||||
/// Partition the CLI into command-line arguments and configuration
|
||||
/// overrides.
|
||||
pub fn partition(self) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
|
||||
pub fn partition(
|
||||
self,
|
||||
global_options: GlobalConfigArgs,
|
||||
) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
|
||||
let format_arguments = FormatArguments {
|
||||
check: self.check,
|
||||
diff: self.diff,
|
||||
files: self.files,
|
||||
isolated: self.isolated,
|
||||
no_cache: self.no_cache,
|
||||
stdin_filename: self.stdin_filename,
|
||||
range: self.range,
|
||||
@@ -730,8 +738,7 @@ impl FormatCommand {
|
||||
..ExplicitConfigOverrides::default()
|
||||
};
|
||||
|
||||
let config_args =
|
||||
ConfigArguments::from_cli_arguments(self.config, cli_overrides, self.isolated)?;
|
||||
let config_args = ConfigArguments::from_cli_arguments(global_options, cli_overrides)?;
|
||||
Ok((format_arguments, config_args))
|
||||
}
|
||||
}
|
||||
@@ -745,38 +752,34 @@ fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumeration of various ways in which a --config CLI flag
|
||||
/// could be invalid
|
||||
#[derive(Debug)]
|
||||
enum TomlParseFailureKind {
|
||||
SyntaxError,
|
||||
UnknownOption,
|
||||
enum InvalidConfigFlagReason {
|
||||
InvalidToml(toml::de::Error),
|
||||
/// It was valid TOML, but not a valid ruff config file.
|
||||
/// E.g. the user tried to select a rule that doesn't exist,
|
||||
/// or tried to enable a setting that doesn't exist
|
||||
ValidTomlButInvalidRuffSchema(toml::de::Error),
|
||||
/// It was a valid ruff config file, but the user tried to pass a
|
||||
/// value for `extend` as part of the config override.
|
||||
// `extend` is special, because it affects which config files we look at
|
||||
/// in the first place. We currently only parse --config overrides *after*
|
||||
/// we've combined them with all the arguments from the various config files
|
||||
/// that we found, so trying to override `extend` as part of a --config
|
||||
/// override is forbidden.
|
||||
ExtendPassedViaConfigFlag,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TomlParseFailureKind {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let display = match self {
|
||||
Self::SyntaxError => "The supplied argument is not valid TOML",
|
||||
Self::UnknownOption => {
|
||||
impl InvalidConfigFlagReason {
|
||||
const fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::InvalidToml(_) => "The supplied argument is not valid TOML",
|
||||
Self::ValidTomlButInvalidRuffSchema(_) => {
|
||||
"Could not parse the supplied argument as a `ruff.toml` configuration option"
|
||||
}
|
||||
};
|
||||
write!(f, "{display}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TomlParseFailure {
|
||||
kind: TomlParseFailureKind,
|
||||
underlying_error: toml::de::Error,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TomlParseFailure {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let TomlParseFailure {
|
||||
kind,
|
||||
underlying_error,
|
||||
} = self;
|
||||
let display = format!("{kind}:\n\n{underlying_error}");
|
||||
write!(f, "{}", display.trim_end())
|
||||
Self::ExtendPassedViaConfigFlag => "Cannot include `extend` in a --config flag value",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,27 +821,38 @@ impl TypedValueParser for ConfigArgumentParser {
|
||||
arg: Option<&clap::Arg>,
|
||||
value: &std::ffi::OsStr,
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
let path_to_config_file = PathBuf::from(value);
|
||||
if path_to_config_file.exists() {
|
||||
return Ok(SingleConfigArgument::FilePath(path_to_config_file));
|
||||
// Convert to UTF-8.
|
||||
let Some(value) = value.to_str() else {
|
||||
// But respect non-UTF-8 paths.
|
||||
let path_to_config_file = PathBuf::from(value);
|
||||
if path_to_config_file.is_file() {
|
||||
return Ok(SingleConfigArgument::FilePath(path_to_config_file));
|
||||
}
|
||||
return Err(clap::Error::new(clap::error::ErrorKind::InvalidUtf8));
|
||||
};
|
||||
|
||||
// Expand environment variables and tildes.
|
||||
if let Ok(path_to_config_file) =
|
||||
shellexpand::full(value).map(|config| PathBuf::from(&*config))
|
||||
{
|
||||
if path_to_config_file.is_file() {
|
||||
return Ok(SingleConfigArgument::FilePath(path_to_config_file));
|
||||
}
|
||||
}
|
||||
|
||||
let value = value
|
||||
.to_str()
|
||||
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
|
||||
|
||||
let toml_parse_error = match toml::Table::from_str(value) {
|
||||
Ok(table) => match table.try_into() {
|
||||
Ok(option) => return Ok(SingleConfigArgument::SettingsOverride(Arc::new(option))),
|
||||
Err(underlying_error) => TomlParseFailure {
|
||||
kind: TomlParseFailureKind::UnknownOption,
|
||||
underlying_error,
|
||||
},
|
||||
},
|
||||
Err(underlying_error) => TomlParseFailure {
|
||||
kind: TomlParseFailureKind::SyntaxError,
|
||||
underlying_error,
|
||||
let config_parse_error = match toml::Table::from_str(value) {
|
||||
Ok(table) => match table.try_into::<Options>() {
|
||||
Ok(option) => {
|
||||
if option.extend.is_none() {
|
||||
return Ok(SingleConfigArgument::SettingsOverride(Arc::new(option)));
|
||||
}
|
||||
InvalidConfigFlagReason::ExtendPassedViaConfigFlag
|
||||
}
|
||||
Err(underlying_error) => {
|
||||
InvalidConfigFlagReason::ValidTomlButInvalidRuffSchema(underlying_error)
|
||||
}
|
||||
},
|
||||
Err(underlying_error) => InvalidConfigFlagReason::InvalidToml(underlying_error),
|
||||
};
|
||||
|
||||
let mut new_error = clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
|
||||
@@ -853,6 +867,21 @@ impl TypedValueParser for ConfigArgumentParser {
|
||||
clap::error::ContextValue::String(value.to_string()),
|
||||
);
|
||||
|
||||
let underlying_error = match &config_parse_error {
|
||||
InvalidConfigFlagReason::ExtendPassedViaConfigFlag => {
|
||||
let tip = config_parse_error.description().into();
|
||||
new_error.insert(
|
||||
clap::error::ContextKind::Suggested,
|
||||
clap::error::ContextValue::StyledStrs(vec![tip]),
|
||||
);
|
||||
return Err(new_error);
|
||||
}
|
||||
InvalidConfigFlagReason::InvalidToml(underlying_error)
|
||||
| InvalidConfigFlagReason::ValidTomlButInvalidRuffSchema(underlying_error) => {
|
||||
underlying_error
|
||||
}
|
||||
};
|
||||
|
||||
// small hack so that multiline tips
|
||||
// have the same indent on the left-hand side:
|
||||
let tip_indent = " ".repeat(" tip: ".len());
|
||||
@@ -877,16 +906,20 @@ A `--config` flag must either be a path to a `.toml` configuration file
|
||||
"
|
||||
|
||||
It looks like you were trying to pass a path to a configuration file.
|
||||
The path `{value}` does not exist"
|
||||
The path `{value}` does not point to a configuration file"
|
||||
));
|
||||
}
|
||||
} else if value.contains('=') {
|
||||
tip.push_str(&format!("\n\n{toml_parse_error}"));
|
||||
tip.push_str(&format!(
|
||||
"\n\n{}:\n\n{underlying_error}",
|
||||
config_parse_error.description()
|
||||
));
|
||||
}
|
||||
let tip = tip.trim_end().to_owned().into();
|
||||
|
||||
new_error.insert(
|
||||
clap::error::ContextKind::Suggested,
|
||||
clap::error::ContextValue::StyledStrs(vec![tip.into()]),
|
||||
clap::error::ContextValue::StyledStrs(vec![tip]),
|
||||
);
|
||||
|
||||
Err(new_error)
|
||||
@@ -941,7 +974,6 @@ pub struct CheckArguments {
|
||||
pub exit_zero: bool,
|
||||
pub files: Vec<PathBuf>,
|
||||
pub ignore_noqa: bool,
|
||||
pub isolated: bool,
|
||||
pub no_cache: bool,
|
||||
pub output_file: Option<PathBuf>,
|
||||
pub show_files: bool,
|
||||
@@ -959,7 +991,6 @@ pub struct FormatArguments {
|
||||
pub no_cache: bool,
|
||||
pub diff: bool,
|
||||
pub files: Vec<PathBuf>,
|
||||
pub isolated: bool,
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
pub range: Option<FormatRange>,
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ pub(crate) fn init(path: &Path) -> Result<()> {
|
||||
let gitignore_path = path.join(".gitignore");
|
||||
if !gitignore_path.exists() {
|
||||
let mut file = fs::File::create(gitignore_path)?;
|
||||
file.write_all(b"*")?;
|
||||
file.write_all(b"# Automatically created by ruff.\n*\n")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -61,13 +61,8 @@ impl FormatMode {
|
||||
pub(crate) fn format(
|
||||
cli: FormatArguments,
|
||||
config_arguments: &ConfigArguments,
|
||||
log_level: LogLevel,
|
||||
) -> Result<ExitStatus> {
|
||||
let pyproject_config = resolve(
|
||||
cli.isolated,
|
||||
config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
let pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
|
||||
let mode = FormatMode::from_cli(&cli);
|
||||
let files = resolve_default_files(cli.files, false);
|
||||
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?;
|
||||
@@ -202,7 +197,7 @@ pub(crate) fn format(
|
||||
}
|
||||
|
||||
// Report on the formatting changes.
|
||||
if log_level >= LogLevel::Default {
|
||||
if config_arguments.log_level >= LogLevel::Default {
|
||||
if mode.is_diff() {
|
||||
// Allow piping the diff to e.g. a file by writing the summary to stderr
|
||||
results.write_summary(&mut stderr().lock())?;
|
||||
@@ -532,7 +527,7 @@ impl<'a> FormatResults<'a> {
|
||||
})
|
||||
.sorted_unstable_by_key(|(path, _, _)| *path)
|
||||
{
|
||||
unformatted.diff(formatted, Some(path), f)?;
|
||||
write!(f, "{}", unformatted.diff(formatted, Some(path)).unwrap())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -23,11 +23,7 @@ pub(crate) fn format_stdin(
|
||||
cli: &FormatArguments,
|
||||
config_arguments: &ConfigArguments,
|
||||
) -> Result<ExitStatus> {
|
||||
let pyproject_config = resolve(
|
||||
cli.isolated,
|
||||
config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
let pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
|
||||
|
||||
let mut resolver = Resolver::new(&pyproject_config);
|
||||
warn_incompatible_formatter_settings(&resolver);
|
||||
@@ -122,9 +118,13 @@ fn format_source_code(
|
||||
}
|
||||
FormatMode::Check => {}
|
||||
FormatMode::Diff => {
|
||||
source_kind
|
||||
.diff(formatted, path, &mut stdout().lock())
|
||||
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
|
||||
use std::io::Write;
|
||||
write!(
|
||||
&mut stdout().lock(),
|
||||
"{}",
|
||||
source_kind.diff(formatted, path).unwrap()
|
||||
)
|
||||
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
|
||||
}
|
||||
},
|
||||
FormattedSource::Unchanged => {
|
||||
|
||||
@@ -7,6 +7,7 @@ pub(crate) mod format;
|
||||
pub(crate) mod format_stdin;
|
||||
pub(crate) mod linter;
|
||||
pub(crate) mod rule;
|
||||
pub(crate) mod server;
|
||||
pub(crate) mod show_files;
|
||||
pub(crate) mod show_settings;
|
||||
pub(crate) mod version;
|
||||
|
||||
73
crates/ruff/src/commands/server.rs
Normal file
73
crates/ruff/src/commands/server.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use crate::ExitStatus;
|
||||
use anyhow::Result;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_server::Server;
|
||||
use tracing::{level_filters::LevelFilter, metadata::Level, subscriber::Interest, Metadata};
|
||||
use tracing_subscriber::{
|
||||
layer::{Context, Filter, SubscriberExt},
|
||||
Layer, Registry,
|
||||
};
|
||||
use tracing_tree::time::Uptime;
|
||||
|
||||
pub(crate) fn run_server(preview: bool, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
if !preview {
|
||||
tracing::error!("--preview needs to be provided as a command line argument while the server is still unstable.\nFor example: `ruff server --preview`");
|
||||
return Ok(ExitStatus::Error);
|
||||
}
|
||||
let trace_level = if log_level == LogLevel::Verbose {
|
||||
Level::TRACE
|
||||
} else {
|
||||
Level::DEBUG
|
||||
};
|
||||
|
||||
let subscriber = Registry::default().with(
|
||||
tracing_tree::HierarchicalLayer::default()
|
||||
.with_indent_lines(true)
|
||||
.with_indent_amount(2)
|
||||
.with_bracketed_fields(true)
|
||||
.with_targets(true)
|
||||
.with_writer(|| Box::new(std::io::stderr()))
|
||||
.with_timer(Uptime::default())
|
||||
.with_filter(LoggingFilter { trace_level }),
|
||||
);
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber)?;
|
||||
|
||||
let server = Server::new()?;
|
||||
|
||||
server.run().map(|()| ExitStatus::Success)
|
||||
}
|
||||
|
||||
struct LoggingFilter {
|
||||
trace_level: Level,
|
||||
}
|
||||
|
||||
impl LoggingFilter {
|
||||
fn is_enabled(&self, meta: &Metadata<'_>) -> bool {
|
||||
let filter = if meta.target().starts_with("ruff") {
|
||||
self.trace_level
|
||||
} else {
|
||||
Level::INFO
|
||||
};
|
||||
|
||||
meta.level() <= &filter
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Filter<S> for LoggingFilter {
|
||||
fn enabled(&self, meta: &Metadata<'_>, _cx: &Context<'_, S>) -> bool {
|
||||
self.is_enabled(meta)
|
||||
}
|
||||
|
||||
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
|
||||
if self.is_enabled(meta) {
|
||||
Interest::always()
|
||||
} else {
|
||||
Interest::never()
|
||||
}
|
||||
}
|
||||
|
||||
fn max_level_hint(&self) -> Option<LevelFilter> {
|
||||
Some(LevelFilter::from_level(self.trace_level))
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::ops::{Add, AddAssign};
|
||||
use std::path::Path;
|
||||
|
||||
@@ -289,10 +290,10 @@ pub(crate) fn lint_path(
|
||||
match fix_mode {
|
||||
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
|
||||
flags::FixMode::Diff => {
|
||||
source_kind.diff(
|
||||
transformed.as_ref(),
|
||||
Some(path),
|
||||
write!(
|
||||
&mut io::stdout().lock(),
|
||||
"{}",
|
||||
source_kind.diff(&transformed, Some(path)).unwrap()
|
||||
)?;
|
||||
}
|
||||
flags::FixMode::Generate => {}
|
||||
@@ -442,7 +443,11 @@ pub(crate) fn lint_stdin(
|
||||
flags::FixMode::Diff => {
|
||||
// But only write a diff if it's non-empty.
|
||||
if !fixed.is_empty() {
|
||||
source_kind.diff(transformed.as_ref(), path, &mut io::stdout().lock())?;
|
||||
write!(
|
||||
&mut io::stdout().lock(),
|
||||
"{}",
|
||||
source_kind.diff(&transformed, path).unwrap()
|
||||
)?;
|
||||
}
|
||||
}
|
||||
flags::FixMode::Generate => {}
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::process::ExitCode;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
use anyhow::Result;
|
||||
use args::{GlobalConfigArgs, ServerCommand};
|
||||
use clap::CommandFactory;
|
||||
use colored::Colorize;
|
||||
use log::warn;
|
||||
@@ -18,7 +19,7 @@ use ruff_linter::settings::types::SerializationFormat;
|
||||
use ruff_linter::{fs, warn_user, warn_user_once};
|
||||
use ruff_workspace::Settings;
|
||||
|
||||
use crate::args::{Args, CheckCommand, Command, FormatCommand, HelpFormat};
|
||||
use crate::args::{Args, CheckCommand, Command, FormatCommand};
|
||||
use crate::printer::{Flags as PrinterFlags, Printer};
|
||||
|
||||
pub mod args;
|
||||
@@ -114,20 +115,12 @@ fn resolve_default_files(files: Vec<PathBuf>, is_stdin: bool) -> Vec<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the actual value of the `format` desired from either `output_format`
|
||||
/// or `format`, and warn the user if they're using the deprecated form.
|
||||
fn resolve_help_output_format(output_format: HelpFormat, format: Option<HelpFormat>) -> HelpFormat {
|
||||
if format.is_some() {
|
||||
warn_user!("The `--format` argument is deprecated. Use `--output-format` instead.");
|
||||
}
|
||||
format.unwrap_or(output_format)
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
Args {
|
||||
command,
|
||||
log_level_args,
|
||||
global_options,
|
||||
}: Args,
|
||||
deprecated_alias_warning: Option<&'static str>,
|
||||
) -> Result<ExitStatus> {
|
||||
{
|
||||
let default_panic_hook = std::panic::take_hook();
|
||||
@@ -155,8 +148,11 @@ pub fn run(
|
||||
#[cfg(windows)]
|
||||
assert!(colored::control::set_virtual_terminal(true).is_ok());
|
||||
|
||||
let log_level = LogLevel::from(&log_level_args);
|
||||
set_up_logging(&log_level)?;
|
||||
set_up_logging(global_options.log_level())?;
|
||||
|
||||
if let Some(deprecated_alias_warning) = deprecated_alias_warning {
|
||||
warn_user!("{}", deprecated_alias_warning);
|
||||
}
|
||||
|
||||
match command {
|
||||
Command::Version { output_format } => {
|
||||
@@ -166,10 +162,8 @@ pub fn run(
|
||||
Command::Rule {
|
||||
rule,
|
||||
all,
|
||||
format,
|
||||
mut output_format,
|
||||
output_format,
|
||||
} => {
|
||||
output_format = resolve_help_output_format(output_format, format);
|
||||
if all {
|
||||
commands::rule::rules(output_format)?;
|
||||
}
|
||||
@@ -182,47 +176,46 @@ pub fn run(
|
||||
commands::config::config(option.as_deref())?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Command::Linter {
|
||||
format,
|
||||
mut output_format,
|
||||
} => {
|
||||
output_format = resolve_help_output_format(output_format, format);
|
||||
Command::Linter { output_format } => {
|
||||
commands::linter::linter(output_format)?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Command::Clean => {
|
||||
commands::clean::clean(log_level)?;
|
||||
commands::clean::clean(global_options.log_level())?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Command::GenerateShellCompletion { shell } => {
|
||||
shell.generate(&mut Args::command(), &mut stdout());
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Command::Check(args) => check(args, log_level),
|
||||
Command::Format(args) => format(args, log_level),
|
||||
Command::Check(args) => check(args, global_options),
|
||||
Command::Format(args) => format(args, global_options),
|
||||
Command::Server(args) => server(args, global_options.log_level()),
|
||||
}
|
||||
}
|
||||
|
||||
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let (cli, config_arguments) = args.partition()?;
|
||||
fn format(args: FormatCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
|
||||
let (cli, config_arguments) = args.partition(global_options)?;
|
||||
|
||||
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
|
||||
commands::format_stdin::format_stdin(&cli, &config_arguments)
|
||||
} else {
|
||||
commands::format::format(cli, &config_arguments, log_level)
|
||||
commands::format::format(cli, &config_arguments)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let (cli, config_arguments) = args.partition()?;
|
||||
#[allow(clippy::needless_pass_by_value)] // TODO: remove once we start taking arguments from here
|
||||
fn server(args: ServerCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let ServerCommand { preview } = args;
|
||||
commands::server::run_server(preview, log_level)
|
||||
}
|
||||
|
||||
pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
|
||||
let (cli, config_arguments) = args.partition(global_options)?;
|
||||
|
||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||
// files are present, or files are injected from outside of the hierarchy.
|
||||
let pyproject_config = resolve::resolve(
|
||||
cli.isolated,
|
||||
&config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
let pyproject_config = resolve::resolve(&config_arguments, cli.stdin_filename.as_deref())?;
|
||||
|
||||
let mut writer: Box<dyn Write> = match cli.output_file {
|
||||
Some(path) if !cli.watch => {
|
||||
@@ -313,7 +306,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
}
|
||||
let modifications =
|
||||
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
|
||||
if modifications > 0 && log_level >= LogLevel::Default {
|
||||
if modifications > 0 && config_arguments.log_level >= LogLevel::Default {
|
||||
let s = if modifications == 1 { "" } else { "s" };
|
||||
#[allow(clippy::print_stderr)]
|
||||
{
|
||||
@@ -325,7 +318,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
|
||||
let printer = Printer::new(
|
||||
output_format,
|
||||
log_level,
|
||||
config_arguments.log_level,
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
printer_flags,
|
||||
@@ -382,11 +375,8 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
};
|
||||
|
||||
if matches!(change_kind, ChangeKind::Configuration) {
|
||||
pyproject_config = resolve::resolve(
|
||||
cli.isolated,
|
||||
&config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
pyproject_config =
|
||||
resolve::resolve(&config_arguments, cli.stdin_filename.as_deref())?;
|
||||
}
|
||||
Printer::clear_screen()?;
|
||||
printer.write_to_user("File change detected...\n");
|
||||
|
||||
@@ -27,26 +27,42 @@ pub fn main() -> ExitCode {
|
||||
let mut args =
|
||||
argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX).unwrap();
|
||||
|
||||
// Clap doesn't support default subcommands but we want to run `check` by
|
||||
// default for convenience and backwards-compatibility, so we just
|
||||
// preprocess the arguments accordingly before passing them to Clap.
|
||||
if let Some(arg) = args.get(1) {
|
||||
if arg
|
||||
.to_str()
|
||||
.is_some_and(|arg| !Command::has_subcommand(rewrite_legacy_subcommand(arg)))
|
||||
// We can't use `warn_user` here because logging isn't set up at this point
|
||||
// and we also don't know if the user runs ruff with quiet.
|
||||
// Keep the message and pass it to `run` that is responsible for emitting the warning.
|
||||
let deprecated_alias_warning = match args.get(1).and_then(|arg| arg.to_str()) {
|
||||
// Deprecated aliases that are handled by clap
|
||||
Some("--explain") => {
|
||||
Some("`ruff --explain <RULE>` is deprecated. Use `ruff rule <RULE>` instead.")
|
||||
}
|
||||
Some("--clean") => {
|
||||
Some("`ruff --clean` is deprecated. Use `ruff clean` instead.")
|
||||
}
|
||||
Some("--generate-shell-completion") => {
|
||||
Some("`ruff --generate-shell-completion <SHELL>` is deprecated. Use `ruff generate-shell-completion <SHELL>` instead.")
|
||||
}
|
||||
// Deprecated `ruff` alias to `ruff check`
|
||||
// Clap doesn't support default subcommands but we want to run `check` by
|
||||
// default for convenience and backwards-compatibility, so we just
|
||||
// preprocess the arguments accordingly before passing them to Clap.
|
||||
Some(arg) if !Command::has_subcommand(arg)
|
||||
&& arg != "-h"
|
||||
&& arg != "--help"
|
||||
&& arg != "-V"
|
||||
&& arg != "--version"
|
||||
&& arg != "help"
|
||||
{
|
||||
args.insert(1, "check".into());
|
||||
}
|
||||
}
|
||||
&& arg != "help" => {
|
||||
|
||||
{
|
||||
args.insert(1, "check".into());
|
||||
Some("`ruff <path>` is deprecated. Use `ruff check <path>` instead.")
|
||||
}
|
||||
},
|
||||
_ => None
|
||||
};
|
||||
|
||||
let args = Args::parse_from(args);
|
||||
|
||||
match run(args) {
|
||||
match run(args, deprecated_alias_warning) {
|
||||
Ok(code) => code.into(),
|
||||
Err(err) => {
|
||||
#[allow(clippy::print_stderr)]
|
||||
@@ -65,12 +81,3 @@ pub fn main() -> ExitCode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_legacy_subcommand(cmd: &str) -> &str {
|
||||
match cmd {
|
||||
"--explain" => "rule",
|
||||
"--clean" => "clean",
|
||||
"--generate-shell-completion" => "generate-shell-completion",
|
||||
cmd => cmd,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,8 @@ impl Printer {
|
||||
} else if remaining > 0 {
|
||||
let s = if remaining == 1 { "" } else { "s" };
|
||||
writeln!(writer, "Found {remaining} error{s}.")?;
|
||||
} else if remaining == 0 {
|
||||
writeln!(writer, "All checks passed!")?;
|
||||
}
|
||||
|
||||
if let Some(fixables) = fixables {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
@@ -16,12 +16,11 @@ use crate::args::ConfigArguments;
|
||||
/// Resolve the relevant settings strategy and defaults for the current
|
||||
/// invocation.
|
||||
pub fn resolve(
|
||||
isolated: bool,
|
||||
config_arguments: &ConfigArguments,
|
||||
stdin_filename: Option<&Path>,
|
||||
) -> Result<PyprojectConfig> {
|
||||
// First priority: if we're running in isolated mode, use the default settings.
|
||||
if isolated {
|
||||
if config_arguments.isolated {
|
||||
let config = config_arguments.transform(Configuration::default());
|
||||
let settings = config.into_settings(&path_dedot::CWD)?;
|
||||
debug!("Isolated mode, not reading any pyproject.toml");
|
||||
@@ -35,13 +34,8 @@ pub fn resolve(
|
||||
// Second priority: the user specified a `pyproject.toml` file. Use that
|
||||
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
||||
// current working directory. (This matches ESLint's behavior.)
|
||||
if let Some(pyproject) = config_arguments
|
||||
.config_file()
|
||||
.map(|config| config.display().to_string())
|
||||
.map(|config| shellexpand::full(&config).map(|config| PathBuf::from(config.as_ref())))
|
||||
.transpose()?
|
||||
{
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, config_arguments)?;
|
||||
if let Some(pyproject) = config_arguments.config_file() {
|
||||
let settings = resolve_root_settings(pyproject, Relativity::Cwd, config_arguments)?;
|
||||
debug!(
|
||||
"Using user-specified configuration file at: {}",
|
||||
pyproject.display()
|
||||
@@ -49,7 +43,7 @@ pub fn resolve(
|
||||
return Ok(PyprojectConfig::new(
|
||||
PyprojectDiscoveryStrategy::Fixed,
|
||||
settings,
|
||||
Some(pyproject),
|
||||
Some(pyproject.to_path_buf()),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@ const STDIN: &str = "l = 1";
|
||||
fn ruff_check(show_source: Option<bool>, output_format: Option<String>) -> Command {
|
||||
let mut cmd = Command::new(get_cargo_bin(BIN_NAME));
|
||||
let output_format = output_format.unwrap_or(format!("{}", SerializationFormat::default(false)));
|
||||
cmd.arg("--output-format");
|
||||
cmd.arg(output_format);
|
||||
cmd.arg("--no-cache");
|
||||
cmd.arg("check")
|
||||
.arg("--output-format")
|
||||
.arg(output_format)
|
||||
.arg("--no-cache");
|
||||
match show_source {
|
||||
Some(true) => {
|
||||
cmd.arg("--show-source");
|
||||
|
||||
@@ -7,10 +7,15 @@ use std::str;
|
||||
|
||||
use anyhow::Result;
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use regex::escape;
|
||||
use tempfile::TempDir;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
fn tempdir_filter(tempdir: &TempDir) -> String {
|
||||
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_options() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
@@ -18,7 +23,7 @@ fn default_options() {
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print('Should\'t change quotes')
|
||||
print('Shouldn\'t change quotes')
|
||||
|
||||
|
||||
if condition:
|
||||
@@ -33,7 +38,7 @@ if condition:
|
||||
arg1,
|
||||
arg2,
|
||||
):
|
||||
print("Should't change quotes")
|
||||
print("Shouldn't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
@@ -106,7 +111,7 @@ fn nonexistent_config_file() {
|
||||
option
|
||||
|
||||
It looks like you were trying to pass a path to a configuration file.
|
||||
The path `foo.toml` does not exist
|
||||
The path `foo.toml` does not point to a configuration file
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
@@ -147,28 +152,29 @@ fn too_many_config_files() -> Result<()> {
|
||||
let ruff2_dot_toml = tempdir.path().join("ruff2.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
fs::File::create(&ruff2_dot_toml)?;
|
||||
let expected_stderr = format!(
|
||||
"\
|
||||
ruff failed
|
||||
Cause: You cannot specify more than one configuration file on the command line.
|
||||
|
||||
tip: remove either `--config={}` or `--config={}`.
|
||||
For more information, try `--help`.
|
||||
|
||||
",
|
||||
ruff_dot_toml.display(),
|
||||
ruff2_dot_toml.display(),
|
||||
);
|
||||
let cmd = Command::new(get_cargo_bin(BIN_NAME))
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("format")
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.arg("--config")
|
||||
.arg(&ruff2_dot_toml)
|
||||
.arg(".")
|
||||
.output()?;
|
||||
let stderr = std::str::from_utf8(&cmd.stderr)?;
|
||||
assert_eq!(stderr, expected_stderr);
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: You cannot specify more than one configuration file on the command line.
|
||||
|
||||
tip: remove either `--config=[TMP]/ruff.toml` or `--config=[TMP]/ruff2.toml`.
|
||||
For more information, try `--help`.
|
||||
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -177,27 +183,29 @@ fn config_file_and_isolated() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
let expected_stderr = format!(
|
||||
"\
|
||||
ruff failed
|
||||
Cause: The argument `--config={}` cannot be used with `--isolated`
|
||||
|
||||
tip: You cannot specify a configuration file and also specify `--isolated`,
|
||||
as `--isolated` causes ruff to ignore all configuration files.
|
||||
For more information, try `--help`.
|
||||
|
||||
",
|
||||
ruff_dot_toml.display(),
|
||||
);
|
||||
let cmd = Command::new(get_cargo_bin(BIN_NAME))
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("format")
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.arg("--isolated")
|
||||
.arg(".")
|
||||
.output()?;
|
||||
let stderr = std::str::from_utf8(&cmd.stderr)?;
|
||||
assert_eq!(stderr, expected_stderr);
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: The argument `--config=[TMP]/ruff.toml` cannot be used with `--isolated`
|
||||
|
||||
tip: You cannot specify a configuration file and also specify `--isolated`,
|
||||
as `--isolated` causes ruff to ignore all configuration files.
|
||||
For more information, try `--help`.
|
||||
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -350,58 +358,52 @@ def f(x):
|
||||
'''
|
||||
pass
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def f(x):
|
||||
"""
|
||||
Something about `f`. And an example:
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def f(x):
|
||||
"""
|
||||
Something about `f`. And an example:
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: python
|
||||
|
||||
(
|
||||
foo,
|
||||
bar,
|
||||
quux,
|
||||
) = this_is_a_long_line(
|
||||
lion,
|
||||
hippo,
|
||||
lemur,
|
||||
bear,
|
||||
foo, bar, quux = (
|
||||
this_is_a_long_line(
|
||||
lion,
|
||||
hippo,
|
||||
lemur,
|
||||
bear,
|
||||
)
|
||||
)
|
||||
|
||||
Another example:
|
||||
|
||||
```py
|
||||
foo, bar, quux = (
|
||||
this_is_a_long_line(
|
||||
lion,
|
||||
hippo,
|
||||
lemur,
|
||||
bear,
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Another example:
|
||||
And another:
|
||||
|
||||
```py
|
||||
(
|
||||
foo,
|
||||
bar,
|
||||
quux,
|
||||
) = this_is_a_long_line(
|
||||
lion,
|
||||
hippo,
|
||||
lemur,
|
||||
bear,
|
||||
)
|
||||
```
|
||||
>>> foo, bar, quux = (
|
||||
... this_is_a_long_line(
|
||||
... lion,
|
||||
... hippo,
|
||||
... lemur,
|
||||
... bear,
|
||||
... )
|
||||
... )
|
||||
"""
|
||||
pass
|
||||
|
||||
And another:
|
||||
|
||||
>>> (
|
||||
... foo,
|
||||
... bar,
|
||||
... quux,
|
||||
... ) = this_is_a_long_line(
|
||||
... lion,
|
||||
... hippo,
|
||||
... lemur,
|
||||
... bear,
|
||||
... )
|
||||
"""
|
||||
pass
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ impl<'a> RuffCheck<'a> {
|
||||
/// Generate a [`Command`] for the `ruff check` command.
|
||||
fn build(self) -> Command {
|
||||
let mut cmd = ruff_cmd();
|
||||
cmd.arg("check");
|
||||
if let Some(output_format) = self.output_format {
|
||||
cmd.args(["--output-format", output_format]);
|
||||
}
|
||||
@@ -100,6 +101,7 @@ fn stdin_success() {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -221,6 +223,7 @@ fn stdin_source_type_pyi() {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -589,6 +592,7 @@ fn stdin_fix_when_no_issues_should_still_print_contents() {
|
||||
print(sys.version)
|
||||
|
||||
----- stderr -----
|
||||
All checks passed!
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -805,13 +809,13 @@ fn full_output_format() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explain_status_codes_f401() {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["--explain", "F401"]));
|
||||
fn rule_f401() {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explain_status_codes_ruf404() {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["--explain", "RUF404"]), @r###"
|
||||
fn rule_invalid_rule_name() {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404"]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
@@ -1022,6 +1026,7 @@ fn preview_disabled_direct() {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: Selection `RUF911` has no effect because preview is not enabled.
|
||||
@@ -1038,6 +1043,7 @@ fn preview_disabled_prefix_empty() {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: Selection `RUF91` has no effect because preview is not enabled.
|
||||
@@ -1054,6 +1060,7 @@ fn preview_disabled_does_not_warn_for_empty_ignore_selections() {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -1069,6 +1076,7 @@ fn preview_disabled_does_not_warn_for_empty_fixable_selections() {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -1174,6 +1182,7 @@ fn removed_indirect() {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -1204,6 +1213,7 @@ fn redirect_indirect() {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -1306,6 +1316,7 @@ fn deprecated_indirect_preview_enabled() {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -1348,7 +1359,7 @@ fn unreadable_pyproject_toml() -> Result<()> {
|
||||
|
||||
// Don't `--isolated` since the configuration discovery is where the error happens
|
||||
let args = Args::parse_from(["", "check", "--no-cache", tempdir.path().to_str().unwrap()]);
|
||||
let err = run(args).err().context("Unexpected success")?;
|
||||
let err = run(args, None).err().context("Unexpected success")?;
|
||||
assert_eq!(
|
||||
err.chain()
|
||||
.map(std::string::ToString::to_string)
|
||||
@@ -1382,6 +1393,7 @@ fn unreadable_dir() -> Result<()> {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: Encountered error: Permission denied (os error 13)
|
||||
@@ -1896,6 +1908,7 @@ def log(x, base) -> float:
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
|
||||
@@ -12,7 +12,7 @@ use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use tempfile::TempDir;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
const STDIN_BASE_OPTIONS: &[&str] = &["--no-cache", "--output-format", "concise"];
|
||||
const STDIN_BASE_OPTIONS: &[&str] = &["check", "--no-cache", "--output-format", "concise"];
|
||||
|
||||
fn tempdir_filter(tempdir: &TempDir) -> String {
|
||||
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
|
||||
@@ -246,7 +246,6 @@ OTHER = "OTHER"
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
// Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude
|
||||
@@ -293,7 +292,6 @@ inline-quotes = "single"
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.args(["--stdin-filename", "generated.py"])
|
||||
@@ -386,7 +384,6 @@ inline-quotes = "single"
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.args(["--stdin-filename", "generated.py"])
|
||||
@@ -435,7 +432,6 @@ inline-quotes = "single"
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.args(["--stdin-filename", "generated.py"])
|
||||
@@ -495,12 +491,12 @@ ignore = ["D203", "D212"]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(sub_dir)
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: No Python files found under the given path(s)
|
||||
@@ -527,7 +523,7 @@ fn nonexistent_config_file() {
|
||||
option
|
||||
|
||||
It looks like you were trying to pass a path to a configuration file.
|
||||
The path `foo.toml` does not exist
|
||||
The path `foo.toml` does not point to a configuration file
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
@@ -595,6 +591,24 @@ fn too_many_config_files() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extend_passed_via_config_argument() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "extend = 'foo.toml'", "."]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'extend = 'foo.toml'' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: Cannot include `extend` in a --config flag value
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_file_and_isolated() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
@@ -820,6 +834,7 @@ fn complex_config_setting_overridden_via_cli() -> Result<()> {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -836,7 +851,7 @@ fn deprecated_config_option_overridden_via_cli() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:7: N801 Class name `lowercase` should use CapWords convention
|
||||
-:1:7: N801 Class name `lowercase` should use CapWords convention
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
@@ -903,7 +918,6 @@ include = ["*.ipy"]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.args(["--extension", "ipy:ipynb"])
|
||||
@@ -921,3 +935,236 @@ include = ["*.ipy"]
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_noqa_external() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
external = ["AAA"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
# flake8: noqa: AAA101, BBB102
|
||||
import os
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:3:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
warning: Invalid rule code provided to `# ruff: noqa` at -:2: BBB102
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_version_exact_mismatch() -> Result<()> {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
required-version = "0.1.0"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
import os
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: Required version `==0.1.0` does not match the running version `[VERSION]`
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_version_exact_match() -> Result<()> {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
format!(
|
||||
r#"
|
||||
required-version = "{version}"
|
||||
"#
|
||||
),
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
import os
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:2:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_version_bound_mismatch() -> Result<()> {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
format!(
|
||||
r#"
|
||||
required-version = ">{version}"
|
||||
"#
|
||||
),
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
import os
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: Required version `>[VERSION]` does not match the running version `[VERSION]`
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_version_bound_match() -> Result<()> {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
required-version = ">=0.1.0"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
import os
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:2:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Expand environment variables in `--config` paths provided via the CLI.
|
||||
#[test]
|
||||
fn config_expand() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
select = ["F"]
|
||||
ignore = ["F841"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg("${NAME}.toml")
|
||||
.env("NAME", "ruff")
|
||||
.arg("-")
|
||||
.current_dir(tempdir.path())
|
||||
.pass_stdin(r#"
|
||||
import os
|
||||
|
||||
def func():
|
||||
x = 1
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:2:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/tests/integration_test.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
- "--explain"
|
||||
- rule
|
||||
- F401
|
||||
---
|
||||
success: true
|
||||
@@ -34,6 +34,11 @@ marking it as unused, as in:
|
||||
from module import member as member
|
||||
```
|
||||
|
||||
## Fix safety
|
||||
|
||||
When `ignore_init_module_imports` is disabled, fixes can remove for unused imports in `__init__` files.
|
||||
These fixes are considered unsafe because they can change the public interface.
|
||||
|
||||
## Example
|
||||
```python
|
||||
import numpy as np # unused import
|
||||
@@ -68,4 +73,3 @@ else:
|
||||
- [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols)
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -44,7 +44,6 @@ file_resolver.exclude = [
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"build",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"site-packages",
|
||||
@@ -202,7 +201,7 @@ linter.allowed_confusables = []
|
||||
linter.builtins = []
|
||||
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
|
||||
linter.external = []
|
||||
linter.ignore_init_module_imports = false
|
||||
linter.ignore_init_module_imports = true
|
||||
linter.logger_objects = []
|
||||
linter.namespace_packages = []
|
||||
linter.src = [
|
||||
@@ -232,7 +231,7 @@ linter.flake8_bandit.check_typed_exception = false
|
||||
linter.flake8_bugbear.extend_immutable_calls = []
|
||||
linter.flake8_builtins.builtins_ignorelist = []
|
||||
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
|
||||
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+(\(C\)\s+)?\d{4}(-\d{4})*
|
||||
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}(-\d{4})*
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
@@ -242,7 +241,22 @@ linter.flake8_gettext.functions_names = [
|
||||
ngettext,
|
||||
]
|
||||
linter.flake8_implicit_str_concat.allow_multiline = true
|
||||
linter.flake8_import_conventions.aliases = {"matplotlib": "mpl", "matplotlib.pyplot": "plt", "pandas": "pd", "seaborn": "sns", "tensorflow": "tf", "networkx": "nx", "plotly.express": "px", "polars": "pl", "numpy": "np", "panel": "pn", "pyarrow": "pa", "altair": "alt", "tkinter": "tk", "holoviews": "hv"}
|
||||
linter.flake8_import_conventions.aliases = {
|
||||
altair = alt,
|
||||
holoviews = hv,
|
||||
matplotlib = mpl,
|
||||
matplotlib.pyplot = plt,
|
||||
networkx = nx,
|
||||
numpy = np,
|
||||
pandas = pd,
|
||||
panel = pn,
|
||||
plotly.express = px,
|
||||
polars = pl,
|
||||
pyarrow = pa,
|
||||
seaborn = sns,
|
||||
tensorflow = tf,
|
||||
tkinter = tk,
|
||||
}
|
||||
linter.flake8_import_conventions.banned_aliases = {}
|
||||
linter.flake8_import_conventions.banned_from = []
|
||||
linter.flake8_pytest_style.fixture_parentheses = true
|
||||
@@ -312,6 +326,7 @@ linter.isort.section_order = [
|
||||
known { type = first_party },
|
||||
known { type = local_folder },
|
||||
]
|
||||
linter.isort.default_section = known { type = third_party }
|
||||
linter.isort.no_sections = false
|
||||
linter.isort.from_first = false
|
||||
linter.isort.length_sort = false
|
||||
@@ -366,4 +381,3 @@ formatter.docstring_code_format = disabled
|
||||
formatter.docstring_code_line_width = dynamic
|
||||
|
||||
----- stderr -----
|
||||
|
||||
|
||||
105
crates/ruff/tests/version.rs
Normal file
105
crates/ruff/tests/version.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
//! Tests for the --version command
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::Result;
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use tempfile::TempDir;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
const VERSION_FILTER: [(&str, &str); 1] = [(
|
||||
r"\d+\.\d+\.\d+(\+\d+)?( \(\w{9} \d\d\d\d-\d\d-\d\d\))?",
|
||||
"[VERSION]",
|
||||
)];
|
||||
|
||||
#[test]
|
||||
fn version_basics() {
|
||||
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME)).arg("version"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
ruff [VERSION]
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// `--config` is a global option,
|
||||
/// so it's allowed to pass --config to subcommands such as `version`
|
||||
/// -- the flag is simply ignored
|
||||
#[test]
|
||||
fn config_option_allowed_but_ignored() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("version")
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.args(["--config", "lint.isort.extra-standard-library = ['foo', 'bar']"]), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
ruff [VERSION]
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn config_option_ignored_but_validated() {
|
||||
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("version")
|
||||
.args(["--config", "foo = bar"]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'foo = bar' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
The supplied argument is not valid TOML:
|
||||
|
||||
TOML parse error at line 1, column 7
|
||||
|
|
||||
1 | foo = bar
|
||||
| ^
|
||||
invalid string
|
||||
expected `"`, `'`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// `--isolated` is also a global option,
|
||||
#[test]
|
||||
fn isolated_option_allowed() {
|
||||
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME)).arg("version").arg("--isolated"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
ruff [VERSION]
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -27,7 +27,7 @@ use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, LogLevelArgs};
|
||||
use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, GlobalConfigArgs, LogLevelArgs};
|
||||
use ruff::resolve::resolve;
|
||||
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||
use ruff_linter::logging::LogLevel;
|
||||
@@ -43,7 +43,7 @@ fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, ConfigArgumen
|
||||
.no_binary_name(true)
|
||||
.get_matches_from(dirs);
|
||||
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
|
||||
let (cli, config_arguments) = arguments.partition()?;
|
||||
let (cli, config_arguments) = arguments.partition(GlobalConfigArgs::default())?;
|
||||
Ok((cli, config_arguments))
|
||||
}
|
||||
|
||||
@@ -52,11 +52,7 @@ fn find_pyproject_config(
|
||||
cli: &FormatArguments,
|
||||
config_arguments: &ConfigArguments,
|
||||
) -> anyhow::Result<PyprojectConfig> {
|
||||
let mut pyproject_config = resolve(
|
||||
cli.isolated,
|
||||
config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
let mut pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
|
||||
// We don't want to format pyproject.toml
|
||||
pyproject_config.settings.file_resolver.include = FilePatternSet::try_from_iter([
|
||||
FilePattern::Builtin("*.py"),
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ruff::check;
|
||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||
use ruff::{args::GlobalConfigArgs, check};
|
||||
use ruff_linter::logging::set_up_logging;
|
||||
use std::process::ExitCode;
|
||||
|
||||
mod format_dev;
|
||||
@@ -28,6 +28,8 @@ const ROOT_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../");
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
#[clap(flatten)]
|
||||
global_options: GlobalConfigArgs,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
@@ -57,8 +59,6 @@ enum Command {
|
||||
Repeat {
|
||||
#[clap(flatten)]
|
||||
args: ruff::args::CheckCommand,
|
||||
#[clap(flatten)]
|
||||
log_level_args: ruff::args::LogLevelArgs,
|
||||
/// Run this many times
|
||||
#[clap(long)]
|
||||
repeat: usize,
|
||||
@@ -75,9 +75,12 @@ enum Command {
|
||||
}
|
||||
|
||||
fn main() -> Result<ExitCode> {
|
||||
let args = Args::parse();
|
||||
let Args {
|
||||
command,
|
||||
global_options,
|
||||
} = Args::parse();
|
||||
#[allow(clippy::print_stdout)]
|
||||
match args.command {
|
||||
match command {
|
||||
Command::GenerateAll(args) => generate_all::main(&args)?,
|
||||
Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?,
|
||||
Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()),
|
||||
@@ -89,14 +92,12 @@ fn main() -> Result<ExitCode> {
|
||||
Command::PrintTokens(args) => print_tokens::main(&args)?,
|
||||
Command::RoundTrip(args) => round_trip::main(&args)?,
|
||||
Command::Repeat {
|
||||
args,
|
||||
args: subcommand_args,
|
||||
repeat,
|
||||
log_level_args,
|
||||
} => {
|
||||
let log_level = LogLevel::from(&log_level_args);
|
||||
set_up_logging(&log_level)?;
|
||||
set_up_logging(global_options.log_level())?;
|
||||
for _ in 0..repeat {
|
||||
check(args.clone(), log_level)?;
|
||||
check(subcommand_args.clone(), global_options.clone())?;
|
||||
}
|
||||
}
|
||||
Command::FormatDev(args) => {
|
||||
|
||||
@@ -37,7 +37,7 @@ pub trait Buffer {
|
||||
#[doc(hidden)]
|
||||
fn elements(&self) -> &[FormatElement];
|
||||
|
||||
/// Glue for usage of the [`write!`] macro with implementors of this trait.
|
||||
/// Glue for usage of the [`write!`] macro with implementers of this trait.
|
||||
///
|
||||
/// This method should generally not be invoked manually, but rather through the [`write!`] macro itself.
|
||||
///
|
||||
|
||||
@@ -545,6 +545,10 @@ impl PrintedRange {
|
||||
&self.code
|
||||
}
|
||||
|
||||
pub fn into_code(self) -> String {
|
||||
self.code
|
||||
}
|
||||
|
||||
/// The range the formatted code corresponds to in the source document.
|
||||
pub fn source_range(&self) -> TextRange {
|
||||
self.source_range
|
||||
|
||||
@@ -78,27 +78,28 @@ impl<'a> PrintQueue<'a> {
|
||||
impl<'a> Queue<'a> for PrintQueue<'a> {
|
||||
fn pop(&mut self) -> Option<&'a FormatElement> {
|
||||
let elements = self.element_slices.last_mut()?;
|
||||
elements.next().or_else(|| {
|
||||
self.element_slices.pop();
|
||||
let elements = self.element_slices.last_mut()?;
|
||||
elements.next()
|
||||
})
|
||||
elements.next().or_else(
|
||||
#[cold]
|
||||
|| {
|
||||
self.element_slices.pop();
|
||||
let elements = self.element_slices.last_mut()?;
|
||||
elements.next()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn top_with_interned(&self) -> Option<&'a FormatElement> {
|
||||
let mut slices = self.element_slices.iter().rev();
|
||||
let slice = slices.next()?;
|
||||
|
||||
match slice.as_slice().first() {
|
||||
Some(element) => Some(element),
|
||||
None => {
|
||||
if let Some(next_elements) = slices.next() {
|
||||
next_elements.as_slice().first()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
slice.as_slice().first().or_else(
|
||||
#[cold]
|
||||
|| {
|
||||
slices
|
||||
.next()
|
||||
.and_then(|next_elements| next_elements.as_slice().first())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn extend_back(&mut self, elements: &'a [FormatElement]) {
|
||||
@@ -146,24 +147,30 @@ impl<'a, 'print> FitsQueue<'a, 'print> {
|
||||
|
||||
impl<'a, 'print> Queue<'a> for FitsQueue<'a, 'print> {
|
||||
fn pop(&mut self) -> Option<&'a FormatElement> {
|
||||
self.queue.pop().or_else(|| {
|
||||
if let Some(next_slice) = self.rest_elements.next_back() {
|
||||
self.queue.extend_back(next_slice.as_slice());
|
||||
self.queue.pop()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
self.queue.pop().or_else(
|
||||
#[cold]
|
||||
|| {
|
||||
if let Some(next_slice) = self.rest_elements.next_back() {
|
||||
self.queue.extend_back(next_slice.as_slice());
|
||||
self.queue.pop()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn top_with_interned(&self) -> Option<&'a FormatElement> {
|
||||
self.queue.top_with_interned().or_else(|| {
|
||||
if let Some(next_elements) = self.rest_elements.as_slice().last() {
|
||||
next_elements.as_slice().first()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
self.queue.top_with_interned().or_else(
|
||||
#[cold]
|
||||
|| {
|
||||
if let Some(next_elements) = self.rest_elements.as_slice().last() {
|
||||
next_elements.as_slice().first()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn extend_back(&mut self, elements: &'a [FormatElement]) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.2.2"
|
||||
version = "0.3.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -60,7 +60,6 @@ regex = { workspace = true }
|
||||
result-like = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
semver = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
|
||||
@@ -28,3 +28,11 @@ dictionary = {
|
||||
}
|
||||
|
||||
#import os # noqa
|
||||
|
||||
# case 1:
|
||||
# try:
|
||||
# try: # with comment
|
||||
# try: print()
|
||||
# except:
|
||||
# except Foo:
|
||||
# except Exception as e: print(e)
|
||||
|
||||
@@ -18,3 +18,7 @@ func("0.0.0.0")
|
||||
def my_func():
|
||||
x = "0.0.0.0"
|
||||
print(x)
|
||||
|
||||
|
||||
# Implicit string concatenation
|
||||
"0.0.0.0" f"0.0.0.0{expr}0.0.0.0"
|
||||
|
||||
@@ -18,6 +18,13 @@ with open("/dev/shm/unit/test", "w") as f:
|
||||
with open("/foo/bar", "w") as f:
|
||||
f.write("def")
|
||||
|
||||
# Implicit string concatenation
|
||||
with open("/tmp/" "abc", "w") as f:
|
||||
f.write("def")
|
||||
|
||||
with open("/tmp/abc" f"/tmp/abc", "w") as f:
|
||||
f.write("def")
|
||||
|
||||
# Using `tempfile` module should be ok
|
||||
import tempfile
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
22
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S311.py
vendored
Normal file
22
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S311.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import os
|
||||
import random
|
||||
|
||||
import a_lib
|
||||
|
||||
# OK
|
||||
random.SystemRandom()
|
||||
|
||||
# Errors
|
||||
random.Random()
|
||||
random.random()
|
||||
random.randrange()
|
||||
random.randint()
|
||||
random.choice()
|
||||
random.choices()
|
||||
random.uniform()
|
||||
random.triangular()
|
||||
random.randbytes()
|
||||
|
||||
# Unrelated
|
||||
os.urandom()
|
||||
a_lib.random()
|
||||
@@ -1,52 +1,47 @@
|
||||
import crypt
|
||||
import hashlib
|
||||
from hashlib import new as hashlib_new
|
||||
from hashlib import sha1 as hashlib_sha1
|
||||
|
||||
# Invalid
|
||||
|
||||
# Errors
|
||||
hashlib.new('md5')
|
||||
|
||||
hashlib.new('md4', b'test')
|
||||
|
||||
hashlib.new(name='md5', data=b'test')
|
||||
|
||||
hashlib.new('MD4', data=b'test')
|
||||
|
||||
hashlib.new('sha1')
|
||||
|
||||
hashlib.new('sha1', data=b'test')
|
||||
|
||||
hashlib.new('sha', data=b'test')
|
||||
|
||||
hashlib.new(name='SHA', data=b'test')
|
||||
|
||||
hashlib.sha(data=b'test')
|
||||
|
||||
hashlib.md5()
|
||||
|
||||
hashlib_new('sha1')
|
||||
|
||||
hashlib_sha1('sha1')
|
||||
|
||||
# usedforsecurity arg only available in Python 3.9+
|
||||
hashlib.new('sha1', usedforsecurity=True)
|
||||
|
||||
# Valid
|
||||
crypt.crypt("test", salt=crypt.METHOD_CRYPT)
|
||||
crypt.crypt("test", salt=crypt.METHOD_MD5)
|
||||
crypt.crypt("test", salt=crypt.METHOD_BLOWFISH)
|
||||
crypt.crypt("test", crypt.METHOD_BLOWFISH)
|
||||
|
||||
crypt.mksalt(crypt.METHOD_CRYPT)
|
||||
crypt.mksalt(crypt.METHOD_MD5)
|
||||
crypt.mksalt(crypt.METHOD_BLOWFISH)
|
||||
|
||||
# OK
|
||||
hashlib.new('sha256')
|
||||
|
||||
hashlib.new('SHA512')
|
||||
|
||||
hashlib.sha256(data=b'test')
|
||||
|
||||
# usedforsecurity arg only available in Python 3.9+
|
||||
hashlib_new(name='sha1', usedforsecurity=False)
|
||||
|
||||
# usedforsecurity arg only available in Python 3.9+
|
||||
hashlib_sha1(name='sha1', usedforsecurity=False)
|
||||
|
||||
# usedforsecurity arg only available in Python 3.9+
|
||||
hashlib.md4(usedforsecurity=False)
|
||||
|
||||
# usedforsecurity arg only available in Python 3.9+
|
||||
hashlib.new(name='sha256', usedforsecurity=False)
|
||||
|
||||
crypt.crypt("test")
|
||||
crypt.crypt("test", salt=crypt.METHOD_SHA256)
|
||||
crypt.crypt("test", salt=crypt.METHOD_SHA512)
|
||||
|
||||
crypt.mksalt()
|
||||
crypt.mksalt(crypt.METHOD_SHA256)
|
||||
crypt.mksalt(crypt.METHOD_SHA512)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import commands
|
||||
import popen2
|
||||
@@ -16,6 +17,8 @@ popen2.Popen3("true")
|
||||
popen2.Popen4("true")
|
||||
commands.getoutput("true")
|
||||
commands.getstatusoutput("true")
|
||||
subprocess.getoutput("true")
|
||||
subprocess.getstatusoutput("true")
|
||||
|
||||
|
||||
# Check command argument looks unsafe.
|
||||
|
||||
34
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S610.py
vendored
Normal file
34
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S610.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
# Errors
|
||||
User.objects.filter(username='admin').extra(dict(could_be='insecure'))
|
||||
User.objects.filter(username='admin').extra(select=dict(could_be='insecure'))
|
||||
User.objects.filter(username='admin').extra(select={'test': '%secure' % 'nos'})
|
||||
User.objects.filter(username='admin').extra(select={'test': '{}secure'.format('nos')})
|
||||
User.objects.filter(username='admin').extra(where=['%secure' % 'nos'])
|
||||
User.objects.filter(username='admin').extra(where=['{}secure'.format('no')])
|
||||
|
||||
query = '"username") AS "username", * FROM "auth_user" WHERE 1=1 OR "username"=? --'
|
||||
User.objects.filter(username='admin').extra(select={'test': query})
|
||||
|
||||
where_var = ['1=1) OR 1=1 AND (1=1']
|
||||
User.objects.filter(username='admin').extra(where=where_var)
|
||||
|
||||
where_str = '1=1) OR 1=1 AND (1=1'
|
||||
User.objects.filter(username='admin').extra(where=[where_str])
|
||||
|
||||
tables_var = ['django_content_type" WHERE "auth_user"."username"="admin']
|
||||
User.objects.all().extra(tables=tables_var).distinct()
|
||||
|
||||
tables_str = 'django_content_type" WHERE "auth_user"."username"="admin'
|
||||
User.objects.all().extra(tables=[tables_str]).distinct()
|
||||
|
||||
# OK
|
||||
User.objects.filter(username='admin').extra(
|
||||
select={'test': 'secure'},
|
||||
where=['secure'],
|
||||
tables=['secure']
|
||||
)
|
||||
User.objects.filter(username='admin').extra({'test': 'secure'})
|
||||
User.objects.filter(username='admin').extra(select={'test': 'secure'})
|
||||
User.objects.filter(username='admin').extra(where=['secure'])
|
||||
@@ -119,3 +119,16 @@ def func(x: bool):
|
||||
|
||||
|
||||
settings(True)
|
||||
|
||||
|
||||
from dataclasses import dataclass, InitVar
|
||||
|
||||
|
||||
@dataclass
|
||||
class Fit:
|
||||
force: InitVar[bool] = False
|
||||
|
||||
def __post_init__(self, force: bool) -> None:
|
||||
print(force)
|
||||
|
||||
Fit(force=True)
|
||||
|
||||
@@ -14,9 +14,6 @@ reversed(sorted(x, reverse=not x))
|
||||
reversed(sorted(i for i in range(42)))
|
||||
reversed(sorted((i for i in range(42)), reverse=True))
|
||||
|
||||
|
||||
def reversed(*args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
reversed(sorted(x, reverse=True))
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/10335
|
||||
reversed(sorted([1, 2, 3], reverse=False or True))
|
||||
reversed(sorted([1, 2, 3], reverse=(False or True)))
|
||||
|
||||
@@ -7,7 +7,19 @@ from pdb import set_trace as st
|
||||
from celery.contrib.rdb import set_trace
|
||||
from celery.contrib import rdb
|
||||
import celery.contrib.rdb
|
||||
from debugpy import wait_for_client
|
||||
import debugpy
|
||||
from ptvsd import break_into_debugger
|
||||
from ptvsd import enable_attach
|
||||
from ptvsd import wait_for_attach
|
||||
import ptvsd
|
||||
|
||||
breakpoint()
|
||||
st()
|
||||
set_trace()
|
||||
debugpy.breakpoint()
|
||||
wait_for_client()
|
||||
debugpy.listen(1234)
|
||||
enable_attach()
|
||||
break_into_debugger()
|
||||
wait_for_attach()
|
||||
|
||||
@@ -64,3 +64,5 @@ def not_warnings_dot_deprecated(
|
||||
"Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
|
||||
)
|
||||
def not_a_deprecated_function() -> None: ...
|
||||
|
||||
fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053
|
||||
|
||||
@@ -40,4 +40,7 @@ f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
|
||||
|
||||
# Make sure we do not unescape quotes
|
||||
this_is_fine = "This is an \\'escaped\\' quote"
|
||||
this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash"
|
||||
this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash" # Q004
|
||||
|
||||
# Invalid escapes in bytestrings are also triggered:
|
||||
x = b"\xe7\xeb\x0c\xa1\x1b\x83tN\xce=x\xe9\xbe\x01\xb9\x13B_\xba\xe7\x0c2\xce\'rm\x0e\xcd\xe9.\xf8\xd2" # Q004
|
||||
|
||||
@@ -93,3 +93,15 @@ def func():
|
||||
|
||||
# OK
|
||||
raise func()
|
||||
|
||||
|
||||
# OK
|
||||
future = executor.submit(float, "a")
|
||||
if future.exception():
|
||||
raise future.exception()
|
||||
|
||||
|
||||
# RSE102
|
||||
future = executor.submit(float, "a")
|
||||
if future.exception():
|
||||
raise future.Exception()
|
||||
|
||||
@@ -10,7 +10,7 @@ async def func():
|
||||
|
||||
trio.sleep(0) # TRIO115
|
||||
foo = 0
|
||||
trio.sleep(foo) # TRIO115
|
||||
trio.sleep(foo) # OK
|
||||
trio.sleep(1) # OK
|
||||
time.sleep(0) # OK
|
||||
|
||||
@@ -20,26 +20,26 @@ async def func():
|
||||
trio.sleep(bar)
|
||||
|
||||
x, y = 0, 2000
|
||||
trio.sleep(x) # TRIO115
|
||||
trio.sleep(x) # OK
|
||||
trio.sleep(y) # OK
|
||||
|
||||
(a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
|
||||
trio.sleep(c) # TRIO115
|
||||
trio.sleep(c) # OK
|
||||
trio.sleep(d) # OK
|
||||
trio.sleep(e) # TRIO115
|
||||
trio.sleep(e) # OK
|
||||
|
||||
m_x, m_y = 0
|
||||
trio.sleep(m_y) # OK
|
||||
trio.sleep(m_x) # OK
|
||||
|
||||
m_a = m_b = 0
|
||||
trio.sleep(m_a) # TRIO115
|
||||
trio.sleep(m_b) # TRIO115
|
||||
trio.sleep(m_a) # OK
|
||||
trio.sleep(m_b) # OK
|
||||
|
||||
m_c = (m_d, m_e) = (0, 0)
|
||||
trio.sleep(m_c) # OK
|
||||
trio.sleep(m_d) # TRIO115
|
||||
trio.sleep(m_e) # TRIO115
|
||||
trio.sleep(m_d) # OK
|
||||
trio.sleep(m_e) # OK
|
||||
|
||||
|
||||
def func():
|
||||
@@ -63,4 +63,16 @@ def func():
|
||||
import trio
|
||||
|
||||
if (walrus := 0) == 0:
|
||||
trio.sleep(walrus) # TRIO115
|
||||
trio.sleep(walrus) # OK
|
||||
|
||||
|
||||
def func():
|
||||
import trio
|
||||
|
||||
async def main() -> None:
|
||||
sleep = 0
|
||||
for _ in range(2):
|
||||
await trio.sleep(sleep) # OK
|
||||
sleep = 10
|
||||
|
||||
trio.run(main)
|
||||
|
||||
9
crates/ruff_linter/resources/test/fixtures/isort/default_section_user_defined.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/isort/default_section_user_defined.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import django.settings
|
||||
from library import foo
|
||||
import os
|
||||
import pytz
|
||||
import sys
|
||||
|
||||
from . import local
|
||||
16
crates/ruff_linter/resources/test/fixtures/isort/lines_after_imports.pyi
vendored
Normal file
16
crates/ruff_linter/resources/test/fixtures/isort/lines_after_imports.pyi
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from requests import Session
|
||||
|
||||
from my_first_party import my_first_party_object
|
||||
|
||||
from . import my_local_folder_object
|
||||
|
||||
|
||||
|
||||
class Thing(object):
|
||||
name: str
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
9
crates/ruff_linter/resources/test/fixtures/isort/no_standard_library.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/isort/no_standard_library.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import django.settings
|
||||
from library import foo
|
||||
import pytz
|
||||
|
||||
from . import local
|
||||
import sys
|
||||
@@ -50,6 +50,29 @@ class MetaClass(ABCMeta):
|
||||
def static_method(not_cls) -> bool:
|
||||
return False
|
||||
|
||||
class ClsInArgsClass(ABCMeta):
|
||||
def cls_as_argument(this, cls):
|
||||
pass
|
||||
|
||||
def cls_as_pos_only_argument(this, cls, /):
|
||||
pass
|
||||
|
||||
def cls_as_kw_only_argument(this, *, cls):
|
||||
pass
|
||||
|
||||
def cls_as_varags(this, *cls):
|
||||
pass
|
||||
|
||||
def cls_as_kwargs(this, **cls):
|
||||
pass
|
||||
|
||||
class RenamingInMethodBodyClass(ABCMeta):
|
||||
def bad_method(this):
|
||||
this = this
|
||||
this
|
||||
|
||||
def bad_method(this):
|
||||
self = this
|
||||
|
||||
def func(x):
|
||||
return x
|
||||
|
||||
@@ -61,7 +61,7 @@ class PosOnlyClass:
|
||||
def good_method_pos_only(self, blah, /, something: str):
|
||||
pass
|
||||
|
||||
def bad_method_pos_only(this, blah, /, self, something: str):
|
||||
def bad_method_pos_only(this, blah, /, something: str):
|
||||
pass
|
||||
|
||||
|
||||
@@ -93,3 +93,27 @@ class ModelClass:
|
||||
@foobar.thisisstatic
|
||||
def badstatic(foo):
|
||||
pass
|
||||
|
||||
class SelfInArgsClass:
|
||||
def self_as_argument(this, self):
|
||||
pass
|
||||
|
||||
def self_as_pos_only_argument(this, self, /):
|
||||
pass
|
||||
|
||||
def self_as_kw_only_argument(this, *, self):
|
||||
pass
|
||||
|
||||
def self_as_varags(this, *self):
|
||||
pass
|
||||
|
||||
def self_as_kwargs(this, **self):
|
||||
pass
|
||||
|
||||
class RenamingInMethodBodyClass:
|
||||
def bad_method(this):
|
||||
this = this
|
||||
this
|
||||
|
||||
def bad_method(this):
|
||||
self = this
|
||||
|
||||
8
crates/ruff_linter/resources/test/fixtures/pycodestyle/.editorconfig
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/pycodestyle/.editorconfig
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# These rules test for intentional "odd" formatting. Using a formatter fixes that
|
||||
[E{1,2,3}*.py]
|
||||
generated_code = true
|
||||
ij_formatter_enabled = false
|
||||
|
||||
[W*.py]
|
||||
generated_code = true
|
||||
ij_formatter_enabled = false
|
||||
@@ -147,3 +147,15 @@ ham[upper : ]
|
||||
|
||||
#: E203:1:10
|
||||
ham[upper :]
|
||||
|
||||
#: Okay
|
||||
ham[lower +1 :, "columnname"]
|
||||
|
||||
#: E203:1:13
|
||||
ham[lower + 1 :, "columnname"]
|
||||
|
||||
#: Okay
|
||||
f"{ham[lower +1 :, "columnname"]}"
|
||||
|
||||
#: E203:1:13
|
||||
f"{ham[lower + 1 :, "columnname"]}"
|
||||
|
||||
1
crates/ruff_linter/resources/test/fixtures/pycodestyle/E2_syntax_error.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pycodestyle/E2_syntax_error.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
a = (1 or)
|
||||
@@ -466,6 +466,29 @@ class Class:
|
||||
# end
|
||||
|
||||
|
||||
# E301
|
||||
class Class:
|
||||
"""Class for minimal repo."""
|
||||
|
||||
columns = []
|
||||
@classmethod
|
||||
def cls_method(cls) -> None:
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E301
|
||||
class Class:
|
||||
"""Class for minimal repo."""
|
||||
|
||||
def method(cls) -> None:
|
||||
pass
|
||||
@classmethod
|
||||
def cls_method(cls) -> None:
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# E302
|
||||
"""Main module."""
|
||||
def fn():
|
||||
|
||||
50
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.pyi
vendored
Normal file
50
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.pyi
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import json
|
||||
|
||||
from typing import Any, Sequence
|
||||
|
||||
class MissingCommand(TypeError): ...
|
||||
class AnoherClass: ...
|
||||
|
||||
def a(): ...
|
||||
|
||||
@overload
|
||||
def a(arg: int): ...
|
||||
|
||||
@overload
|
||||
def a(arg: int, name: str): ...
|
||||
|
||||
|
||||
def grouped1(): ...
|
||||
def grouped2(): ...
|
||||
def grouped3( ): ...
|
||||
|
||||
|
||||
class BackendProxy:
|
||||
backend_module: str
|
||||
backend_object: str | None
|
||||
backend: Any
|
||||
|
||||
def grouped1(): ...
|
||||
def grouped2(): ...
|
||||
def grouped3( ): ...
|
||||
@decorated
|
||||
|
||||
def with_blank_line(): ...
|
||||
|
||||
|
||||
def ungrouped(): ...
|
||||
a = "test"
|
||||
|
||||
def function_def():
|
||||
pass
|
||||
b = "test"
|
||||
|
||||
|
||||
def outer():
|
||||
def inner():
|
||||
pass
|
||||
def inner2():
|
||||
pass
|
||||
|
||||
class Foo: ...
|
||||
class Bar: ...
|
||||
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_docstring.py
vendored
Normal file
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_docstring.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
"""Test where the error is after the module's docstring."""
|
||||
|
||||
def fn():
|
||||
pass
|
||||
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_expression.py
vendored
Normal file
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_expression.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
"Test where the first line is a comment, " + "and the rule violation follows it."
|
||||
|
||||
def fn():
|
||||
pass
|
||||
5
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_function.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_function.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
def fn1():
|
||||
pass
|
||||
|
||||
def fn2():
|
||||
pass
|
||||
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_statement.py
vendored
Normal file
4
crates/ruff_linter/resources/test/fixtures/pycodestyle/E302_first_line_statement.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
print("Test where the first line is a statement, and the rule violation follows it.")
|
||||
|
||||
def fn():
|
||||
pass
|
||||
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_comment.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_comment.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Test where the first line is a comment, and the rule violation follows it.
|
||||
|
||||
|
||||
|
||||
def fn():
|
||||
pass
|
||||
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_docstring.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_docstring.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Test where the error is after the module's docstring."""
|
||||
|
||||
|
||||
|
||||
def fn():
|
||||
pass
|
||||
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_expression.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_expression.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
"Test where the first line is a comment, " + "and the rule violation follows it."
|
||||
|
||||
|
||||
|
||||
def fn():
|
||||
pass
|
||||
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_statement.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pycodestyle/E303_first_line_statement.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
print("Test where the first line is a statement, and the rule violation follows it.")
|
||||
|
||||
|
||||
|
||||
def fn():
|
||||
pass
|
||||
62
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30_isort.py
vendored
Normal file
62
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30_isort.py
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import json
|
||||
|
||||
|
||||
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
class MissingCommand(TypeError): ... # noqa: N818
|
||||
|
||||
|
||||
class BackendProxy:
|
||||
backend_module: str
|
||||
backend_object: str | None
|
||||
backend: Any
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import abcd
|
||||
|
||||
|
||||
abcd.foo()
|
||||
|
||||
def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import os
|
||||
|
||||
|
||||
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
|
||||
abcd.foo()
|
||||
|
||||
def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
||||
...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
||||
...
|
||||
|
||||
|
||||
def _exit(self) -> None: ...
|
||||
|
||||
|
||||
def _optional_commands(self) -> dict[str, bool]: ...
|
||||
|
||||
|
||||
def run(argv: Sequence[str]) -> int: ...
|
||||
|
||||
|
||||
def read_line(fd: int = 0) -> bytearray: ...
|
||||
|
||||
|
||||
def flush() -> None: ...
|
||||
|
||||
|
||||
from typing import Any, Sequence
|
||||
|
||||
class MissingCommand(TypeError): ... # noqa: N818
|
||||
62
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30_isort.pyi
vendored
Normal file
62
crates/ruff_linter/resources/test/fixtures/pycodestyle/E30_isort.pyi
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import json
|
||||
|
||||
|
||||
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
class MissingCommand(TypeError): ... # noqa: N818
|
||||
|
||||
|
||||
class BackendProxy:
|
||||
backend_module: str
|
||||
backend_object: str | None
|
||||
backend: Any
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import abcd
|
||||
|
||||
|
||||
abcd.foo()
|
||||
|
||||
def __init__(self, backend_module: str, backend_obj: str | None) -> None: ...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import os
|
||||
|
||||
|
||||
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
|
||||
abcd.foo()
|
||||
|
||||
def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
||||
...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
def __call__2(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
||||
...
|
||||
|
||||
|
||||
def _exit(self) -> None: ...
|
||||
|
||||
|
||||
def _optional_commands(self) -> dict[str, bool]: ...
|
||||
|
||||
|
||||
def run(argv: Sequence[str]) -> int: ...
|
||||
|
||||
|
||||
def read_line(fd: int = 0) -> bytearray: ...
|
||||
|
||||
|
||||
def flush() -> None: ...
|
||||
|
||||
|
||||
from typing import Any, Sequence
|
||||
|
||||
class MissingCommand(TypeError): ... # noqa: N818
|
||||
7
crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_2.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_2.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import os
|
||||
|
||||
os.environ["WORLD_SIZE"] = "1"
|
||||
os.putenv("CUDA_VISIBLE_DEVICES", "4")
|
||||
del os.environ["WORLD_SIZE"]
|
||||
|
||||
import torch
|
||||
88
crates/ruff_linter/resources/test/fixtures/pycodestyle/E502.py
vendored
Normal file
88
crates/ruff_linter/resources/test/fixtures/pycodestyle/E502.py
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
a = 2 + 2
|
||||
|
||||
a = (2 + 2)
|
||||
|
||||
a = 2 + \
|
||||
3 \
|
||||
+ 4
|
||||
|
||||
a = (3 -\
|
||||
2 + \
|
||||
7)
|
||||
|
||||
z = 5 + \
|
||||
(3 -\
|
||||
2 + \
|
||||
7) + \
|
||||
4
|
||||
|
||||
b = [2 +
|
||||
2]
|
||||
|
||||
b = [
|
||||
2 + 4 + 5 + \
|
||||
44 \
|
||||
- 5
|
||||
]
|
||||
|
||||
c = (True and
|
||||
False \
|
||||
or False \
|
||||
and True \
|
||||
)
|
||||
|
||||
c = (True and
|
||||
False)
|
||||
|
||||
d = True and \
|
||||
False or \
|
||||
False \
|
||||
and not True
|
||||
|
||||
|
||||
s = {
|
||||
'x': 2 + \
|
||||
2
|
||||
}
|
||||
|
||||
|
||||
s = {
|
||||
'x': 2 +
|
||||
2
|
||||
}
|
||||
|
||||
|
||||
x = {2 + 4 \
|
||||
+ 3}
|
||||
|
||||
y = (
|
||||
2 + 2 # \
|
||||
+ 3 # \
|
||||
+ 4 \
|
||||
+ 3
|
||||
)
|
||||
|
||||
|
||||
x = """
|
||||
(\\
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
("""hello \
|
||||
""")
|
||||
|
||||
("hello \
|
||||
")
|
||||
|
||||
|
||||
x = "abc" \
|
||||
"xyz"
|
||||
|
||||
x = ("abc" \
|
||||
"xyz")
|
||||
|
||||
|
||||
def foo():
|
||||
x = (a + \
|
||||
2)
|
||||
@@ -14,3 +14,6 @@ class Chassis(RobotModuleTemplate):
|
||||
" \
|
||||
\
|
||||
|
||||
'''blank line with whitespace
|
||||
|
||||
inside a multiline string'''
|
||||
|
||||
14
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_0.py
vendored
Normal file
14
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_0.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Unix style
|
||||
def foo() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def bar() -> None:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
foo()
|
||||
bar()
|
||||
|
||||
13
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_1.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_1.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Unix style
|
||||
def foo() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def bar() -> None:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
foo()
|
||||
bar()
|
||||
17
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_2.py
vendored
Normal file
17
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_2.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Windows style
|
||||
def foo() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def bar() -> None:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
foo()
|
||||
bar()
|
||||
|
||||
|
||||
|
||||
|
||||
13
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Windows style
|
||||
def foo() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def bar() -> None:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
foo()
|
||||
bar()
|
||||
5
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_4.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_4.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# This is fine
|
||||
def foo():
|
||||
pass
|
||||
|
||||
# Some comment
|
||||
@@ -10,7 +10,7 @@ def f1():
|
||||
# Here's a standalone comment that's over the limit.
|
||||
|
||||
x = 2
|
||||
# Another standalone that is preceded by a newline and indent toke and is over the limit.
|
||||
# Another standalone that is preceded by a newline and indent token and is over the limit.
|
||||
|
||||
print("Here's a string that's over the limit, but it's not a docstring.")
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ def f1():
|
||||
# Here's a standalone comment that's over theß9💣2ℝ.
|
||||
|
||||
x = 2
|
||||
# Another standalone that is preceded by a newline and indent toke and is over theß9💣2ℝ.
|
||||
# Another standalone that is preceded by a newline and indent token and is over theß9💣2ℝ.
|
||||
|
||||
print("Here's a string that's over theß9💣2ℝ, but it's not a ß9💣2ℝing.")
|
||||
|
||||
|
||||
@@ -57,3 +57,15 @@ def func():
|
||||
|
||||
Returns:
|
||||
the value"""
|
||||
|
||||
|
||||
def func():
|
||||
"""Do something.
|
||||
|
||||
Args:
|
||||
x: the value
|
||||
with a hanging indent
|
||||
|
||||
Returns:
|
||||
the value
|
||||
"""
|
||||
|
||||
7
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_23.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_23.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Test: ensure that we treat strings in `typing.Annotation` as type definitions."""
|
||||
|
||||
from pathlib import Path
|
||||
from re import RegexFlag
|
||||
from typing import Annotated
|
||||
|
||||
p: Annotated["Path", int] = 1
|
||||
6
crates/ruff_linter/resources/test/fixtures/pyflakes/F811_28.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pyflakes/F811_28.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Regression test for: https://github.com/astral-sh/ruff/issues/10384"""
|
||||
|
||||
import datetime
|
||||
from datetime import datetime
|
||||
|
||||
datetime(1, 2, 3)
|
||||
16
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_11.pyi
vendored
Normal file
16
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_11.pyi
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Test case: strings used within calls within type annotations."""
|
||||
|
||||
from typing import Callable
|
||||
|
||||
import bpy
|
||||
from mypy_extensions import VarArg
|
||||
|
||||
class LightShow(bpy.types.Operator):
|
||||
label = "Create Character"
|
||||
name = "lightshow.letter_creation"
|
||||
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # OK
|
||||
|
||||
|
||||
def f(x: Callable[[VarArg("os")], None]): # F821
|
||||
pass
|
||||
44
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py
vendored
Normal file
44
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Tests for constructs allowed in `.pyi` stub files but not at runtime"""
|
||||
|
||||
from typing import Optional, TypeAlias, Union
|
||||
|
||||
__version__: str
|
||||
__author__: str
|
||||
|
||||
# Forward references:
|
||||
MaybeCStr: TypeAlias = Optional[CStr] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
MaybeCStr2: TypeAlias = Optional["CStr"] # always okay
|
||||
CStr: TypeAlias = Union[C, str] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
CStr2: TypeAlias = Union["C", str] # always okay
|
||||
|
||||
# References to a class from inside the class:
|
||||
class C:
|
||||
other: C = ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
other2: "C" = ... # always okay
|
||||
def from_str(self, s: str) -> C: ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
def from_str2(self, s: str) -> "C": ... # always okay
|
||||
|
||||
# Circular references:
|
||||
class A:
|
||||
foo: B # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
foo2: "B" # always okay
|
||||
bar: dict[str, B] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
bar2: dict[str, "A"] # always okay
|
||||
|
||||
class B:
|
||||
foo: A # always okay
|
||||
bar: dict[str, A] # always okay
|
||||
|
||||
class Leaf: ...
|
||||
class Tree(list[Tree | Leaf]): ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
class Tree2(list["Tree | Leaf"]): ... # always okay
|
||||
|
||||
# Annotations are treated as assignments in .pyi files, but not in .py files
|
||||
class MyClass:
|
||||
foo: int
|
||||
bar = foo # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
bar = "foo" # always okay
|
||||
|
||||
baz: MyClass
|
||||
eggs = baz # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
eggs = "baz" # always okay
|
||||
44
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.pyi
vendored
Normal file
44
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.pyi
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Tests for constructs allowed in `.pyi` stub files but not at runtime"""
|
||||
|
||||
from typing import Optional, TypeAlias, Union
|
||||
|
||||
__version__: str
|
||||
__author__: str
|
||||
|
||||
# Forward references:
|
||||
MaybeCStr: TypeAlias = Optional[CStr] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
MaybeCStr2: TypeAlias = Optional["CStr"] # always okay
|
||||
CStr: TypeAlias = Union[C, str] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
CStr2: TypeAlias = Union["C", str] # always okay
|
||||
|
||||
# References to a class from inside the class:
|
||||
class C:
|
||||
other: C = ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
other2: "C" = ... # always okay
|
||||
def from_str(self, s: str) -> C: ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
def from_str2(self, s: str) -> "C": ... # always okay
|
||||
|
||||
# Circular references:
|
||||
class A:
|
||||
foo: B # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
foo2: "B" # always okay
|
||||
bar: dict[str, B] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
bar2: dict[str, "A"] # always okay
|
||||
|
||||
class B:
|
||||
foo: A # always okay
|
||||
bar: dict[str, A] # always okay
|
||||
|
||||
class Leaf: ...
|
||||
class Tree(list[Tree | Leaf]): ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
class Tree2(list["Tree | Leaf"]): ... # always okay
|
||||
|
||||
# Annotations are treated as assignments in .pyi files, but not in .py files
|
||||
class MyClass:
|
||||
foo: int
|
||||
bar = foo # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
bar = "foo" # always okay
|
||||
|
||||
baz: MyClass
|
||||
eggs = baz # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
eggs = "baz" # always okay
|
||||
48
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_27.py
vendored
Normal file
48
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_27.py
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
"""Tests for constructs allowed when `__future__` annotations are enabled but not otherwise"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, TypeAlias, Union
|
||||
|
||||
__version__: str
|
||||
__author__: str
|
||||
|
||||
# References to a class from inside the class:
|
||||
class C:
|
||||
other: C = ... # valid when `__future__.annotations are enabled
|
||||
other2: "C" = ... # always okay
|
||||
def from_str(self, s: str) -> C: ... # valid when `__future__.annotations are enabled
|
||||
def from_str2(self, s: str) -> "C": ... # always okay
|
||||
|
||||
# Circular references:
|
||||
class A:
|
||||
foo: B # valid when `__future__.annotations are enabled
|
||||
foo2: "B" # always okay
|
||||
bar: dict[str, B] # valid when `__future__.annotations are enabled
|
||||
bar2: dict[str, "A"] # always okay
|
||||
|
||||
class B:
|
||||
foo: A # always okay
|
||||
bar: dict[str, A] # always okay
|
||||
|
||||
# Annotations are treated as assignments in .pyi files, but not in .py files
|
||||
class MyClass:
|
||||
foo: int
|
||||
bar = foo # Still invalid even when `__future__.annotations` are enabled
|
||||
bar = "foo" # always okay
|
||||
|
||||
baz: MyClass
|
||||
eggs = baz # Still invalid even when `__future__.annotations` are enabled
|
||||
eggs = "baz" # always okay
|
||||
|
||||
# Forward references:
|
||||
MaybeDStr: TypeAlias = Optional[DStr] # Still invalid even when `__future__.annotations` are enabled
|
||||
MaybeDStr2: TypeAlias = Optional["DStr"] # always okay
|
||||
DStr: TypeAlias = Union[D, str] # Still invalid even when `__future__.annotations` are enabled
|
||||
DStr2: TypeAlias = Union["D", str] # always okay
|
||||
|
||||
class D: ...
|
||||
|
||||
# More circular references
|
||||
class Leaf: ...
|
||||
class Tree(list[Tree | Leaf]): ... # Still invalid even when `__future__.annotations` are enabled
|
||||
class Tree2(list["Tree | Leaf"]): ... # always okay
|
||||
10
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_5.pyi
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_5.pyi
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Test: inner class annotation."""
|
||||
|
||||
class RandomClass:
|
||||
def bad_func(self) -> InnerClass: ... # F821
|
||||
def good_func(self) -> OuterClass.InnerClass: ... # Okay
|
||||
|
||||
class OuterClass:
|
||||
class InnerClass: ...
|
||||
|
||||
def good_func(self) -> InnerClass: ... # Okay
|
||||
4
crates/ruff_linter/resources/test/fixtures/pyflakes/F822_0.pyi
vendored
Normal file
4
crates/ruff_linter/resources/test/fixtures/pyflakes/F822_0.pyi
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
a = 1
|
||||
b: int # Considered a binding in a `.pyi` stub file, not in a `.py` runtime file
|
||||
|
||||
__all__ = ["a", "b", "c"] # c is flagged as missing; b is not
|
||||
32
crates/ruff_linter/resources/test/fixtures/pylint/dict_iter_missing_items.py
vendored
Normal file
32
crates/ruff_linter/resources/test/fixtures/pylint/dict_iter_missing_items.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
d = {1: 1, 2: 2}
|
||||
d_tuple = {(1, 2): 3, (4, 5): 6}
|
||||
d_tuple_annotated: Any = {(1, 2): 3, (4, 5): 6}
|
||||
d_tuple_incorrect_tuple = {(1,): 3, (4, 5): 6}
|
||||
l = [1, 2]
|
||||
s1 = {1, 2}
|
||||
s2 = {1, 2, 3}
|
||||
|
||||
# Errors
|
||||
for k, v in d:
|
||||
pass
|
||||
|
||||
for k, v in d_tuple_incorrect_tuple:
|
||||
pass
|
||||
|
||||
|
||||
# Non errors
|
||||
for k, v in d.items():
|
||||
pass
|
||||
for k in d.keys():
|
||||
pass
|
||||
for i, v in enumerate(l):
|
||||
pass
|
||||
for i, v in s1.intersection(s2):
|
||||
pass
|
||||
for a, b in d_tuple:
|
||||
pass
|
||||
for a, b in d_tuple_annotated:
|
||||
pass
|
||||
37
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_bool.py
vendored
Normal file
37
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_bool.py
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# These testcases should raise errors
|
||||
|
||||
class Float:
|
||||
def __bool__(self):
|
||||
return 3.05 # [invalid-bool-return]
|
||||
|
||||
class Int:
|
||||
def __bool__(self):
|
||||
return 0 # [invalid-bool-return]
|
||||
|
||||
|
||||
class Str:
|
||||
def __bool__(self):
|
||||
x = "ruff"
|
||||
return x # [invalid-bool-return]
|
||||
|
||||
# TODO: Once Ruff has better type checking
|
||||
def return_int():
|
||||
return 3
|
||||
|
||||
class ComplexReturn:
|
||||
def __bool__(self):
|
||||
return return_int() # [invalid-bool-return]
|
||||
|
||||
|
||||
|
||||
# These testcases should NOT raise errors
|
||||
|
||||
class Bool:
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
|
||||
class Bool2:
|
||||
def __bool__(self):
|
||||
x = True
|
||||
return x
|
||||
@@ -1,28 +1,36 @@
|
||||
class Str:
|
||||
def __str__(self):
|
||||
return 1
|
||||
# These testcases should raise errors
|
||||
|
||||
class Float:
|
||||
def __str__(self):
|
||||
return 3.05
|
||||
|
||||
|
||||
class Int:
|
||||
def __str__(self):
|
||||
return 1
|
||||
|
||||
class Int2:
|
||||
def __str__(self):
|
||||
return 0
|
||||
|
||||
|
||||
class Bool:
|
||||
def __str__(self):
|
||||
return False
|
||||
|
||||
class Str2:
|
||||
def __str__(self):
|
||||
x = "ruff"
|
||||
return x
|
||||
|
||||
# TODO fixme once Ruff has better type checking
|
||||
|
||||
# TODO: Once Ruff has better type checking
|
||||
def return_int():
|
||||
return 3
|
||||
|
||||
class ComplexReturn:
|
||||
def __str__(self):
|
||||
return return_int()
|
||||
return return_int()
|
||||
|
||||
# These testcases should NOT raise errors
|
||||
|
||||
class Str:
|
||||
def __str__(self):
|
||||
return "ruff"
|
||||
|
||||
class Str2:
|
||||
def __str__(self):
|
||||
x = "ruff"
|
||||
return x
|
||||
|
||||
@@ -17,3 +17,14 @@ class Fruit:
|
||||
return choice(Fruit.COLORS)
|
||||
|
||||
pick_one_color = staticmethod(pick_one_color)
|
||||
|
||||
class Class:
|
||||
def class_method(cls):
|
||||
pass
|
||||
|
||||
class_method = classmethod(class_method);another_statement
|
||||
|
||||
def static_method():
|
||||
pass
|
||||
|
||||
static_method = staticmethod(static_method);
|
||||
|
||||
@@ -54,3 +54,15 @@ class StudentE(StudentD):
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
|
||||
class StudentF(object):
|
||||
__slots__ = ("name", "__dict__")
|
||||
|
||||
def __init__(self, name, middle_name):
|
||||
self.name = name
|
||||
self.middle_name = middle_name # [assigning-non-slot]
|
||||
self.setup()
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
@@ -51,3 +51,7 @@ foo == foo or foo == bar # Self-comparison.
|
||||
foo[0] == "a" or foo[0] == "b" # Subscripts.
|
||||
|
||||
foo() == "a" or foo() == "b" # Calls.
|
||||
|
||||
import sys
|
||||
|
||||
sys.platform == "win32" or sys.platform == "emscripten" # sys attributes
|
||||
|
||||
39
crates/ruff_linter/resources/test/fixtures/pylint/singledispatch_method.py
vendored
Normal file
39
crates/ruff_linter/resources/test/fixtures/pylint/singledispatch_method.py
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
from functools import singledispatch, singledispatchmethod
|
||||
|
||||
|
||||
@singledispatch
|
||||
def convert_position(position):
|
||||
pass
|
||||
|
||||
|
||||
class Board:
|
||||
@singledispatch # [singledispatch-method]
|
||||
@classmethod
|
||||
def convert_position(cls, position):
|
||||
pass
|
||||
|
||||
@singledispatch # [singledispatch-method]
|
||||
def move(self, position):
|
||||
pass
|
||||
|
||||
@singledispatchmethod
|
||||
def place(self, position):
|
||||
pass
|
||||
|
||||
@singledispatch
|
||||
@staticmethod
|
||||
def do(position):
|
||||
pass
|
||||
|
||||
# False negative (flagged by Pylint).
|
||||
@convert_position.register
|
||||
@classmethod
|
||||
def _(cls, position: str) -> tuple:
|
||||
position_a, position_b = position.split(",")
|
||||
return (int(position_a), int(position_b))
|
||||
|
||||
# False negative (flagged by Pylint).
|
||||
@convert_position.register
|
||||
@classmethod
|
||||
def _(cls, position: tuple) -> str:
|
||||
return f"{position[0]},{position[1]}"
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
a = 2
|
||||
print((3.0).__add__(4.0)) # PLC2801
|
||||
print((3.0).__sub__(4.0)) # PLC2801
|
||||
print((3.0).__mul__(4.0)) # PLC2801
|
||||
@@ -17,6 +17,67 @@ print((3.0).__str__()) # PLC2801
|
||||
print((3.0).__repr__()) # PLC2801
|
||||
print([1, 2, 3].__len__()) # PLC2801
|
||||
print((1).__neg__()) # PLC2801
|
||||
print(-a.__sub__(1)) # PLC2801
|
||||
print(-(a).__sub__(1)) # PLC2801
|
||||
print(-(-a.__sub__(1))) # PLC2801
|
||||
print((5 - a).__sub__(1)) # PLC2801
|
||||
print(-(5 - a).__sub__(1)) # PLC2801
|
||||
print(-(-5 - a).__sub__(1)) # PLC2801
|
||||
print(+-+-+-a.__sub__(1)) # PLC2801
|
||||
print(a.__rsub__(2 - 1)) # PLC2801
|
||||
print(a.__sub__(((((1)))))) # PLC2801
|
||||
print(a.__sub__(((((2 - 1)))))) # PLC2801
|
||||
print(a.__sub__(
|
||||
3
|
||||
+
|
||||
4
|
||||
))
|
||||
print(a.__rsub__(
|
||||
3
|
||||
+
|
||||
4
|
||||
))
|
||||
print(2 * a.__add__(3)) # PLC2801
|
||||
x = 2 * a.__add__(3) # PLC2801
|
||||
x = 2 * -a.__add__(3) # PLC2801
|
||||
x = a.__add__(3) # PLC2801
|
||||
x = -a.__add__(3) # PLC2801
|
||||
x = (-a).__add__(3) # PLC2801
|
||||
x = -(-a).__add__(3) # PLC2801
|
||||
|
||||
# Calls
|
||||
print(a.__call__()) # PLC2801 (no fix, intentional)
|
||||
|
||||
# Lambda expressions
|
||||
blah = lambda: a.__add__(1) # PLC2801
|
||||
|
||||
# If expressions
|
||||
print(a.__add__(1) if a > 0 else a.__sub__(1)) # PLC2801
|
||||
|
||||
# Dict/Set/List/Tuple
|
||||
print({"a": a.__add__(1)}) # PLC2801
|
||||
print({a.__add__(1)}) # PLC2801
|
||||
print([a.__add__(1)]) # PLC2801
|
||||
print((a.__add__(1),)) # PLC2801
|
||||
|
||||
# Comprehension variants
|
||||
print({i: i.__add__(1) for i in range(5)}) # PLC2801
|
||||
print({i.__add__(1) for i in range(5)}) # PLC2801
|
||||
print([i.__add__(1) for i in range(5)]) # PLC2801
|
||||
print((i.__add__(1) for i in range(5))) # PLC2801
|
||||
|
||||
# Generators
|
||||
gen = (i.__add__(1) for i in range(5)) # PLC2801
|
||||
print(next(gen))
|
||||
|
||||
# Subscripts
|
||||
print({"a": a.__add__(1)}["a"]) # PLC2801
|
||||
|
||||
# Starred
|
||||
print(*[a.__add__(1)]) # PLC2801
|
||||
|
||||
# Slices
|
||||
print([a.__add__(1), a.__sub__(1)][0:1]) # PLC2801
|
||||
|
||||
|
||||
class Thing:
|
||||
|
||||
121
crates/ruff_linter/resources/test/fixtures/pylint/useless_exception_statement.py
vendored
Normal file
121
crates/ruff_linter/resources/test/fixtures/pylint/useless_exception_statement.py
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from contextlib import suppress
|
||||
|
||||
|
||||
# Test case 1: Useless exception statement
|
||||
def func():
|
||||
AssertionError("This is an assertion error") # PLW0133
|
||||
|
||||
|
||||
# Test case 2: Useless exception statement in try-except block
|
||||
def func():
|
||||
try:
|
||||
Exception("This is an exception") # PLW0133
|
||||
except Exception as err:
|
||||
pass
|
||||
|
||||
|
||||
# Test case 3: Useless exception statement in if statement
|
||||
def func():
|
||||
if True:
|
||||
RuntimeError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 4: Useless exception statement in class
|
||||
def func():
|
||||
class Class:
|
||||
def __init__(self):
|
||||
TypeError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 5: Useless exception statement in function
|
||||
def func():
|
||||
def inner():
|
||||
IndexError("This is an exception") # PLW0133
|
||||
|
||||
inner()
|
||||
|
||||
|
||||
# Test case 6: Useless exception statement in while loop
|
||||
def func():
|
||||
while True:
|
||||
KeyError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 7: Useless exception statement in abstract class
|
||||
def func():
|
||||
class Class(ABC):
|
||||
@abstractmethod
|
||||
def method(self):
|
||||
NotImplementedError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 8: Useless exception statement inside context manager
|
||||
def func():
|
||||
with suppress(AttributeError):
|
||||
AttributeError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 9: Useless exception statement in parentheses
|
||||
def func():
|
||||
(RuntimeError("This is an exception")) # PLW0133
|
||||
|
||||
|
||||
# Test case 10: Useless exception statement in continuation
|
||||
def func():
|
||||
x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
|
||||
|
||||
# Test case 11: Useless warning statement
|
||||
def func():
|
||||
UserWarning("This is an assertion error") # PLW0133
|
||||
|
||||
|
||||
# Non-violation test cases: PLW0133
|
||||
|
||||
|
||||
# Test case 1: Used exception statement in try-except block
|
||||
def func():
|
||||
raise Exception("This is an exception") # OK
|
||||
|
||||
|
||||
# Test case 2: Used exception statement in if statement
|
||||
def func():
|
||||
if True:
|
||||
raise ValueError("This is an exception") # OK
|
||||
|
||||
|
||||
# Test case 3: Used exception statement in class
|
||||
def func():
|
||||
class Class:
|
||||
def __init__(self):
|
||||
raise TypeError("This is an exception") # OK
|
||||
|
||||
|
||||
# Test case 4: Exception statement used in list comprehension
|
||||
def func():
|
||||
[ValueError("This is an exception") for i in range(10)] # OK
|
||||
|
||||
|
||||
# Test case 5: Exception statement used when initializing a dictionary
|
||||
def func():
|
||||
{i: TypeError("This is an exception") for i in range(10)} # OK
|
||||
|
||||
|
||||
# Test case 6: Exception statement used in function
|
||||
def func():
|
||||
def inner():
|
||||
raise IndexError("This is an exception") # OK
|
||||
|
||||
inner()
|
||||
|
||||
|
||||
# Test case 7: Exception statement used in variable assignment
|
||||
def func():
|
||||
err = KeyError("This is an exception") # OK
|
||||
|
||||
|
||||
# Test case 8: Exception statement inside context manager
|
||||
def func():
|
||||
with suppress(AttributeError):
|
||||
raise AttributeError("This is an exception") # OK
|
||||
@@ -33,7 +33,7 @@ bool(b"")
|
||||
bool(1.0)
|
||||
int().denominator
|
||||
|
||||
# These become string or byte literals
|
||||
# These become literals
|
||||
str()
|
||||
str("foo")
|
||||
str("""
|
||||
@@ -53,3 +53,9 @@ bool(False)
|
||||
|
||||
# These become a literal but retain parentheses
|
||||
int(1).denominator
|
||||
|
||||
# These too are literals in spirit
|
||||
int(+1)
|
||||
int(-1)
|
||||
float(+1.0)
|
||||
float(-1.0)
|
||||
|
||||
@@ -252,3 +252,10 @@ raise ValueError(
|
||||
|
||||
# The dictionary should be parenthesized.
|
||||
"{}".format({0: 1}())
|
||||
|
||||
# The string shouldn't be converted, since it would require repeating the function call.
|
||||
"{x} {x}".format(x=foo())
|
||||
"{0} {0}".format(foo())
|
||||
|
||||
# The string _should_ be converted, since the function call is repeated in the arguments.
|
||||
"{0} {1}".format(foo(), foo())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user