Compare commits

..

2 Commits

Author SHA1 Message Date
Micha Reiser
d3e160dcb7 Indent expanded binary expressions 2024-02-14 18:53:11 +01:00
Micha Reiser
003851b54c Beautify 2024-02-14 18:10:38 +01:00
1083 changed files with 42433 additions and 64015 deletions

2
.gitattributes vendored
View File

@@ -2,8 +2,6 @@
crates/ruff_linter/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf crates/ruff_linter/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_2.py text eol=crlf
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py text eol=crlf
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf

6
.github/CODEOWNERS vendored
View File

@@ -7,9 +7,3 @@
# Jupyter # Jupyter
/crates/ruff_linter/src/jupyter/ @dhruvmanila /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

View File

@@ -3,8 +3,6 @@ Thank you for taking the time to report an issue! We're glad to have you involve
If you're filing a bug report, please consider including the following information: If you're filing a bug report, please consider including the following information:
* List of keywords you searched for before creating this issue. Write them down here so that others can find this issue more easily and help provide feedback.
e.g. "RUF001", "unused variable", "Jupyter notebook"
* A minimal code snippet that reproduces the bug. * A minimal code snippet that reproduces the bug.
* The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag. * The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
* The current Ruff settings (any relevant sections from your `pyproject.toml`). * The current Ruff settings (any relevant sections from your `pyproject.toml`).

View File

@@ -133,7 +133,7 @@ jobs:
env: env:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025). # Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
RUSTDOCFLAGS: "-D warnings" RUSTDOCFLAGS: "-D warnings"
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
with: with:
name: ruff name: ruff
path: target/debug/ruff path: target/debug/ruff
@@ -238,7 +238,7 @@ jobs:
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
name: Download comparison Ruff binary name: Download comparison Ruff binary
id: ruff-target id: ruff-target
with: with:
@@ -250,7 +250,6 @@ jobs:
with: with:
name: ruff name: ruff
branch: ${{ github.event.pull_request.base.ref }} branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true check_artifacts: true
- name: Install ruff-ecosystem - name: Install ruff-ecosystem
@@ -325,13 +324,13 @@ jobs:
run: | run: |
echo ${{ github.event.number }} > pr-number echo ${{ github.event.number }} > pr-number
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
name: Upload PR Number name: Upload PR Number
with: with:
name: pr-number name: pr-number
path: pr-number path: pr-number
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
name: Upload Results name: Upload Results
with: with:
name: ecosystem-result name: ecosystem-result
@@ -472,7 +471,7 @@ jobs:
- determine_changes - determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }} if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
steps: steps:
- uses: extractions/setup-just@v2 - uses: extractions/setup-just@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -485,7 +484,7 @@ jobs:
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
name: Download development ruff binary name: Download development ruff binary
id: ruff-target id: ruff-target
with: with:

View File

@@ -52,9 +52,9 @@ jobs:
ruff --help ruff --help
python -m ruff --help python -m ruff --help
- name: "Upload sdist" - name: "Upload sdist"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: wheels-sdist name: wheels
path: dist path: dist
macos-x86_64: macos-x86_64:
@@ -80,9 +80,9 @@ jobs:
ruff --help ruff --help
python -m ruff --help python -m ruff --help
- name: "Upload wheels" - name: "Upload wheels"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: wheels-macos-x86_64 name: wheels
path: dist path: dist
- name: "Archive binary" - name: "Archive binary"
run: | run: |
@@ -90,9 +90,9 @@ jobs:
tar czvf $ARCHIVE_FILE -C target/x86_64-apple-darwin/release ruff tar czvf $ARCHIVE_FILE -C target/x86_64-apple-darwin/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: binaries-macos-x86_64 name: binaries
path: | path: |
*.tar.gz *.tar.gz
*.sha256 *.sha256
@@ -119,9 +119,9 @@ jobs:
ruff --help ruff --help
python -m ruff --help python -m ruff --help
- name: "Upload wheels" - name: "Upload wheels"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: wheels-aarch64-apple-darwin name: wheels
path: dist path: dist
- name: "Archive binary" - name: "Archive binary"
run: | run: |
@@ -129,9 +129,9 @@ jobs:
tar czvf $ARCHIVE_FILE -C target/aarch64-apple-darwin/release ruff tar czvf $ARCHIVE_FILE -C target/aarch64-apple-darwin/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: binaries-aarch64-apple-darwin name: binaries
path: | path: |
*.tar.gz *.tar.gz
*.sha256 *.sha256
@@ -170,9 +170,9 @@ jobs:
ruff --help ruff --help
python -m ruff --help python -m ruff --help
- name: "Upload wheels" - name: "Upload wheels"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: wheels-${{ matrix.platform.target }} name: wheels
path: dist path: dist
- name: "Archive binary" - name: "Archive binary"
shell: bash shell: bash
@@ -181,9 +181,9 @@ jobs:
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe 7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: binaries-${{ matrix.platform.target }} name: binaries
path: | path: |
*.zip *.zip
*.sha256 *.sha256
@@ -218,9 +218,9 @@ jobs:
ruff --help ruff --help
python -m ruff --help python -m ruff --help
- name: "Upload wheels" - name: "Upload wheels"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: wheels-${{ matrix.target }} name: wheels
path: dist path: dist
- name: "Archive binary" - name: "Archive binary"
run: | run: |
@@ -228,9 +228,9 @@ jobs:
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: binaries-${{ matrix.target }} name: binaries
path: | path: |
*.tar.gz *.tar.gz
*.sha256 *.sha256
@@ -251,12 +251,8 @@ jobs:
arch: s390x arch: s390x
- target: powerpc64le-unknown-linux-gnu - target: powerpc64le-unknown-linux-gnu
arch: ppc64le 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 - target: powerpc64-unknown-linux-gnu
arch: ppc64 arch: ppc64
# see https://github.com/astral-sh/ruff/issues/10073
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -289,9 +285,9 @@ jobs:
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
ruff --help ruff --help
- name: "Upload wheels" - name: "Upload wheels"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: wheels-${{ matrix.platform.target }} name: wheels
path: dist path: dist
- name: "Archive binary" - name: "Archive binary"
run: | run: |
@@ -299,9 +295,9 @@ jobs:
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: binaries-${{ matrix.platform.target }} name: binaries
path: | path: |
*.tar.gz *.tar.gz
*.sha256 *.sha256
@@ -341,9 +337,9 @@ jobs:
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall .venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
.venv/bin/ruff check --help .venv/bin/ruff check --help
- name: "Upload wheels" - name: "Upload wheels"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: wheels-${{ matrix.target }} name: wheels
path: dist path: dist
- name: "Archive binary" - name: "Archive binary"
run: | run: |
@@ -351,9 +347,9 @@ jobs:
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: binaries-${{ matrix.target }} name: binaries
path: | path: |
*.tar.gz *.tar.gz
*.sha256 *.sha256
@@ -398,9 +394,9 @@ jobs:
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall .venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
.venv/bin/ruff check --help .venv/bin/ruff check --help
- name: "Upload wheels" - name: "Upload wheels"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: wheels-${{ matrix.platform.target }} name: wheels
path: dist path: dist
- name: "Archive binary" - name: "Archive binary"
run: | run: |
@@ -408,9 +404,9 @@ jobs:
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: binaries-${{ matrix.platform.target }} name: binaries
path: | path: |
*.tar.gz *.tar.gz
*.sha256 *.sha256
@@ -467,11 +463,10 @@ jobs:
# For pypi trusted publishing # For pypi trusted publishing
id-token: write id-token: write
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
with: with:
pattern: wheels-* name: wheels
path: wheels path: wheels
merge-multiple: true
- name: Publish to PyPi - name: Publish to PyPi
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@release/v1
with: with:
@@ -511,13 +506,12 @@ jobs:
# For GitHub release publishing # For GitHub release publishing
contents: write contents: write
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
with: with:
pattern: binaries-* name: binaries
path: binaries path: binaries
merge-multiple: true
- name: "Publish to GitHub" - name: "Publish to GitHub"
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v1
with: with:
draft: true draft: true
files: binaries/* files: binaries/*

View File

@@ -1,56 +1,5 @@
# Breaking Changes # 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 ## 0.1.9
### `site-packages` is now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513)) ### `site-packages` is now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))

View File

@@ -1,186 +1,5 @@
# Changelog # Changelog
## 0.3.2
### Preview features
- Improve single-`with` item formatting for Python 3.8 or older ([#10276](https://github.com/astral-sh/ruff/pull/10276))
### Rule changes
- \[`pyupgrade`\] Allow fixes for f-string rule regardless of line length (`UP032`) ([#10263](https://github.com/astral-sh/ruff/pull/10263))
- \[`pycodestyle`\] Include actual conditions in E712 diagnostics ([#10254](https://github.com/astral-sh/ruff/pull/10254))
### Bug fixes
- Fix trailing kwargs end of line comment after slash ([#10297](https://github.com/astral-sh/ruff/pull/10297))
- Fix unstable `with` items formatting ([#10274](https://github.com/astral-sh/ruff/pull/10274))
- Avoid repeating function calls in f-string conversions ([#10265](https://github.com/astral-sh/ruff/pull/10265))
- Fix E203 false positive for slices in format strings ([#10280](https://github.com/astral-sh/ruff/pull/10280))
- Fix incorrect `Parameter` range for `*args` and `**kwargs` ([#10283](https://github.com/astral-sh/ruff/pull/10283))
- Treat `typing.Annotated` subscripts as type definitions ([#10285](https://github.com/astral-sh/ruff/pull/10285))
## 0.3.1
### Preview features
- \[`pycodestyle`\] Fix E301 not triggering on decorated methods. ([#10117](https://github.com/astral-sh/ruff/pull/10117))
- \[`pycodestyle`\] Respect `isort` settings in blank line rules (`E3*`) ([#10096](https://github.com/astral-sh/ruff/pull/10096))
- \[`pycodestyle`\] Make blank lines in typing stub files optional (`E3*`) ([#10098](https://github.com/astral-sh/ruff/pull/10098))
- \[`pylint`\] Implement `singledispatch-method` (`E1519`) ([#10140](https://github.com/astral-sh/ruff/pull/10140))
- \[`pylint`\] Implement `useless-exception-statement` (`W0133`) ([#10176](https://github.com/astral-sh/ruff/pull/10176))
### Rule changes
- \[`flake8-debugger`\] Check for use of `debugpy` and `ptvsd` debug modules (#10177) ([#10194](https://github.com/astral-sh/ruff/pull/10194))
- \[`pyupgrade`\] Generate diagnostic for all valid f-string conversions regardless of line length (`UP032`) ([#10238](https://github.com/astral-sh/ruff/pull/10238))
- \[`pep8_naming`\] Add fixes for `N804` and `N805` ([#10215](https://github.com/astral-sh/ruff/pull/10215))
### CLI
- Colorize the output of `ruff format --diff` ([#10110](https://github.com/astral-sh/ruff/pull/10110))
- Make `--config` and `--isolated` global flags ([#10150](https://github.com/astral-sh/ruff/pull/10150))
- Correctly expand tildes and environment variables in paths passed to `--config` ([#10219](https://github.com/astral-sh/ruff/pull/10219))
### Configuration
- Accept a PEP 440 version specifier for `required-version` ([#10216](https://github.com/astral-sh/ruff/pull/10216))
- Implement isort's `default-section` setting ([#10149](https://github.com/astral-sh/ruff/pull/10149))
### Bug fixes
- Remove trailing space from `CapWords` message ([#10220](https://github.com/astral-sh/ruff/pull/10220))
- Respect external codes in file-level exemptions ([#10203](https://github.com/astral-sh/ruff/pull/10203))
- \[`flake8-raise`\] Avoid false-positives for parens-on-raise with `future.exception()` (`RSE102`) ([#10206](https://github.com/astral-sh/ruff/pull/10206))
- \[`pylint`\] Add fix for unary expressions in `PLC2801` ([#9587](https://github.com/astral-sh/ruff/pull/9587))
- \[`ruff`\] Fix RUF028 not allowing `# fmt: skip` on match cases ([#10178](https://github.com/astral-sh/ruff/pull/10178))
## 0.3.0
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:
- Initial support formatting f-strings (in `--preview`).
- Support for overriding arbitrary configuration options via the CLI through an expanded `--config`
argument (e.g., `--config "lint.isort.combine-as-imports=false"`).
- Significant performance improvements in Ruff's lexer, parser, and lint rules.
### Preview features
- Implement minimal f-string formatting ([#9642](https://github.com/astral-sh/ruff/pull/9642))
- \[`pycodestyle`\] Add blank line(s) rules (`E301`, `E302`, `E303`, `E304`, `E305`, `E306`) ([#9266](https://github.com/astral-sh/ruff/pull/9266))
- \[`refurb`\] Implement `readlines_in_for` (`FURB129`) ([#9880](https://github.com/astral-sh/ruff/pull/9880))
### Rule changes
- \[`ruff`\] Ensure closing parentheses for multiline sequences are always on their own line (`RUF022`, `RUF023`) ([#9793](https://github.com/astral-sh/ruff/pull/9793))
- \[`numpy`\] Add missing deprecation violations (`NPY002`) ([#9862](https://github.com/astral-sh/ruff/pull/9862))
- \[`flake8-bandit`\] Detect `mark_safe` usages in decorators ([#9887](https://github.com/astral-sh/ruff/pull/9887))
- \[`ruff`\] Expand `asyncio-dangling-task` (`RUF006`) to include `new_event_loop` ([#9976](https://github.com/astral-sh/ruff/pull/9976))
- \[`flake8-pyi`\] Ignore 'unused' private type dicts in class scopes ([#9952](https://github.com/astral-sh/ruff/pull/9952))
### Formatter
- Docstring formatting: Preserve tab indentation when using `indent-style=tabs` ([#9915](https://github.com/astral-sh/ruff/pull/9915))
- Disable top-level docstring formatting for notebooks ([#9957](https://github.com/astral-sh/ruff/pull/9957))
- Stabilize quote-style's `preserve` mode ([#9922](https://github.com/astral-sh/ruff/pull/9922))
### CLI
- Allow arbitrary configuration options to be overridden via the CLI ([#9599](https://github.com/astral-sh/ruff/pull/9599))
### Bug fixes
- Make `show-settings` filters directory-agnostic ([#9866](https://github.com/astral-sh/ruff/pull/9866))
- Respect duplicates when rewriting type aliases ([#9905](https://github.com/astral-sh/ruff/pull/9905))
- Respect tuple assignments in typing analyzer ([#9969](https://github.com/astral-sh/ruff/pull/9969))
- Use atomic write when persisting cache ([#9981](https://github.com/astral-sh/ruff/pull/9981))
- Use non-parenthesized range for `DebugText` ([#9953](https://github.com/astral-sh/ruff/pull/9953))
- \[`flake8-simplify`\] Avoid false positive with `async` for loops (`SIM113`) ([#9996](https://github.com/astral-sh/ruff/pull/9996))
- \[`flake8-trio`\] Respect `async with` in `timeout-without-await` ([#9859](https://github.com/astral-sh/ruff/pull/9859))
- \[`perflint`\] Catch a wider range of mutations in `PERF101` ([#9955](https://github.com/astral-sh/ruff/pull/9955))
- \[`pycodestyle`\] Fix `E30X` panics on blank lines with trailing white spaces ([#9907](https://github.com/astral-sh/ruff/pull/9907))
- \[`pydocstyle`\] Allow using `parameters` as a subsection header (`D405`) ([#9894](https://github.com/astral-sh/ruff/pull/9894))
- \[`pydocstyle`\] Fix blank-line docstring rules for module-level docstrings ([#9878](https://github.com/astral-sh/ruff/pull/9878))
- \[`pylint`\] Accept 0.0 and 1.0 as common magic values (`PLR2004`) ([#9964](https://github.com/astral-sh/ruff/pull/9964))
- \[`pylint`\] Avoid suggesting set rewrites for non-hashable types ([#9956](https://github.com/astral-sh/ruff/pull/9956))
- \[`ruff`\] Avoid false negatives with string literals inside of method calls (`RUF027`) ([#9865](https://github.com/astral-sh/ruff/pull/9865))
- \[`ruff`\] Fix panic on with f-string detection (`RUF027`) ([#9990](https://github.com/astral-sh/ruff/pull/9990))
- \[`ruff`\] Ignore builtins when detecting missing f-strings ([#9849](https://github.com/astral-sh/ruff/pull/9849))
### Performance
- Use `memchr` for string lexing ([#9888](https://github.com/astral-sh/ruff/pull/9888))
- Use `memchr` for tab-indentation detection ([#9853](https://github.com/astral-sh/ruff/pull/9853))
- Reduce `Result<Tok, LexicalError>` size by using `Box<str>` instead of `String` ([#9885](https://github.com/astral-sh/ruff/pull/9885))
- Reduce size of `Expr` from 80 to 64 bytes ([#9900](https://github.com/astral-sh/ruff/pull/9900))
- Improve trailing comma rule performance ([#9867](https://github.com/astral-sh/ruff/pull/9867))
- Remove unnecessary string cloning from the parser ([#9884](https://github.com/astral-sh/ruff/pull/9884))
## 0.2.1 ## 0.2.1
This release includes support for range formatting (i.e., the ability to format specific lines This release includes support for range formatting (i.e., the ability to format specific lines

View File

@@ -39,7 +39,7 @@ For small changes (e.g., bug fixes), feel free to submit a PR.
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change. creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
You can also join us on [**Discord**](https://discord.com/invite/astral-sh) to discuss your idea with the You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5) to discuss your idea with the
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug) in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted) and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted)
@@ -316,7 +316,7 @@ To preview any changes to the documentation locally:
``` ```
The documentation should then be available locally at The documentation should then be available locally at
[http://127.0.0.1:8000/ruff/](http://127.0.0.1:8000/ruff/). [http://127.0.0.1:8000/docs/](http://127.0.0.1:8000/docs/).
## Release Process ## Release Process
@@ -329,13 +329,13 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
### Creating a new release ### Creating a new release
1. Install `uv`: `curl -LsSf https://astral.sh/uv/install.sh | sh` We use an experimental in-house tool for managing releases.
1. Run `./scripts/release/bump.sh`; this command will:
- Generate a temporary virtual environment with `rooster` 1. Install `rooster`: `pip install git+https://github.com/zanieb/rooster@main`
1. Run `rooster release`; this command will:
- Generate a changelog entry in `CHANGELOG.md` - Generate a changelog entry in `CHANGELOG.md`
- Update versions in `pyproject.toml` and `Cargo.toml` - Update versions in `pyproject.toml` and `Cargo.toml`
- Update references to versions in the `README.md` and documentation - Update references to versions in the `README.md` and documentation
- Display contributors for the release
1. The changelog should then be editorialized for consistency 1. The changelog should then be editorialized for consistency
- Often labels will be missing from pull requests they will need to be manually organized into the proper section - Often labels will be missing from pull requests they will need to be manually organized into the proper section
- Changes should be edited to be user-facing descriptions, avoiding internal details - Changes should be edited to be user-facing descriptions, avoiding internal details
@@ -359,7 +359,7 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
1. Open the draft release in the GitHub release section 1. Open the draft release in the GitHub release section
1. Copy the changelog for the release into the GitHub release 1. Copy the changelog for the release into the GitHub release
- See previous releases for formatting of section headers - See previous releases for formatting of section headers
1. Append the contributors from the `bump.sh` script 1. Generate the contributor list with `rooster contributors` and add to the release notes
1. If needed, [update the schemastore](https://github.com/astral-sh/ruff/blob/main/scripts/update_schemastore.py). 1. If needed, [update the schemastore](https://github.com/astral-sh/ruff/blob/main/scripts/update_schemastore.py).
1. One can determine if an update is needed when 1. One can determine if an update is needed when
`git diff old-version-tag new-version-tag -- ruff.schema.json` returns a non-empty diff. `git diff old-version-tag new-version-tag -- ruff.schema.json` returns a non-empty diff.

526
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,55 +14,50 @@ license = "MIT"
[workspace.dependencies] [workspace.dependencies]
aho-corasick = { version = "1.1.2" } aho-corasick = { version = "1.1.2" }
annotate-snippets = { version = "0.9.2", features = ["color"] } annotate-snippets = { version = "0.9.2", features = ["color"] }
anyhow = { version = "1.0.80" } anyhow = { version = "1.0.79" }
argfile = { version = "0.1.6" } argfile = { version = "0.1.6" }
assert_cmd = { version = "2.0.13" } assert_cmd = { version = "2.0.13" }
bincode = { version = "1.3.3" } bincode = { version = "1.3.3" }
bitflags = { version = "2.4.1" } bitflags = { version = "2.4.1" }
bstr = { version = "1.9.1" } bstr = { version = "1.9.0" }
cachedir = { version = "0.3.1" } cachedir = { version = "0.3.1" }
chrono = { version = "0.4.35", default-features = false, features = ["clock"] } chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
clap = { version = "4.5.2", features = ["derive"] } clap = { version = "4.4.18", features = ["derive"] }
clap_complete_command = { version = "0.5.1" } clap_complete_command = { version = "0.5.1" }
clearscreen = { version = "2.0.0" } clearscreen = { version = "2.0.0" }
codspeed-criterion-compat = { version = "2.4.0", default-features = false } codspeed-criterion-compat = { version = "2.3.3", default-features = false }
colored = { version = "2.1.0" } colored = { version = "2.1.0" }
configparser = { version = "3.0.3" } configparser = { version = "3.0.3" }
console_error_panic_hook = { version = "0.1.7" } console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" } console_log = { version = "1.0.0" }
countme = { version = "3.0.1" } countme = { version ="3.0.1"}
criterion = { version = "0.5.1", default-features = false } criterion = { version = "0.5.1", default-features = false }
crossbeam = { version = "0.8.4" }
dirs = { version = "5.0.0" } dirs = { version = "5.0.0" }
drop_bomb = { version = "0.1.5" } drop_bomb = { version = "0.1.5" }
env_logger = { version = "0.10.1" } env_logger = { version ="0.10.1"}
fern = { version = "0.6.1" } fern = { version = "0.6.1" }
filetime = { version = "0.2.23" } filetime = { version = "0.2.23" }
fs-err = { version = "2.11.0" } fs-err = { version ="2.11.0"}
glob = { version = "0.3.1" } glob = { version = "0.3.1" }
globset = { version = "0.4.14" } globset = { version = "0.4.14" }
hexf-parse = { version = "0.2.1" } hexf-parse = { version ="0.2.1"}
ignore = { version = "0.4.22" } ignore = { version = "0.4.22" }
imara-diff = { version = "0.1.5" } imara-diff ={ version = "0.1.5"}
imperative = { version = "1.0.4" } imperative = { version = "1.0.4" }
indicatif = { version = "0.17.8" } indicatif ={ version = "0.17.8"}
indoc = { version = "2.0.4" } indoc ={ version = "2.0.4"}
insta = { version = "1.35.1", feature = ["filters", "glob"] } insta = { version = "1.34.0", feature = ["filters", "glob"] }
insta-cmd = { version = "0.4.0" } insta-cmd = { version = "0.4.0" }
is-macro = { version = "0.3.5" } is-macro = { version = "0.3.5" }
is-wsl = { version = "0.4.0" } is-wsl = { version = "0.4.0" }
itertools = { version = "0.12.1" } itertools = { version = "0.12.1" }
js-sys = { version = "0.3.69" } js-sys = { version = "0.3.67" }
jod-thread = { version = "0.1.2" }
lalrpop-util = { version = "0.20.0", default-features = false } lalrpop-util = { version = "0.20.0", default-features = false }
lexical-parse-float = { version = "0.8.0", features = ["format"] } lexical-parse-float = { version = "0.8.0", features = ["format"] }
libc = { version = "0.2.153" }
libcst = { version = "1.1.0", default-features = false } libcst = { version = "1.1.0", default-features = false }
log = { version = "0.4.17" } log = { version = "0.4.17" }
lsp-server = { version = "0.7.6" }
lsp-types = { version = "0.95.0", features = ["proposed"] }
memchr = { version = "2.7.1" } memchr = { version = "2.7.1" }
mimalloc = { version = "0.1.39" } mimalloc = { version ="0.1.39"}
natord = { version = "1.0.9" } natord = { version = "1.0.9" }
notify = { version = "6.1.1" } notify = { version = "6.1.1" }
once_cell = { version = "1.19.0" } once_cell = { version = "1.19.0" }
@@ -80,39 +75,39 @@ regex = { version = "1.10.2" }
result-like = { version = "0.5.0" } result-like = { version = "0.5.0" }
rustc-hash = { version = "1.1.0" } rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.16" } schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" } seahash = { version ="4.1.0"}
serde = { version = "1.0.197", features = ["derive"] } semver = { version = "1.0.21" }
serde-wasm-bindgen = { version = "0.6.4" } serde = { version = "1.0.196", features = ["derive"] }
serde-wasm-bindgen = { version = "0.6.3" }
serde_json = { version = "1.0.113" } serde_json = { version = "1.0.113" }
serde_test = { version = "1.0.152" } serde_test = { version = "1.0.152" }
serde_with = { version = "3.6.0", default-features = false, features = ["macros"] } serde_with = { version = "3.6.0", default-features = false, features = ["macros"] }
shellexpand = { version = "3.0.0" } shellexpand = { version = "3.0.0" }
shlex = { version = "1.3.0" } shlex = { version ="1.3.0"}
similar = { version = "2.4.0", features = ["inline"] } similar = { version = "2.4.0", features = ["inline"] }
smallvec = { version = "1.13.1" } smallvec = { version = "1.13.1" }
static_assertions = "1.1.0" static_assertions = "1.1.0"
strum = { version = "0.25.0", features = ["strum_macros"] } strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.3" } strum_macros = { version = "0.25.3" }
syn = { version = "2.0.51" } syn = { version = "2.0.40" }
tempfile = { version = "3.9.0" } tempfile = { version ="3.9.0"}
test-case = { version = "3.3.1" } test-case = { version = "3.3.1" }
thiserror = { version = "1.0.57" } thiserror = { version = "1.0.57" }
tikv-jemallocator = { version = "0.5.0" } tikv-jemallocator = { version ="0.5.0"}
toml = { version = "0.8.9" } toml = { version = "0.8.9" }
tracing = { version = "0.1.40" } tracing = { version = "0.1.40" }
tracing-indicatif = { version = "0.3.6" } tracing-indicatif = { version = "0.3.6" }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-tree = { version = "0.2.4" }
typed-arena = { version = "2.0.2" } typed-arena = { version = "2.0.2" }
unic-ucd-category = { version = "0.9" } unic-ucd-category = { version ="0.9"}
unicode-ident = { version = "1.0.12" } unicode-ident = { version = "1.0.12" }
unicode-width = { version = "0.1.11" } unicode-width = { version = "0.1.11" }
unicode_names2 = { version = "1.2.2" } unicode_names2 = { version = "1.2.1" }
ureq = { version = "2.9.6" } ureq = { version = "2.9.1" }
url = { version = "2.5.0" } url = { version = "2.5.0" }
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] } uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
walkdir = { version = "2.3.2" } walkdir = { version = "2.3.2" }
wasm-bindgen = { version = "0.2.92" } wasm-bindgen = { version = "0.2.84" }
wasm-bindgen-test = { version = "0.3.40" } wasm-bindgen-test = { version = "0.3.40" }
wild = { version = "2" } wild = { version = "2" }

View File

@@ -7,9 +7,8 @@
[![image](https://img.shields.io/pypi/l/ruff.svg)](https://pypi.python.org/pypi/ruff) [![image](https://img.shields.io/pypi/l/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/pyversions/ruff.svg)](https://pypi.python.org/pypi/ruff) [![image](https://img.shields.io/pypi/pyversions/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![Actions status](https://github.com/astral-sh/ruff/workflows/CI/badge.svg)](https://github.com/astral-sh/ruff/actions) [![Actions status](https://github.com/astral-sh/ruff/workflows/CI/badge.svg)](https://github.com/astral-sh/ruff/actions)
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.com/invite/astral-sh)
[**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/) [**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
An extremely fast Python linter and code formatter, written in Rust. An extremely fast Python linter and code formatter, written in Rust.
@@ -129,7 +128,7 @@ and with [a variety of other package managers](https://docs.astral.sh/ruff/insta
To run Ruff as a linter, try any of the following: To run Ruff as a linter, try any of the following:
```shell ```shell
ruff check # Lint all files in the current directory (and any subdirectories). ruff check . # Lint all files in the current directory (and any subdirectories).
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories). ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories).
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`. ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`.
ruff check path/to/code/to/file.py # Lint `file.py`. ruff check path/to/code/to/file.py # Lint `file.py`.
@@ -139,7 +138,7 @@ ruff check @arguments.txt # Lint using an input file, treating its con
Or, to run Ruff as a formatter: Or, to run Ruff as a formatter:
```shell ```shell
ruff format # Format all files in the current directory (and any subdirectories). ruff format . # Format all files in the current directory (and any subdirectories).
ruff format path/to/code/ # Format all files in `/path/to/code` (and any subdirectories). ruff format path/to/code/ # Format all files in `/path/to/code` (and any subdirectories).
ruff format path/to/code/*.py # Format all `.py` files in `/path/to/code`. ruff format path/to/code/*.py # Format all `.py` files in `/path/to/code`.
ruff format path/to/code/to/file.py # Format `file.py`. ruff format path/to/code/to/file.py # Format `file.py`.
@@ -151,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml ```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.3.2 rev: v0.2.1
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff
@@ -173,7 +172,7 @@ jobs:
ruff: ruff:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: chartboost/ruff-action@v1 - uses: chartboost/ruff-action@v1
``` ```
@@ -183,9 +182,10 @@ Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml`
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/) [_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
for a complete list of all configuration options). for a complete list of all configuration options).
If left unspecified, Ruff's default configuration is equivalent to the following `ruff.toml` file: If left unspecified, Ruff's default configuration is equivalent to:
```toml ```toml
[tool.ruff]
# Exclude a variety of commonly ignored directories. # Exclude a variety of commonly ignored directories.
exclude = [ exclude = [
".bzr", ".bzr",
@@ -223,7 +223,7 @@ indent-width = 4
# Assume Python 3.8 # Assume Python 3.8
target-version = "py38" target-version = "py38"
[lint] [tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
select = ["E4", "E7", "E9", "F"] select = ["E4", "E7", "E9", "F"]
ignore = [] ignore = []
@@ -235,7 +235,7 @@ unfixable = []
# Allow unused variables when underscore-prefixed. # Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[format] [tool.ruff.format]
# Like Black, use double quotes for strings. # Like Black, use double quotes for strings.
quote-style = "double" quote-style = "double"
@@ -249,20 +249,11 @@ skip-magic-trailing-comma = false
line-ending = "auto" line-ending = "auto"
``` ```
Note that, in a `pyproject.toml`, each section header should be prefixed with `tool.ruff`. For Some configuration options can be provided via the command-line, such as those related to
example, `[lint]` should be replaced with `[tool.ruff.lint]`. rule enablement and disablement, file discovery, and logging level:
Some configuration options can be provided via dedicated command-line arguments, such as those
related to rule enablement and disablement, file discovery, and logging level:
```shell ```shell
ruff check --select F401 --select F403 --quiet ruff check path/to/code/ --select F401 --select F403 --quiet
```
The remaining configuration options can be provided through a catch-all `--config` argument:
```shell
ruff check --config "lint.per-file-ignores = {'some_file.py' = ['F841']}"
``` ```
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format` See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format`
@@ -350,14 +341,14 @@ For a complete enumeration of the supported rules, see [_Rules_](https://docs.as
Contributions are welcome and highly appreciated. To get started, check out the Contributions are welcome and highly appreciated. To get started, check out the
[**contributing guidelines**](https://docs.astral.sh/ruff/contributing/). [**contributing guidelines**](https://docs.astral.sh/ruff/contributing/).
You can also join us on [**Discord**](https://discord.com/invite/astral-sh). You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
## Support ## Support
Having trouble? Check out the existing issues on [**GitHub**](https://github.com/astral-sh/ruff/issues), Having trouble? Check out the existing issues on [**GitHub**](https://github.com/astral-sh/ruff/issues),
or feel free to [**open a new one**](https://github.com/astral-sh/ruff/issues/new). or feel free to [**open a new one**](https://github.com/astral-sh/ruff/issues/new).
You can also ask for help on [**Discord**](https://discord.com/invite/astral-sh). You can also ask for help on [**Discord**](https://discord.gg/c9MhzV8aU5).
## Acknowledgements ## Acknowledgements
@@ -387,7 +378,6 @@ Ruff is released under the MIT license.
Ruff is used by a number of major open-source projects and companies, including: 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)) - Amazon ([AWS SAM](https://github.com/aws/serverless-application-model))
- Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python)) - Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python))
- [Apache Airflow](https://github.com/apache/airflow) - [Apache Airflow](https://github.com/apache/airflow)
@@ -414,7 +404,6 @@ Ruff is used by a number of major open-source projects and companies, including:
- [Ibis](https://github.com/ibis-project/ibis) - [Ibis](https://github.com/ibis-project/ibis)
- [ivy](https://github.com/unifyai/ivy) - [ivy](https://github.com/unifyai/ivy)
- [Jupyter](https://github.com/jupyter-server/jupyter_server) - [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [Kraken Tech](https://kraken.tech/)
- [LangChain](https://github.com/hwchase17/langchain) - [LangChain](https://github.com/hwchase17/langchain)
- [Litestar](https://litestar.dev/) - [Litestar](https://litestar.dev/)
- [LlamaIndex](https://github.com/jerryjliu/llama_index) - [LlamaIndex](https://github.com/jerryjliu/llama_index)

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ruff" name = "ruff"
version = "0.3.2" version = "0.2.1"
publish = false publish = false
authors = { workspace = true } authors = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
@@ -20,7 +20,6 @@ ruff_macros = { path = "../ruff_macros" }
ruff_notebook = { path = "../ruff_notebook" } ruff_notebook = { path = "../ruff_notebook" }
ruff_python_ast = { path = "../ruff_python_ast" } ruff_python_ast = { path = "../ruff_python_ast" }
ruff_python_formatter = { path = "../ruff_python_formatter" } ruff_python_formatter = { path = "../ruff_python_formatter" }
ruff_server = { path = "../ruff_server" }
ruff_source_file = { path = "../ruff_source_file" } ruff_source_file = { path = "../ruff_source_file" }
ruff_text_size = { path = "../ruff_text_size" } ruff_text_size = { path = "../ruff_text_size" }
ruff_workspace = { path = "../ruff_workspace" } ruff_workspace = { path = "../ruff_workspace" }
@@ -53,8 +52,6 @@ tempfile = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
toml = { workspace = true } toml = { workspace = true }
tracing = { workspace = true, features = ["log"] } tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["registry"]}
tracing-tree = { workspace = true }
walkdir = { workspace = true } walkdir = { workspace = true }
wild = { workspace = true } wild = { workspace = true }

View File

@@ -28,52 +28,6 @@ use ruff_workspace::configuration::{Configuration, RuleSelection};
use ruff_workspace::options::{Options, PycodestyleOptions}; use ruff_workspace::options::{Options, PycodestyleOptions};
use ruff_workspace::resolver::ConfigurationTransformer; use ruff_workspace::resolver::ConfigurationTransformer;
/// All configuration options that can be passed "globally",
/// i.e., can be passed to all subcommands
#[derive(Debug, Default, Clone, clap::Args)]
pub struct GlobalConfigArgs {
#[clap(flatten)]
log_level_args: LogLevelArgs,
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
/// or a TOML `<KEY> = <VALUE>` pair
/// (such as you might find in a `ruff.toml` configuration file)
/// overriding a specific configuration option.
/// Overrides of individual settings using this option always take precedence
/// over all configuration files, including configuration files that were also
/// specified using `--config`.
#[arg(
long,
action = clap::ArgAction::Append,
value_name = "CONFIG_OPTION",
value_parser = ConfigArgumentParser,
global = true,
help_heading = "Global options",
)]
pub config: Vec<SingleConfigArgument>,
/// Ignore all configuration files.
//
// Note: We can't mark this as conflicting with `--config` here
// as `--config` can be used for specifying configuration overrides
// as well as configuration files.
// Specifying a configuration file conflicts with `--isolated`;
// specifying a configuration override does not.
// If a user specifies `ruff check --isolated --config=ruff.toml`,
// we emit an error later on, after the initial parsing by clap.
#[arg(long, help_heading = "Global options", global = true)]
pub isolated: bool,
}
impl GlobalConfigArgs {
pub fn log_level(&self) -> LogLevel {
LogLevel::from(&self.log_level_args)
}
#[must_use]
fn partition(self) -> (LogLevel, Vec<SingleConfigArgument>, bool) {
(self.log_level(), self.config, self.isolated)
}
}
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command( #[command(
author, author,
@@ -84,9 +38,9 @@ impl GlobalConfigArgs {
#[command(version)] #[command(version)]
pub struct Args { pub struct Args {
#[command(subcommand)] #[command(subcommand)]
pub(crate) command: Command, pub command: Command,
#[clap(flatten)] #[clap(flatten)]
pub(crate) global_options: GlobalConfigArgs, pub log_level_args: LogLevelArgs,
} }
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
@@ -109,6 +63,10 @@ pub enum Command {
/// Output format /// Output format
#[arg(long, value_enum, default_value = "text")] #[arg(long, value_enum, default_value = "text")]
output_format: HelpFormat, 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. /// List or describe the available configuration options.
Config { option: Option<String> }, Config { option: Option<String> },
@@ -117,6 +75,10 @@ pub enum Command {
/// Output format /// Output format
#[arg(long, value_enum, default_value = "text")] #[arg(long, value_enum, default_value = "text")]
output_format: HelpFormat, 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. /// Clear any caches in the current directory and any subdirectories.
#[clap(alias = "--clean")] #[clap(alias = "--clean")]
@@ -126,8 +88,6 @@ pub enum Command {
GenerateShellCompletion { shell: clap_complete_command::Shell }, GenerateShellCompletion { shell: clap_complete_command::Shell },
/// Run the Ruff formatter on the given files or directories. /// Run the Ruff formatter on the given files or directories.
Format(FormatCommand), Format(FormatCommand),
/// Run the language server.
Server(ServerCommand),
/// Display Ruff's version /// Display Ruff's version
Version { Version {
#[arg(long, value_enum, default_value = "text")] #[arg(long, value_enum, default_value = "text")]
@@ -201,6 +161,20 @@ pub struct CheckCommand {
preview: bool, preview: bool,
#[clap(long, overrides_with("preview"), hide = true)] #[clap(long, overrides_with("preview"), hide = true)]
no_preview: bool, no_preview: bool,
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
/// or a TOML `<KEY> = <VALUE>` pair
/// (such as you might find in a `ruff.toml` configuration file)
/// overriding a specific configuration option.
/// Overrides of individual settings using this option always take precedence
/// over all configuration files, including configuration files that were also
/// specified using `--config`.
#[arg(
long,
action = clap::ArgAction::Append,
value_name = "CONFIG_OPTION",
value_parser = ConfigArgumentParser,
)]
pub config: Vec<SingleConfigArgument>,
/// Comma-separated list of rule codes to enable (or ALL, to enable all rules). /// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
#[arg( #[arg(
long, long,
@@ -332,6 +306,17 @@ pub struct CheckCommand {
/// Disable cache reads. /// Disable cache reads.
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")] #[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
pub no_cache: bool, pub no_cache: bool,
/// Ignore all configuration files.
//
// Note: We can't mark this as conflicting with `--config` here
// as `--config` can be used for specifying configuration overrides
// as well as configuration files.
// Specifying a configuration file conflicts with `--isolated`;
// specifying a configuration override does not.
// If a user specifies `ruff check --isolated --config=ruff.toml`,
// we emit an error later on, after the initial parsing by clap.
#[arg(long, help_heading = "Miscellaneous")]
pub isolated: bool,
/// Path to the cache directory. /// Path to the cache directory.
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")] #[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
pub cache_dir: Option<PathBuf>, pub cache_dir: Option<PathBuf>,
@@ -423,6 +408,20 @@ pub struct FormatCommand {
/// difference between the current file and how the formatted file would look like. /// difference between the current file and how the formatted file would look like.
#[arg(long)] #[arg(long)]
pub diff: bool, pub diff: bool,
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
/// or a TOML `<KEY> = <VALUE>` pair
/// (such as you might find in a `ruff.toml` configuration file)
/// overriding a specific configuration option.
/// Overrides of individual settings using this option always take precedence
/// over all configuration files, including configuration files that were also
/// specified using `--config`.
#[arg(
long,
action = clap::ArgAction::Append,
value_name = "CONFIG_OPTION",
value_parser = ConfigArgumentParser,
)]
pub config: Vec<SingleConfigArgument>,
/// Disable cache reads. /// Disable cache reads.
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")] #[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
@@ -463,6 +462,17 @@ pub struct FormatCommand {
/// Set the line-length. /// Set the line-length.
#[arg(long, help_heading = "Format configuration")] #[arg(long, help_heading = "Format configuration")]
pub line_length: Option<LineLength>, pub line_length: Option<LineLength>,
/// Ignore all configuration files.
//
// Note: We can't mark this as conflicting with `--config` here
// as `--config` can be used for specifying configuration overrides
// as well as configuration files.
// Specifying a configuration file conflicts with `--isolated`;
// specifying a configuration override does not.
// If a user specifies `ruff check --isolated --config=ruff.toml`,
// we emit an error later on, after the initial parsing by clap.
#[arg(long, help_heading = "Miscellaneous")]
pub isolated: bool,
/// The name of the file when passing it through stdin. /// The name of the file when passing it through stdin.
#[arg(long, help_heading = "Miscellaneous")] #[arg(long, help_heading = "Miscellaneous")]
pub stdin_filename: Option<PathBuf>, pub stdin_filename: Option<PathBuf>,
@@ -496,13 +506,6 @@ pub struct FormatCommand {
pub range: Option<FormatRange>, pub range: Option<FormatRange>,
} }
#[derive(Clone, Debug, clap::Parser)]
pub struct ServerCommand {
/// Enable preview mode; required for regular operation
#[arg(long)]
pub(crate) preview: bool,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)] #[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum HelpFormat { pub enum HelpFormat {
Text, Text,
@@ -510,7 +513,7 @@ pub enum HelpFormat {
} }
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
#[derive(Debug, Default, Clone, clap::Args)] #[derive(Debug, clap::Args)]
pub struct LogLevelArgs { pub struct LogLevelArgs {
/// Enable verbose logging. /// Enable verbose logging.
#[arg( #[arg(
@@ -558,10 +561,6 @@ impl From<&LogLevelArgs> for LogLevel {
/// Configuration-related arguments passed via the CLI. /// Configuration-related arguments passed via the CLI.
#[derive(Default)] #[derive(Default)]
pub struct ConfigArguments { pub struct ConfigArguments {
/// Whether the user specified --isolated on the command line
pub(crate) isolated: bool,
/// The logging level to be used, derived from command-line arguments passed
pub(crate) log_level: LogLevel,
/// Path to a pyproject.toml or ruff.toml configuration file (etc.). /// Path to a pyproject.toml or ruff.toml configuration file (etc.).
/// Either 0 or 1 configuration file paths may be provided on the command line. /// Either 0 or 1 configuration file paths may be provided on the command line.
config_file: Option<PathBuf>, config_file: Option<PathBuf>,
@@ -582,19 +581,21 @@ impl ConfigArguments {
} }
fn from_cli_arguments( fn from_cli_arguments(
global_options: GlobalConfigArgs, config_options: Vec<SingleConfigArgument>,
per_flag_overrides: ExplicitConfigOverrides, per_flag_overrides: ExplicitConfigOverrides,
isolated: bool,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let (log_level, config_options, isolated) = global_options.partition(); let mut new = Self {
let mut config_file: Option<PathBuf> = None; per_flag_overrides,
let mut overrides = Configuration::default(); ..Self::default()
};
for option in config_options { for option in config_options {
match option { match option {
SingleConfigArgument::SettingsOverride(overridden_option) => { SingleConfigArgument::SettingsOverride(overridden_option) => {
let overridden_option = Arc::try_unwrap(overridden_option) let overridden_option = Arc::try_unwrap(overridden_option)
.unwrap_or_else(|option| option.deref().clone()); .unwrap_or_else(|option| option.deref().clone());
overrides = overrides.combine(Configuration::from_options( new.overrides = new.overrides.combine(Configuration::from_options(
overridden_option, overridden_option,
None, None,
&path_dedot::CWD, &path_dedot::CWD,
@@ -613,7 +614,7 @@ The argument `--config={}` cannot be used with `--isolated`
path.display() path.display()
); );
} }
if let Some(ref config_file) = config_file { if let Some(ref config_file) = new.config_file {
let (first, second) = (config_file.display(), path.display()); let (first, second) = (config_file.display(), path.display());
bail!( bail!(
"\ "\
@@ -624,17 +625,11 @@ You cannot specify more than one configuration file on the command line.
" "
); );
} }
config_file = Some(path); new.config_file = Some(path);
} }
} }
} }
Ok(Self { Ok(new)
isolated,
log_level,
config_file,
overrides,
per_flag_overrides,
})
} }
} }
@@ -648,10 +643,7 @@ impl ConfigurationTransformer for ConfigArguments {
impl CheckCommand { impl CheckCommand {
/// Partition the CLI into command-line arguments and configuration /// Partition the CLI into command-line arguments and configuration
/// overrides. /// overrides.
pub fn partition( pub fn partition(self) -> anyhow::Result<(CheckArguments, ConfigArguments)> {
self,
global_options: GlobalConfigArgs,
) -> anyhow::Result<(CheckArguments, ConfigArguments)> {
let check_arguments = CheckArguments { let check_arguments = CheckArguments {
add_noqa: self.add_noqa, add_noqa: self.add_noqa,
diff: self.diff, diff: self.diff,
@@ -660,6 +652,7 @@ impl CheckCommand {
exit_zero: self.exit_zero, exit_zero: self.exit_zero,
files: self.files, files: self.files,
ignore_noqa: self.ignore_noqa, ignore_noqa: self.ignore_noqa,
isolated: self.isolated,
no_cache: self.no_cache, no_cache: self.no_cache,
output_file: self.output_file, output_file: self.output_file,
show_files: self.show_files, show_files: self.show_files,
@@ -703,7 +696,8 @@ impl CheckCommand {
extension: self.extension, extension: self.extension,
}; };
let config_args = ConfigArguments::from_cli_arguments(global_options, cli_overrides)?; let config_args =
ConfigArguments::from_cli_arguments(self.config, cli_overrides, self.isolated)?;
Ok((check_arguments, config_args)) Ok((check_arguments, config_args))
} }
} }
@@ -711,14 +705,12 @@ impl CheckCommand {
impl FormatCommand { impl FormatCommand {
/// Partition the CLI into command-line arguments and configuration /// Partition the CLI into command-line arguments and configuration
/// overrides. /// overrides.
pub fn partition( pub fn partition(self) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
self,
global_options: GlobalConfigArgs,
) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
let format_arguments = FormatArguments { let format_arguments = FormatArguments {
check: self.check, check: self.check,
diff: self.diff, diff: self.diff,
files: self.files, files: self.files,
isolated: self.isolated,
no_cache: self.no_cache, no_cache: self.no_cache,
stdin_filename: self.stdin_filename, stdin_filename: self.stdin_filename,
range: self.range, range: self.range,
@@ -738,7 +730,8 @@ impl FormatCommand {
..ExplicitConfigOverrides::default() ..ExplicitConfigOverrides::default()
}; };
let config_args = ConfigArguments::from_cli_arguments(global_options, cli_overrides)?; let config_args =
ConfigArguments::from_cli_arguments(self.config, cli_overrides, self.isolated)?;
Ok((format_arguments, config_args)) Ok((format_arguments, config_args))
} }
} }
@@ -752,34 +745,38 @@ 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)] #[derive(Debug)]
enum InvalidConfigFlagReason { enum TomlParseFailureKind {
InvalidToml(toml::de::Error), SyntaxError,
/// It was valid TOML, but not a valid ruff config file. UnknownOption,
/// 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 InvalidConfigFlagReason { impl std::fmt::Display for TomlParseFailureKind {
const fn description(&self) -> &'static str { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { let display = match self {
Self::InvalidToml(_) => "The supplied argument is not valid TOML", Self::SyntaxError => "The supplied argument is not valid TOML",
Self::ValidTomlButInvalidRuffSchema(_) => { Self::UnknownOption => {
"Could not parse the supplied argument as a `ruff.toml` configuration option" "Could not parse the supplied argument as a `ruff.toml` configuration option"
} }
Self::ExtendPassedViaConfigFlag => "Cannot include `extend` in a --config flag value", };
} 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())
} }
} }
@@ -821,38 +818,27 @@ impl TypedValueParser for ConfigArgumentParser {
arg: Option<&clap::Arg>, arg: Option<&clap::Arg>,
value: &std::ffi::OsStr, value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> { ) -> Result<Self::Value, clap::Error> {
// Convert to UTF-8. let path_to_config_file = PathBuf::from(value);
let Some(value) = value.to_str() else { if path_to_config_file.exists() {
// But respect non-UTF-8 paths. return Ok(SingleConfigArgument::FilePath(path_to_config_file));
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 config_parse_error = match toml::Table::from_str(value) { let value = value
Ok(table) => match table.try_into::<Options>() { .to_str()
Ok(option) => { .ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
if option.extend.is_none() {
return Ok(SingleConfigArgument::SettingsOverride(Arc::new(option))); let toml_parse_error = match toml::Table::from_str(value) {
} Ok(table) => match table.try_into() {
InvalidConfigFlagReason::ExtendPassedViaConfigFlag Ok(option) => return Ok(SingleConfigArgument::SettingsOverride(Arc::new(option))),
} Err(underlying_error) => TomlParseFailure {
Err(underlying_error) => { kind: TomlParseFailureKind::UnknownOption,
InvalidConfigFlagReason::ValidTomlButInvalidRuffSchema(underlying_error) underlying_error,
} },
},
Err(underlying_error) => TomlParseFailure {
kind: TomlParseFailureKind::SyntaxError,
underlying_error,
}, },
Err(underlying_error) => InvalidConfigFlagReason::InvalidToml(underlying_error),
}; };
let mut new_error = clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd); let mut new_error = clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
@@ -867,21 +853,6 @@ impl TypedValueParser for ConfigArgumentParser {
clap::error::ContextValue::String(value.to_string()), 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 // small hack so that multiline tips
// have the same indent on the left-hand side: // have the same indent on the left-hand side:
let tip_indent = " ".repeat(" tip: ".len()); let tip_indent = " ".repeat(" tip: ".len());
@@ -906,20 +877,16 @@ A `--config` flag must either be a path to a `.toml` configuration file
" "
It looks like you were trying to pass a path to a configuration file. It looks like you were trying to pass a path to a configuration file.
The path `{value}` does not point to a configuration file" The path `{value}` does not exist"
)); ));
} }
} else if value.contains('=') { } else if value.contains('=') {
tip.push_str(&format!( tip.push_str(&format!("\n\n{toml_parse_error}"));
"\n\n{}:\n\n{underlying_error}",
config_parse_error.description()
));
} }
let tip = tip.trim_end().to_owned().into();
new_error.insert( new_error.insert(
clap::error::ContextKind::Suggested, clap::error::ContextKind::Suggested,
clap::error::ContextValue::StyledStrs(vec![tip]), clap::error::ContextValue::StyledStrs(vec![tip.into()]),
); );
Err(new_error) Err(new_error)
@@ -974,6 +941,7 @@ pub struct CheckArguments {
pub exit_zero: bool, pub exit_zero: bool,
pub files: Vec<PathBuf>, pub files: Vec<PathBuf>,
pub ignore_noqa: bool, pub ignore_noqa: bool,
pub isolated: bool,
pub no_cache: bool, pub no_cache: bool,
pub output_file: Option<PathBuf>, pub output_file: Option<PathBuf>,
pub show_files: bool, pub show_files: bool,
@@ -991,6 +959,7 @@ pub struct FormatArguments {
pub no_cache: bool, pub no_cache: bool,
pub diff: bool, pub diff: bool,
pub files: Vec<PathBuf>, pub files: Vec<PathBuf>,
pub isolated: bool,
pub stdin_filename: Option<PathBuf>, pub stdin_filename: Option<PathBuf>,
pub range: Option<FormatRange>, pub range: Option<FormatRange>,
} }

View File

@@ -383,7 +383,7 @@ pub(crate) fn init(path: &Path) -> Result<()> {
let gitignore_path = path.join(".gitignore"); let gitignore_path = path.join(".gitignore");
if !gitignore_path.exists() { if !gitignore_path.exists() {
let mut file = fs::File::create(gitignore_path)?; let mut file = fs::File::create(gitignore_path)?;
file.write_all(b"# Automatically created by ruff.\n*\n")?; file.write_all(b"*")?;
} }
Ok(()) Ok(())

View File

@@ -61,8 +61,13 @@ impl FormatMode {
pub(crate) fn format( pub(crate) fn format(
cli: FormatArguments, cli: FormatArguments,
config_arguments: &ConfigArguments, config_arguments: &ConfigArguments,
log_level: LogLevel,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
let pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?; let pyproject_config = resolve(
cli.isolated,
config_arguments,
cli.stdin_filename.as_deref(),
)?;
let mode = FormatMode::from_cli(&cli); let mode = FormatMode::from_cli(&cli);
let files = resolve_default_files(cli.files, false); let files = resolve_default_files(cli.files, false);
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?; let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?;
@@ -197,7 +202,7 @@ pub(crate) fn format(
} }
// Report on the formatting changes. // Report on the formatting changes.
if config_arguments.log_level >= LogLevel::Default { if log_level >= LogLevel::Default {
if mode.is_diff() { if mode.is_diff() {
// Allow piping the diff to e.g. a file by writing the summary to stderr // Allow piping the diff to e.g. a file by writing the summary to stderr
results.write_summary(&mut stderr().lock())?; results.write_summary(&mut stderr().lock())?;
@@ -527,7 +532,7 @@ impl<'a> FormatResults<'a> {
}) })
.sorted_unstable_by_key(|(path, _, _)| *path) .sorted_unstable_by_key(|(path, _, _)| *path)
{ {
write!(f, "{}", unformatted.diff(formatted, Some(path)).unwrap())?; unformatted.diff(formatted, Some(path), f)?;
} }
Ok(()) Ok(())

View File

@@ -23,7 +23,11 @@ pub(crate) fn format_stdin(
cli: &FormatArguments, cli: &FormatArguments,
config_arguments: &ConfigArguments, config_arguments: &ConfigArguments,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
let pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?; let pyproject_config = resolve(
cli.isolated,
config_arguments,
cli.stdin_filename.as_deref(),
)?;
let mut resolver = Resolver::new(&pyproject_config); let mut resolver = Resolver::new(&pyproject_config);
warn_incompatible_formatter_settings(&resolver); warn_incompatible_formatter_settings(&resolver);
@@ -118,13 +122,9 @@ fn format_source_code(
} }
FormatMode::Check => {} FormatMode::Check => {}
FormatMode::Diff => { FormatMode::Diff => {
use std::io::Write; source_kind
write!( .diff(formatted, path, &mut stdout().lock())
&mut stdout().lock(), .map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
"{}",
source_kind.diff(formatted, path).unwrap()
)
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
} }
}, },
FormattedSource::Unchanged => { FormattedSource::Unchanged => {

View File

@@ -7,7 +7,6 @@ pub(crate) mod format;
pub(crate) mod format_stdin; pub(crate) mod format_stdin;
pub(crate) mod linter; pub(crate) mod linter;
pub(crate) mod rule; pub(crate) mod rule;
pub(crate) mod server;
pub(crate) mod show_files; pub(crate) mod show_files;
pub(crate) mod show_settings; pub(crate) mod show_settings;
pub(crate) mod version; pub(crate) mod version;

View File

@@ -1,73 +0,0 @@
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))
}
}

View File

@@ -3,7 +3,6 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::Write;
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
use std::path::Path; use std::path::Path;
@@ -290,10 +289,10 @@ pub(crate) fn lint_path(
match fix_mode { match fix_mode {
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?, flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
flags::FixMode::Diff => { flags::FixMode::Diff => {
write!( source_kind.diff(
transformed.as_ref(),
Some(path),
&mut io::stdout().lock(), &mut io::stdout().lock(),
"{}",
source_kind.diff(&transformed, Some(path)).unwrap()
)?; )?;
} }
flags::FixMode::Generate => {} flags::FixMode::Generate => {}
@@ -443,11 +442,7 @@ pub(crate) fn lint_stdin(
flags::FixMode::Diff => { flags::FixMode::Diff => {
// But only write a diff if it's non-empty. // But only write a diff if it's non-empty.
if !fixed.is_empty() { if !fixed.is_empty() {
write!( source_kind.diff(transformed.as_ref(), path, &mut io::stdout().lock())?;
&mut io::stdout().lock(),
"{}",
source_kind.diff(&transformed, path).unwrap()
)?;
} }
} }
flags::FixMode::Generate => {} flags::FixMode::Generate => {}

View File

@@ -7,7 +7,6 @@ use std::process::ExitCode;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
use anyhow::Result; use anyhow::Result;
use args::{GlobalConfigArgs, ServerCommand};
use clap::CommandFactory; use clap::CommandFactory;
use colored::Colorize; use colored::Colorize;
use log::warn; use log::warn;
@@ -19,7 +18,7 @@ use ruff_linter::settings::types::SerializationFormat;
use ruff_linter::{fs, warn_user, warn_user_once}; use ruff_linter::{fs, warn_user, warn_user_once};
use ruff_workspace::Settings; use ruff_workspace::Settings;
use crate::args::{Args, CheckCommand, Command, FormatCommand}; use crate::args::{Args, CheckCommand, Command, FormatCommand, HelpFormat};
use crate::printer::{Flags as PrinterFlags, Printer}; use crate::printer::{Flags as PrinterFlags, Printer};
pub mod args; pub mod args;
@@ -115,12 +114,20 @@ 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( pub fn run(
Args { Args {
command, command,
global_options, log_level_args,
}: Args, }: Args,
deprecated_alias_warning: Option<&'static str>,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
{ {
let default_panic_hook = std::panic::take_hook(); let default_panic_hook = std::panic::take_hook();
@@ -148,11 +155,8 @@ pub fn run(
#[cfg(windows)] #[cfg(windows)]
assert!(colored::control::set_virtual_terminal(true).is_ok()); assert!(colored::control::set_virtual_terminal(true).is_ok());
set_up_logging(global_options.log_level())?; let log_level = LogLevel::from(&log_level_args);
set_up_logging(&log_level)?;
if let Some(deprecated_alias_warning) = deprecated_alias_warning {
warn_user!("{}", deprecated_alias_warning);
}
match command { match command {
Command::Version { output_format } => { Command::Version { output_format } => {
@@ -162,8 +166,10 @@ pub fn run(
Command::Rule { Command::Rule {
rule, rule,
all, all,
output_format, format,
mut output_format,
} => { } => {
output_format = resolve_help_output_format(output_format, format);
if all { if all {
commands::rule::rules(output_format)?; commands::rule::rules(output_format)?;
} }
@@ -176,46 +182,47 @@ pub fn run(
commands::config::config(option.as_deref())?; commands::config::config(option.as_deref())?;
Ok(ExitStatus::Success) Ok(ExitStatus::Success)
} }
Command::Linter { output_format } => { Command::Linter {
format,
mut output_format,
} => {
output_format = resolve_help_output_format(output_format, format);
commands::linter::linter(output_format)?; commands::linter::linter(output_format)?;
Ok(ExitStatus::Success) Ok(ExitStatus::Success)
} }
Command::Clean => { Command::Clean => {
commands::clean::clean(global_options.log_level())?; commands::clean::clean(log_level)?;
Ok(ExitStatus::Success) Ok(ExitStatus::Success)
} }
Command::GenerateShellCompletion { shell } => { Command::GenerateShellCompletion { shell } => {
shell.generate(&mut Args::command(), &mut stdout()); shell.generate(&mut Args::command(), &mut stdout());
Ok(ExitStatus::Success) Ok(ExitStatus::Success)
} }
Command::Check(args) => check(args, global_options), Command::Check(args) => check(args, log_level),
Command::Format(args) => format(args, global_options), Command::Format(args) => format(args, log_level),
Command::Server(args) => server(args, global_options.log_level()),
} }
} }
fn format(args: FormatCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> { fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
let (cli, config_arguments) = args.partition(global_options)?; let (cli, config_arguments) = args.partition()?;
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) { if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
commands::format_stdin::format_stdin(&cli, &config_arguments) commands::format_stdin::format_stdin(&cli, &config_arguments)
} else { } else {
commands::format::format(cli, &config_arguments) commands::format::format(cli, &config_arguments, log_level)
} }
} }
#[allow(clippy::needless_pass_by_value)] // TODO: remove once we start taking arguments from here pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
fn server(args: ServerCommand, log_level: LogLevel) -> Result<ExitStatus> { let (cli, config_arguments) = args.partition()?;
let ServerCommand { preview } = args;
commands::server::run_server(preview, log_level)
}
pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
let (cli, config_arguments) = args.partition(global_options)?;
// Construct the "default" settings. These are used when no `pyproject.toml` // Construct the "default" settings. These are used when no `pyproject.toml`
// files are present, or files are injected from outside of the hierarchy. // files are present, or files are injected from outside of the hierarchy.
let pyproject_config = resolve::resolve(&config_arguments, cli.stdin_filename.as_deref())?; let pyproject_config = resolve::resolve(
cli.isolated,
&config_arguments,
cli.stdin_filename.as_deref(),
)?;
let mut writer: Box<dyn Write> = match cli.output_file { let mut writer: Box<dyn Write> = match cli.output_file {
Some(path) if !cli.watch => { Some(path) if !cli.watch => {
@@ -306,7 +313,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
} }
let modifications = let modifications =
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?; commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
if modifications > 0 && config_arguments.log_level >= LogLevel::Default { if modifications > 0 && log_level >= LogLevel::Default {
let s = if modifications == 1 { "" } else { "s" }; let s = if modifications == 1 { "" } else { "s" };
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
{ {
@@ -318,7 +325,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
let printer = Printer::new( let printer = Printer::new(
output_format, output_format,
config_arguments.log_level, log_level,
fix_mode, fix_mode,
unsafe_fixes, unsafe_fixes,
printer_flags, printer_flags,
@@ -375,8 +382,11 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
}; };
if matches!(change_kind, ChangeKind::Configuration) { if matches!(change_kind, ChangeKind::Configuration) {
pyproject_config = pyproject_config = resolve::resolve(
resolve::resolve(&config_arguments, cli.stdin_filename.as_deref())?; cli.isolated,
&config_arguments,
cli.stdin_filename.as_deref(),
)?;
} }
Printer::clear_screen()?; Printer::clear_screen()?;
printer.write_to_user("File change detected...\n"); printer.write_to_user("File change detected...\n");

View File

@@ -27,42 +27,26 @@ pub fn main() -> ExitCode {
let mut args = let mut args =
argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX).unwrap(); argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX).unwrap();
// We can't use `warn_user` here because logging isn't set up at this point // Clap doesn't support default subcommands but we want to run `check` by
// and we also don't know if the user runs ruff with quiet. // default for convenience and backwards-compatibility, so we just
// Keep the message and pass it to `run` that is responsible for emitting the warning. // preprocess the arguments accordingly before passing them to Clap.
let deprecated_alias_warning = match args.get(1).and_then(|arg| arg.to_str()) { if let Some(arg) = args.get(1) {
// Deprecated aliases that are handled by clap if arg
Some("--explain") => { .to_str()
Some("`ruff --explain <RULE>` is deprecated. Use `ruff rule <RULE>` instead.") .is_some_and(|arg| !Command::has_subcommand(rewrite_legacy_subcommand(arg)))
}
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 != "-h"
&& arg != "--help" && arg != "--help"
&& arg != "-V" && arg != "-V"
&& arg != "--version" && arg != "--version"
&& arg != "help" => { && arg != "help"
{
{ args.insert(1, "check".into());
args.insert(1, "check".into()); }
Some("`ruff <path>` is deprecated. Use `ruff check <path>` instead.") }
}
},
_ => None
};
let args = Args::parse_from(args); let args = Args::parse_from(args);
match run(args, deprecated_alias_warning) { match run(args) {
Ok(code) => code.into(), Ok(code) => code.into(),
Err(err) => { Err(err) => {
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
@@ -81,3 +65,12 @@ 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,
}
}

View File

@@ -118,8 +118,6 @@ impl Printer {
} else if remaining > 0 { } else if remaining > 0 {
let s = if remaining == 1 { "" } else { "s" }; let s = if remaining == 1 { "" } else { "s" };
writeln!(writer, "Found {remaining} error{s}.")?; writeln!(writer, "Found {remaining} error{s}.")?;
} else if remaining == 0 {
writeln!(writer, "All checks passed!")?;
} }
if let Some(fixables) = fixables { if let Some(fixables) = fixables {

View File

@@ -1,4 +1,4 @@
use std::path::Path; use std::path::{Path, PathBuf};
use anyhow::Result; use anyhow::Result;
use log::debug; use log::debug;
@@ -16,11 +16,12 @@ use crate::args::ConfigArguments;
/// Resolve the relevant settings strategy and defaults for the current /// Resolve the relevant settings strategy and defaults for the current
/// invocation. /// invocation.
pub fn resolve( pub fn resolve(
isolated: bool,
config_arguments: &ConfigArguments, config_arguments: &ConfigArguments,
stdin_filename: Option<&Path>, stdin_filename: Option<&Path>,
) -> Result<PyprojectConfig> { ) -> Result<PyprojectConfig> {
// First priority: if we're running in isolated mode, use the default settings. // First priority: if we're running in isolated mode, use the default settings.
if config_arguments.isolated { if isolated {
let config = config_arguments.transform(Configuration::default()); let config = config_arguments.transform(Configuration::default());
let settings = config.into_settings(&path_dedot::CWD)?; let settings = config.into_settings(&path_dedot::CWD)?;
debug!("Isolated mode, not reading any pyproject.toml"); debug!("Isolated mode, not reading any pyproject.toml");
@@ -34,8 +35,13 @@ pub fn resolve(
// Second priority: the user specified a `pyproject.toml` file. Use that // Second priority: the user specified a `pyproject.toml` file. Use that
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the // `pyproject.toml` for _all_ configuration, and resolve paths relative to the
// current working directory. (This matches ESLint's behavior.) // current working directory. (This matches ESLint's behavior.)
if let Some(pyproject) = config_arguments.config_file() { if let Some(pyproject) = config_arguments
let settings = resolve_root_settings(pyproject, Relativity::Cwd, 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)?;
debug!( debug!(
"Using user-specified configuration file at: {}", "Using user-specified configuration file at: {}",
pyproject.display() pyproject.display()
@@ -43,7 +49,7 @@ pub fn resolve(
return Ok(PyprojectConfig::new( return Ok(PyprojectConfig::new(
PyprojectDiscoveryStrategy::Fixed, PyprojectDiscoveryStrategy::Fixed,
settings, settings,
Some(pyproject.to_path_buf()), Some(pyproject),
)); ));
} }

View File

@@ -12,10 +12,9 @@ const STDIN: &str = "l = 1";
fn ruff_check(show_source: Option<bool>, output_format: Option<String>) -> Command { fn ruff_check(show_source: Option<bool>, output_format: Option<String>) -> Command {
let mut cmd = Command::new(get_cargo_bin(BIN_NAME)); let mut cmd = Command::new(get_cargo_bin(BIN_NAME));
let output_format = output_format.unwrap_or(format!("{}", SerializationFormat::default(false))); let output_format = output_format.unwrap_or(format!("{}", SerializationFormat::default(false)));
cmd.arg("check") cmd.arg("--output-format");
.arg("--output-format") cmd.arg(output_format);
.arg(output_format) cmd.arg("--no-cache");
.arg("--no-cache");
match show_source { match show_source {
Some(true) => { Some(true) => {
cmd.arg("--show-source"); cmd.arg("--show-source");

View File

@@ -7,15 +7,10 @@ use std::str;
use anyhow::Result; use anyhow::Result;
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
use regex::escape;
use tempfile::TempDir; use tempfile::TempDir;
const BIN_NAME: &str = "ruff"; const BIN_NAME: &str = "ruff";
fn tempdir_filter(tempdir: &TempDir) -> String {
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
}
#[test] #[test]
fn default_options() { fn default_options() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
@@ -23,7 +18,7 @@ fn default_options() {
.arg("-") .arg("-")
.pass_stdin(r#" .pass_stdin(r#"
def foo(arg1, arg2,): def foo(arg1, arg2,):
print('Shouldn\'t change quotes') print('Should\'t change quotes')
if condition: if condition:
@@ -38,7 +33,7 @@ if condition:
arg1, arg1,
arg2, arg2,
): ):
print("Shouldn't change quotes") print("Should't change quotes")
if condition: if condition:
@@ -111,7 +106,7 @@ fn nonexistent_config_file() {
option option
It looks like you were trying to pass a path to a configuration file. It looks like you were trying to pass a path to a configuration file.
The path `foo.toml` does not point to a configuration file The path `foo.toml` does not exist
For more information, try '--help'. For more information, try '--help'.
"###); "###);
@@ -152,29 +147,28 @@ fn too_many_config_files() -> Result<()> {
let ruff2_dot_toml = tempdir.path().join("ruff2.toml"); let ruff2_dot_toml = tempdir.path().join("ruff2.toml");
fs::File::create(&ruff_dot_toml)?; fs::File::create(&ruff_dot_toml)?;
fs::File::create(&ruff2_dot_toml)?; fs::File::create(&ruff2_dot_toml)?;
insta::with_settings!({ let expected_stderr = format!(
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")] "\
}, { ruff failed
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) 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))
.arg("format") .arg("format")
.arg("--config") .arg("--config")
.arg(&ruff_dot_toml) .arg(&ruff_dot_toml)
.arg("--config") .arg("--config")
.arg(&ruff2_dot_toml) .arg(&ruff2_dot_toml)
.arg("."), @r###" .arg(".")
success: false .output()?;
exit_code: 2 let stderr = std::str::from_utf8(&cmd.stderr)?;
----- stdout ----- assert_eq!(stderr, expected_stderr);
----- 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(()) Ok(())
} }
@@ -183,29 +177,27 @@ fn config_file_and_isolated() -> Result<()> {
let tempdir = TempDir::new()?; let tempdir = TempDir::new()?;
let ruff_dot_toml = tempdir.path().join("ruff.toml"); let ruff_dot_toml = tempdir.path().join("ruff.toml");
fs::File::create(&ruff_dot_toml)?; fs::File::create(&ruff_dot_toml)?;
insta::with_settings!({ let expected_stderr = format!(
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")] "\
}, { ruff failed
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) 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))
.arg("format") .arg("format")
.arg("--config") .arg("--config")
.arg(&ruff_dot_toml) .arg(&ruff_dot_toml)
.arg("--isolated") .arg("--isolated")
.arg("."), @r###" .arg(".")
success: false .output()?;
exit_code: 2 let stderr = std::str::from_utf8(&cmd.stderr)?;
----- stdout ----- assert_eq!(stderr, expected_stderr);
----- 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(()) Ok(())
} }
@@ -358,52 +350,58 @@ def f(x):
''' '''
pass pass
"#), @r###" "#), @r###"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
def f(x): def f(x):
""" """
Something about `f`. And an example: Something about `f`. And an example:
.. code-block:: python .. code-block:: python
foo, bar, quux = ( (
this_is_a_long_line( foo,
lion, bar,
hippo, quux,
lemur, ) = this_is_a_long_line(
bear, lion,
) hippo,
) lemur,
bear,
Another example:
```py
foo, bar, quux = (
this_is_a_long_line(
lion,
hippo,
lemur,
bear,
)
) )
```
And another: Another example:
>>> foo, bar, quux = ( ```py
... this_is_a_long_line( (
... lion, foo,
... hippo, bar,
... lemur, quux,
... bear, ) = this_is_a_long_line(
... ) lion,
... ) hippo,
""" lemur,
pass bear,
)
```
----- stderr ----- And another:
"###);
>>> (
... foo,
... bar,
... quux,
... ) = this_is_a_long_line(
... lion,
... hippo,
... lemur,
... bear,
... )
"""
pass
----- stderr -----
"###);
Ok(()) Ok(())
} }

View File

@@ -71,7 +71,6 @@ impl<'a> RuffCheck<'a> {
/// Generate a [`Command`] for the `ruff check` command. /// Generate a [`Command`] for the `ruff check` command.
fn build(self) -> Command { fn build(self) -> Command {
let mut cmd = ruff_cmd(); let mut cmd = ruff_cmd();
cmd.arg("check");
if let Some(output_format) = self.output_format { if let Some(output_format) = self.output_format {
cmd.args(["--output-format", output_format]); cmd.args(["--output-format", output_format]);
} }
@@ -101,7 +100,6 @@ fn stdin_success() {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
"###); "###);
@@ -223,7 +221,6 @@ fn stdin_source_type_pyi() {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
"###); "###);
@@ -592,7 +589,6 @@ fn stdin_fix_when_no_issues_should_still_print_contents() {
print(sys.version) print(sys.version)
----- stderr ----- ----- stderr -----
All checks passed!
"###); "###);
} }
@@ -809,13 +805,13 @@ fn full_output_format() {
} }
#[test] #[test]
fn rule_f401() { fn explain_status_codes_f401() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401"])); assert_cmd_snapshot!(ruff_cmd().args(["--explain", "F401"]));
} }
#[test] #[test]
fn rule_invalid_rule_name() { fn explain_status_codes_ruf404() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404"]), @r###" assert_cmd_snapshot!(ruff_cmd().args(["--explain", "RUF404"]), @r###"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -1026,7 +1022,6 @@ fn preview_disabled_direct() {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
warning: Selection `RUF911` has no effect because preview is not enabled. warning: Selection `RUF911` has no effect because preview is not enabled.
@@ -1043,7 +1038,6 @@ fn preview_disabled_prefix_empty() {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
warning: Selection `RUF91` has no effect because preview is not enabled. warning: Selection `RUF91` has no effect because preview is not enabled.
@@ -1060,7 +1054,6 @@ fn preview_disabled_does_not_warn_for_empty_ignore_selections() {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
"###); "###);
@@ -1076,7 +1069,6 @@ fn preview_disabled_does_not_warn_for_empty_fixable_selections() {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
"###); "###);
@@ -1182,7 +1174,6 @@ fn removed_indirect() {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
"###); "###);
@@ -1213,7 +1204,6 @@ fn redirect_indirect() {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
"###); "###);
@@ -1316,7 +1306,6 @@ fn deprecated_indirect_preview_enabled() {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
"###); "###);
@@ -1359,7 +1348,7 @@ fn unreadable_pyproject_toml() -> Result<()> {
// Don't `--isolated` since the configuration discovery is where the error happens // 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 args = Args::parse_from(["", "check", "--no-cache", tempdir.path().to_str().unwrap()]);
let err = run(args, None).err().context("Unexpected success")?; let err = run(args).err().context("Unexpected success")?;
assert_eq!( assert_eq!(
err.chain() err.chain()
.map(std::string::ToString::to_string) .map(std::string::ToString::to_string)
@@ -1393,7 +1382,6 @@ fn unreadable_dir() -> Result<()> {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
warning: Encountered error: Permission denied (os error 13) warning: Encountered error: Permission denied (os error 13)
@@ -1908,7 +1896,6 @@ def log(x, base) -> float:
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
"### "###

View File

@@ -12,7 +12,7 @@ use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
use tempfile::TempDir; use tempfile::TempDir;
const BIN_NAME: &str = "ruff"; const BIN_NAME: &str = "ruff";
const STDIN_BASE_OPTIONS: &[&str] = &["check", "--no-cache", "--output-format", "concise"]; const STDIN_BASE_OPTIONS: &[&str] = &["--no-cache", "--output-format", "concise"];
fn tempdir_filter(tempdir: &TempDir) -> String { fn tempdir_filter(tempdir: &TempDir) -> String {
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap())) format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
@@ -246,6 +246,7 @@ OTHER = "OTHER"
}, { }, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path()) .current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS) .args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) .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 // Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude
@@ -292,6 +293,7 @@ inline-quotes = "single"
}, { }, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path()) .current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS) .args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) .args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--stdin-filename", "generated.py"]) .args(["--stdin-filename", "generated.py"])
@@ -384,6 +386,7 @@ inline-quotes = "single"
}, { }, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path()) .current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS) .args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) .args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--stdin-filename", "generated.py"]) .args(["--stdin-filename", "generated.py"])
@@ -432,6 +435,7 @@ inline-quotes = "single"
}, { }, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path()) .current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS) .args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) .args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--stdin-filename", "generated.py"]) .args(["--stdin-filename", "generated.py"])
@@ -491,12 +495,12 @@ ignore = ["D203", "D212"]
}, { }, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(sub_dir) .current_dir(sub_dir)
.arg("check")
.args(STDIN_BASE_OPTIONS) .args(STDIN_BASE_OPTIONS)
, @r###" , @r###"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
warning: No Python files found under the given path(s) warning: No Python files found under the given path(s)
@@ -523,7 +527,7 @@ fn nonexistent_config_file() {
option option
It looks like you were trying to pass a path to a configuration file. It looks like you were trying to pass a path to a configuration file.
The path `foo.toml` does not point to a configuration file The path `foo.toml` does not exist
For more information, try '--help'. For more information, try '--help'.
"###); "###);
@@ -591,24 +595,6 @@ fn too_many_config_files() -> Result<()> {
Ok(()) 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] #[test]
fn config_file_and_isolated() -> Result<()> { fn config_file_and_isolated() -> Result<()> {
let tempdir = TempDir::new()?; let tempdir = TempDir::new()?;
@@ -834,7 +820,6 @@ fn complex_config_setting_overridden_via_cli() -> Result<()> {
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
All checks passed!
----- stderr ----- ----- stderr -----
"###); "###);
@@ -851,7 +836,7 @@ fn deprecated_config_option_overridden_via_cli() {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
-:1:7: N801 Class name `lowercase` should use CapWords convention -:1:7: N801 Class name `lowercase` should use CapWords convention
Found 1 error. Found 1 error.
----- stderr ----- ----- stderr -----
@@ -918,6 +903,7 @@ include = ["*.ipy"]
}, { }, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path()) .current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS) .args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) .args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--extension", "ipy:ipynb"]) .args(["--extension", "ipy:ipynb"])
@@ -935,236 +921,3 @@ include = ["*.ipy"]
Ok(()) Ok(())
} }
#[test]
fn file_noqa_external() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint]
external = ["AAA"]
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("-")
.pass_stdin(r#"
# flake8: noqa: AAA101, BBB102
import os
"#), @r###"
success: false
exit_code: 1
----- stdout -----
-:3:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
warning: Invalid rule code provided to `# ruff: noqa` at -:2: BBB102
"###);
});
Ok(())
}
#[test]
fn required_version_exact_mismatch() -> Result<()> {
let version = env!("CARGO_PKG_VERSION");
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
required-version = "0.1.0"
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("-")
.pass_stdin(r#"
import os
"#), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Required version `==0.1.0` does not match the running version `[VERSION]`
"###);
});
Ok(())
}
#[test]
fn required_version_exact_match() -> Result<()> {
let version = env!("CARGO_PKG_VERSION");
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
format!(
r#"
required-version = "{version}"
"#
),
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("-")
.pass_stdin(r#"
import os
"#), @r###"
success: false
exit_code: 1
----- stdout -----
-:2:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
});
Ok(())
}
#[test]
fn required_version_bound_mismatch() -> Result<()> {
let version = env!("CARGO_PKG_VERSION");
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
format!(
r#"
required-version = ">{version}"
"#
),
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("-")
.pass_stdin(r#"
import os
"#), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Required version `>[VERSION]` does not match the running version `[VERSION]`
"###);
});
Ok(())
}
#[test]
fn required_version_bound_match() -> Result<()> {
let version = env!("CARGO_PKG_VERSION");
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
required-version = ">=0.1.0"
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("-")
.pass_stdin(r#"
import os
"#), @r###"
success: false
exit_code: 1
----- stdout -----
-:2:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
});
Ok(())
}
/// Expand environment variables in `--config` paths provided via the CLI.
#[test]
fn config_expand() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
ruff_toml,
r#"
[lint]
select = ["F"]
ignore = ["F841"]
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg("${NAME}.toml")
.env("NAME", "ruff")
.arg("-")
.current_dir(tempdir.path())
.pass_stdin(r#"
import os
def func():
x = 1
"#), @r###"
success: false
exit_code: 1
----- stdout -----
-:2:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
Ok(())
}

View File

@@ -3,7 +3,7 @@ source: crates/ruff/tests/integration_test.rs
info: info:
program: ruff program: ruff
args: args:
- rule - "--explain"
- F401 - F401
--- ---
success: true success: true
@@ -34,11 +34,6 @@ marking it as unused, as in:
from module import member as member from module import member as member
``` ```
## Fix safety
When `ignore_init_module_imports` is disabled, fixes can remove for unused imports in `__init__` files.
These fixes are considered unsafe because they can change the public interface.
## Example ## Example
```python ```python
import numpy as np # unused import import numpy as np # unused import
@@ -73,3 +68,4 @@ else:
- [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols) - [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols)
----- stderr ----- ----- stderr -----

View File

@@ -44,6 +44,7 @@ file_resolver.exclude = [
"__pypackages__", "__pypackages__",
"_build", "_build",
"buck-out", "buck-out",
"build",
"dist", "dist",
"node_modules", "node_modules",
"site-packages", "site-packages",
@@ -201,7 +202,7 @@ linter.allowed_confusables = []
linter.builtins = [] linter.builtins = []
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$ linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
linter.external = [] linter.external = []
linter.ignore_init_module_imports = true linter.ignore_init_module_imports = false
linter.logger_objects = [] linter.logger_objects = []
linter.namespace_packages = [] linter.namespace_packages = []
linter.src = [ linter.src = [
@@ -231,7 +232,7 @@ linter.flake8_bandit.check_typed_exception = false
linter.flake8_bugbear.extend_immutable_calls = [] linter.flake8_bugbear.extend_immutable_calls = []
linter.flake8_builtins.builtins_ignorelist = [] linter.flake8_builtins.builtins_ignorelist = []
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false 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.author = none
linter.flake8_copyright.min_file_size = 0 linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0 linter.flake8_errmsg.max_string_length = 0
@@ -241,22 +242,7 @@ linter.flake8_gettext.functions_names = [
ngettext, ngettext,
] ]
linter.flake8_implicit_str_concat.allow_multiline = true linter.flake8_implicit_str_concat.allow_multiline = true
linter.flake8_import_conventions.aliases = { 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"}
altair = alt,
holoviews = hv,
matplotlib = mpl,
matplotlib.pyplot = plt,
networkx = nx,
numpy = np,
pandas = pd,
panel = pn,
plotly.express = px,
polars = pl,
pyarrow = pa,
seaborn = sns,
tensorflow = tf,
tkinter = tk,
}
linter.flake8_import_conventions.banned_aliases = {} linter.flake8_import_conventions.banned_aliases = {}
linter.flake8_import_conventions.banned_from = [] linter.flake8_import_conventions.banned_from = []
linter.flake8_pytest_style.fixture_parentheses = true linter.flake8_pytest_style.fixture_parentheses = true
@@ -326,7 +312,6 @@ linter.isort.section_order = [
known { type = first_party }, known { type = first_party },
known { type = local_folder }, known { type = local_folder },
] ]
linter.isort.default_section = known { type = third_party }
linter.isort.no_sections = false linter.isort.no_sections = false
linter.isort.from_first = false linter.isort.from_first = false
linter.isort.length_sort = false linter.isort.length_sort = false
@@ -381,3 +366,4 @@ formatter.docstring_code_format = disabled
formatter.docstring_code_line_width = dynamic formatter.docstring_code_line_width = dynamic
----- stderr ----- ----- stderr -----

View File

@@ -1,105 +0,0 @@
//! 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 -----
"###
);
});
}

View File

@@ -27,7 +27,7 @@ use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, GlobalConfigArgs, LogLevelArgs}; use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, LogLevelArgs};
use ruff::resolve::resolve; use ruff::resolve::resolve;
use ruff_formatter::{FormatError, LineWidth, PrintError}; use ruff_formatter::{FormatError, LineWidth, PrintError};
use ruff_linter::logging::LogLevel; use ruff_linter::logging::LogLevel;
@@ -43,7 +43,7 @@ fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, ConfigArgumen
.no_binary_name(true) .no_binary_name(true)
.get_matches_from(dirs); .get_matches_from(dirs);
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?; let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
let (cli, config_arguments) = arguments.partition(GlobalConfigArgs::default())?; let (cli, config_arguments) = arguments.partition()?;
Ok((cli, config_arguments)) Ok((cli, config_arguments))
} }
@@ -52,7 +52,11 @@ fn find_pyproject_config(
cli: &FormatArguments, cli: &FormatArguments,
config_arguments: &ConfigArguments, config_arguments: &ConfigArguments,
) -> anyhow::Result<PyprojectConfig> { ) -> anyhow::Result<PyprojectConfig> {
let mut pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?; let mut pyproject_config = resolve(
cli.isolated,
config_arguments,
cli.stdin_filename.as_deref(),
)?;
// We don't want to format pyproject.toml // We don't want to format pyproject.toml
pyproject_config.settings.file_resolver.include = FilePatternSet::try_from_iter([ pyproject_config.settings.file_resolver.include = FilePatternSet::try_from_iter([
FilePattern::Builtin("*.py"), FilePattern::Builtin("*.py"),

View File

@@ -4,8 +4,8 @@
use anyhow::Result; use anyhow::Result;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use ruff::{args::GlobalConfigArgs, check}; use ruff::check;
use ruff_linter::logging::set_up_logging; use ruff_linter::logging::{set_up_logging, LogLevel};
use std::process::ExitCode; use std::process::ExitCode;
mod format_dev; mod format_dev;
@@ -28,8 +28,6 @@ const ROOT_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../");
struct Args { struct Args {
#[command(subcommand)] #[command(subcommand)]
command: Command, command: Command,
#[clap(flatten)]
global_options: GlobalConfigArgs,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@@ -59,6 +57,8 @@ enum Command {
Repeat { Repeat {
#[clap(flatten)] #[clap(flatten)]
args: ruff::args::CheckCommand, args: ruff::args::CheckCommand,
#[clap(flatten)]
log_level_args: ruff::args::LogLevelArgs,
/// Run this many times /// Run this many times
#[clap(long)] #[clap(long)]
repeat: usize, repeat: usize,
@@ -75,12 +75,9 @@ enum Command {
} }
fn main() -> Result<ExitCode> { fn main() -> Result<ExitCode> {
let Args { let args = Args::parse();
command,
global_options,
} = Args::parse();
#[allow(clippy::print_stdout)] #[allow(clippy::print_stdout)]
match command { match args.command {
Command::GenerateAll(args) => generate_all::main(&args)?, Command::GenerateAll(args) => generate_all::main(&args)?,
Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?, Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?,
Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()), Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()),
@@ -92,12 +89,14 @@ fn main() -> Result<ExitCode> {
Command::PrintTokens(args) => print_tokens::main(&args)?, Command::PrintTokens(args) => print_tokens::main(&args)?,
Command::RoundTrip(args) => round_trip::main(&args)?, Command::RoundTrip(args) => round_trip::main(&args)?,
Command::Repeat { Command::Repeat {
args: subcommand_args, args,
repeat, repeat,
log_level_args,
} => { } => {
set_up_logging(global_options.log_level())?; let log_level = LogLevel::from(&log_level_args);
set_up_logging(&log_level)?;
for _ in 0..repeat { for _ in 0..repeat {
check(subcommand_args.clone(), global_options.clone())?; check(args.clone(), log_level)?;
} }
} }
Command::FormatDev(args) => { Command::FormatDev(args) => {

View File

@@ -37,7 +37,7 @@ pub trait Buffer {
#[doc(hidden)] #[doc(hidden)]
fn elements(&self) -> &[FormatElement]; fn elements(&self) -> &[FormatElement];
/// Glue for usage of the [`write!`] macro with implementers of this trait. /// Glue for usage of the [`write!`] macro with implementors of this trait.
/// ///
/// This method should generally not be invoked manually, but rather through the [`write!`] macro itself. /// This method should generally not be invoked manually, but rather through the [`write!`] macro itself.
/// ///

View File

@@ -1,8 +1,9 @@
use crate::format_element::PrintMode;
use crate::{GroupId, TextSize};
use std::cell::Cell; use std::cell::Cell;
use std::num::NonZeroU8; use std::num::NonZeroU8;
use crate::format_element::PrintMode;
use crate::{GroupId, TextSize};
/// A Tag marking the start and end of some content to which some special formatting should be applied. /// A Tag marking the start and end of some content to which some special formatting should be applied.
/// ///
/// Tags always come in pairs of a start and an end tag and the styling defined by this tag /// Tags always come in pairs of a start and an end tag and the styling defined by this tag
@@ -99,6 +100,10 @@ pub enum Tag {
} }
impl Tag { impl Tag {
pub const fn align(count: NonZeroU8) -> Tag {
Tag::StartAlign(Align(count))
}
/// Returns `true` if `self` is any start tag. /// Returns `true` if `self` is any start tag.
pub const fn is_start(&self) -> bool { pub const fn is_start(&self) -> bool {
matches!( matches!(

View File

@@ -545,10 +545,6 @@ impl PrintedRange {
&self.code &self.code
} }
pub fn into_code(self) -> String {
self.code
}
/// The range the formatted code corresponds to in the source document. /// The range the formatted code corresponds to in the source document.
pub fn source_range(&self) -> TextRange { pub fn source_range(&self) -> TextRange {
self.source_range self.source_range

View File

@@ -78,28 +78,27 @@ impl<'a> PrintQueue<'a> {
impl<'a> Queue<'a> for PrintQueue<'a> { impl<'a> Queue<'a> for PrintQueue<'a> {
fn pop(&mut self) -> Option<&'a FormatElement> { fn pop(&mut self) -> Option<&'a FormatElement> {
let elements = self.element_slices.last_mut()?; let elements = self.element_slices.last_mut()?;
elements.next().or_else( elements.next().or_else(|| {
#[cold] self.element_slices.pop();
|| { let elements = self.element_slices.last_mut()?;
self.element_slices.pop(); elements.next()
let elements = self.element_slices.last_mut()?; })
elements.next()
},
)
} }
fn top_with_interned(&self) -> Option<&'a FormatElement> { fn top_with_interned(&self) -> Option<&'a FormatElement> {
let mut slices = self.element_slices.iter().rev(); let mut slices = self.element_slices.iter().rev();
let slice = slices.next()?; let slice = slices.next()?;
slice.as_slice().first().or_else( match slice.as_slice().first() {
#[cold] Some(element) => Some(element),
|| { None => {
slices if let Some(next_elements) = slices.next() {
.next() next_elements.as_slice().first()
.and_then(|next_elements| next_elements.as_slice().first()) } else {
}, None
) }
}
}
} }
fn extend_back(&mut self, elements: &'a [FormatElement]) { fn extend_back(&mut self, elements: &'a [FormatElement]) {
@@ -147,30 +146,24 @@ impl<'a, 'print> FitsQueue<'a, 'print> {
impl<'a, 'print> Queue<'a> for FitsQueue<'a, 'print> { impl<'a, 'print> Queue<'a> for FitsQueue<'a, 'print> {
fn pop(&mut self) -> Option<&'a FormatElement> { fn pop(&mut self) -> Option<&'a FormatElement> {
self.queue.pop().or_else( self.queue.pop().or_else(|| {
#[cold] if let Some(next_slice) = self.rest_elements.next_back() {
|| { self.queue.extend_back(next_slice.as_slice());
if let Some(next_slice) = self.rest_elements.next_back() { self.queue.pop()
self.queue.extend_back(next_slice.as_slice()); } else {
self.queue.pop() None
} else { }
None })
}
},
)
} }
fn top_with_interned(&self) -> Option<&'a FormatElement> { fn top_with_interned(&self) -> Option<&'a FormatElement> {
self.queue.top_with_interned().or_else( self.queue.top_with_interned().or_else(|| {
#[cold] if let Some(next_elements) = self.rest_elements.as_slice().last() {
|| { next_elements.as_slice().first()
if let Some(next_elements) = self.rest_elements.as_slice().last() { } else {
next_elements.as_slice().first() None
} else { }
None })
}
},
)
} }
fn extend_back(&mut self, elements: &'a [FormatElement]) { fn extend_back(&mut self, elements: &'a [FormatElement]) {

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ruff_linter" name = "ruff_linter"
version = "0.3.2" version = "0.2.1"
publish = false publish = false
authors = { workspace = true } authors = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
@@ -60,6 +60,7 @@ regex = { workspace = true }
result-like = { workspace = true } result-like = { workspace = true }
rustc-hash = { workspace = true } rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true } schemars = { workspace = true, optional = true }
semver = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
similar = { workspace = true } similar = { workspace = true }

View File

@@ -28,11 +28,3 @@ dictionary = {
} }
#import os # noqa #import os # noqa
# case 1:
# try:
# try: # with comment
# try: print()
# except:
# except Foo:
# except Exception as e: print(e)

View File

@@ -18,7 +18,3 @@ func("0.0.0.0")
def my_func(): def my_func():
x = "0.0.0.0" x = "0.0.0.0"
print(x) print(x)
# Implicit string concatenation
"0.0.0.0" f"0.0.0.0{expr}0.0.0.0"

View File

@@ -18,13 +18,6 @@ with open("/dev/shm/unit/test", "w") as f:
with open("/foo/bar", "w") as f: with open("/foo/bar", "w") as f:
f.write("def") f.write("def")
# Implicit string concatenation
with open("/tmp/" "abc", "w") as f:
f.write("def")
with open("/tmp/abc" f"/tmp/abc", "w") as f:
f.write("def")
# Using `tempfile` module should be ok # Using `tempfile` module should be ok
import tempfile import tempfile
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory

View File

@@ -1,22 +0,0 @@
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()

View File

@@ -1,47 +1,52 @@
import crypt
import hashlib import hashlib
from hashlib import new as hashlib_new from hashlib import new as hashlib_new
from hashlib import sha1 as hashlib_sha1 from hashlib import sha1 as hashlib_sha1
# Errors # Invalid
hashlib.new('md5') hashlib.new('md5')
hashlib.new('md4', b'test') hashlib.new('md4', b'test')
hashlib.new(name='md5', data=b'test') hashlib.new(name='md5', data=b'test')
hashlib.new('MD4', data=b'test') hashlib.new('MD4', data=b'test')
hashlib.new('sha1') hashlib.new('sha1')
hashlib.new('sha1', data=b'test') hashlib.new('sha1', data=b'test')
hashlib.new('sha', data=b'test') hashlib.new('sha', data=b'test')
hashlib.new(name='SHA', data=b'test') hashlib.new(name='SHA', data=b'test')
hashlib.sha(data=b'test') hashlib.sha(data=b'test')
hashlib.md5() hashlib.md5()
hashlib_new('sha1') hashlib_new('sha1')
hashlib_sha1('sha1') hashlib_sha1('sha1')
# usedforsecurity arg only available in Python 3.9+ # usedforsecurity arg only available in Python 3.9+
hashlib.new('sha1', usedforsecurity=True) hashlib.new('sha1', usedforsecurity=True)
crypt.crypt("test", salt=crypt.METHOD_CRYPT) # Valid
crypt.crypt("test", salt=crypt.METHOD_MD5)
crypt.crypt("test", salt=crypt.METHOD_BLOWFISH)
crypt.crypt("test", crypt.METHOD_BLOWFISH)
crypt.mksalt(crypt.METHOD_CRYPT)
crypt.mksalt(crypt.METHOD_MD5)
crypt.mksalt(crypt.METHOD_BLOWFISH)
# OK
hashlib.new('sha256') hashlib.new('sha256')
hashlib.new('SHA512') hashlib.new('SHA512')
hashlib.sha256(data=b'test') hashlib.sha256(data=b'test')
# usedforsecurity arg only available in Python 3.9+ # usedforsecurity arg only available in Python 3.9+
hashlib_new(name='sha1', usedforsecurity=False) hashlib_new(name='sha1', usedforsecurity=False)
# usedforsecurity arg only available in Python 3.9+
hashlib_sha1(name='sha1', usedforsecurity=False) hashlib_sha1(name='sha1', usedforsecurity=False)
# usedforsecurity arg only available in Python 3.9+
hashlib.md4(usedforsecurity=False) hashlib.md4(usedforsecurity=False)
# usedforsecurity arg only available in Python 3.9+
hashlib.new(name='sha256', usedforsecurity=False) hashlib.new(name='sha256', usedforsecurity=False)
crypt.crypt("test")
crypt.crypt("test", salt=crypt.METHOD_SHA256)
crypt.crypt("test", salt=crypt.METHOD_SHA512)
crypt.mksalt()
crypt.mksalt(crypt.METHOD_SHA256)
crypt.mksalt(crypt.METHOD_SHA512)

View File

@@ -1,5 +1,4 @@
import os import os
import subprocess
import commands import commands
import popen2 import popen2
@@ -17,8 +16,6 @@ popen2.Popen3("true")
popen2.Popen4("true") popen2.Popen4("true")
commands.getoutput("true") commands.getoutput("true")
commands.getstatusoutput("true") commands.getstatusoutput("true")
subprocess.getoutput("true")
subprocess.getstatusoutput("true")
# Check command argument looks unsafe. # Check command argument looks unsafe.

View File

@@ -1,34 +0,0 @@
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'])

View File

@@ -119,16 +119,3 @@ def func(x: bool):
settings(True) 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)

View File

@@ -14,6 +14,9 @@ reversed(sorted(x, reverse=not x))
reversed(sorted(i for i in range(42))) reversed(sorted(i for i in range(42)))
reversed(sorted((i for i in range(42)), reverse=True)) reversed(sorted((i for i in range(42)), reverse=True))
# Regression test for: https://github.com/astral-sh/ruff/issues/10335
reversed(sorted([1, 2, 3], reverse=False or True)) def reversed(*args, **kwargs):
reversed(sorted([1, 2, 3], reverse=(False or True))) return None
reversed(sorted(x, reverse=True))

View File

@@ -7,19 +7,7 @@ from pdb import set_trace as st
from celery.contrib.rdb import set_trace from celery.contrib.rdb import set_trace
from celery.contrib import rdb from celery.contrib import rdb
import celery.contrib.rdb import celery.contrib.rdb
from debugpy import wait_for_client
import debugpy
from ptvsd import break_into_debugger
from ptvsd import enable_attach
from ptvsd import wait_for_attach
import ptvsd
breakpoint() breakpoint()
st() st()
set_trace() set_trace()
debugpy.breakpoint()
wait_for_client()
debugpy.listen(1234)
enable_attach()
break_into_debugger()
wait_for_attach()

View File

@@ -64,5 +64,3 @@ def not_warnings_dot_deprecated(
"Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053 "Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
) )
def not_a_deprecated_function() -> None: ... def not_a_deprecated_function() -> None: ...
fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053

View File

@@ -40,7 +40,4 @@ f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
# Make sure we do not unescape quotes # Make sure we do not unescape quotes
this_is_fine = "This is an \\'escaped\\' quote" this_is_fine = "This is an \\'escaped\\' quote"
this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash" # Q004 this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash"
# 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

View File

@@ -93,15 +93,3 @@ def func():
# OK # OK
raise func() raise func()
# OK
future = executor.submit(float, "a")
if future.exception():
raise future.exception()
# RSE102
future = executor.submit(float, "a")
if future.exception():
raise future.Exception()

View File

@@ -193,11 +193,3 @@ def func():
for y in range(5): for y in range(5):
g(x, idx) g(x, idx)
idx += 1 idx += 1
async def func():
# OK (for loop is async)
idx = 0
async for x in async_gen():
g(x, idx)
idx += 1

View File

@@ -10,7 +10,7 @@ async def func():
trio.sleep(0) # TRIO115 trio.sleep(0) # TRIO115
foo = 0 foo = 0
trio.sleep(foo) # OK trio.sleep(foo) # TRIO115
trio.sleep(1) # OK trio.sleep(1) # OK
time.sleep(0) # OK time.sleep(0) # OK
@@ -20,26 +20,26 @@ async def func():
trio.sleep(bar) trio.sleep(bar)
x, y = 0, 2000 x, y = 0, 2000
trio.sleep(x) # OK trio.sleep(x) # TRIO115
trio.sleep(y) # OK trio.sleep(y) # OK
(a, b, [c, (d, e)]) = (1, 2, (0, [4, 0])) (a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
trio.sleep(c) # OK trio.sleep(c) # TRIO115
trio.sleep(d) # OK trio.sleep(d) # OK
trio.sleep(e) # OK trio.sleep(e) # TRIO115
m_x, m_y = 0 m_x, m_y = 0
trio.sleep(m_y) # OK trio.sleep(m_y) # OK
trio.sleep(m_x) # OK trio.sleep(m_x) # OK
m_a = m_b = 0 m_a = m_b = 0
trio.sleep(m_a) # OK trio.sleep(m_a) # TRIO115
trio.sleep(m_b) # OK trio.sleep(m_b) # TRIO115
m_c = (m_d, m_e) = (0, 0) m_c = (m_d, m_e) = (0, 0)
trio.sleep(m_c) # OK trio.sleep(m_c) # OK
trio.sleep(m_d) # OK trio.sleep(m_d) # TRIO115
trio.sleep(m_e) # OK trio.sleep(m_e) # TRIO115
def func(): def func():
@@ -63,16 +63,4 @@ def func():
import trio import trio
if (walrus := 0) == 0: if (walrus := 0) == 0:
trio.sleep(walrus) # OK trio.sleep(walrus) # TRIO115
def func():
import trio
async def main() -> None:
sleep = 0
for _ in range(2):
await trio.sleep(sleep) # OK
sleep = 10
trio.run(main)

View File

@@ -1,9 +0,0 @@
from __future__ import annotations
import django.settings
from library import foo
import os
import pytz
import sys
from . import local

View File

@@ -1,16 +0,0 @@
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

View File

@@ -1,9 +0,0 @@
from __future__ import annotations
import os
import django.settings
from library import foo
import pytz
from . import local
import sys

View File

@@ -50,29 +50,6 @@ class MetaClass(ABCMeta):
def static_method(not_cls) -> bool: def static_method(not_cls) -> bool:
return False return False
class ClsInArgsClass(ABCMeta):
def cls_as_argument(this, cls):
pass
def cls_as_pos_only_argument(this, cls, /):
pass
def cls_as_kw_only_argument(this, *, cls):
pass
def cls_as_varags(this, *cls):
pass
def cls_as_kwargs(this, **cls):
pass
class RenamingInMethodBodyClass(ABCMeta):
def bad_method(this):
this = this
this
def bad_method(this):
self = this
def func(x): def func(x):
return x return x

View File

@@ -61,7 +61,7 @@ class PosOnlyClass:
def good_method_pos_only(self, blah, /, something: str): def good_method_pos_only(self, blah, /, something: str):
pass pass
def bad_method_pos_only(this, blah, /, something: str): def bad_method_pos_only(this, blah, /, self, something: str):
pass pass
@@ -93,27 +93,3 @@ class ModelClass:
@foobar.thisisstatic @foobar.thisisstatic
def badstatic(foo): def badstatic(foo):
pass pass
class SelfInArgsClass:
def self_as_argument(this, self):
pass
def self_as_pos_only_argument(this, self, /):
pass
def self_as_kw_only_argument(this, *, self):
pass
def self_as_varags(this, *self):
pass
def self_as_kwargs(this, **self):
pass
class RenamingInMethodBodyClass:
def bad_method(this):
this = this
this
def bad_method(this):
self = this

View File

@@ -1,8 +0,0 @@
# 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

View File

@@ -147,15 +147,3 @@ ham[upper : ]
#: E203:1:10 #: E203:1:10
ham[upper :] 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"]}"

View File

@@ -1 +0,0 @@
a = (1 or)

View File

@@ -466,29 +466,6 @@ class Class:
# end # end
# E301
class Class:
"""Class for minimal repo."""
columns = []
@classmethod
def cls_method(cls) -> None:
pass
# end
# E301
class Class:
"""Class for minimal repo."""
def method(cls) -> None:
pass
@classmethod
def cls_method(cls) -> None:
pass
# end
# E302 # E302
"""Main module.""" """Main module."""
def fn(): def fn():

View File

@@ -1,50 +0,0 @@
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: ...

View File

@@ -1,4 +0,0 @@
"""Test where the error is after the module's docstring."""
def fn():
pass

View File

@@ -1,4 +0,0 @@
"Test where the first line is a comment, " + "and the rule violation follows it."
def fn():
pass

View File

@@ -1,5 +0,0 @@
def fn1():
pass
def fn2():
pass

View File

@@ -1,4 +0,0 @@
print("Test where the first line is a statement, and the rule violation follows it.")
def fn():
pass

View File

@@ -1,6 +0,0 @@
# Test where the first line is a comment, and the rule violation follows it.
def fn():
pass

View File

@@ -1,6 +0,0 @@
"""Test where the error is after the module's docstring."""
def fn():
pass

View File

@@ -1,6 +0,0 @@
"Test where the first line is a comment, " + "and the rule violation follows it."
def fn():
pass

View File

@@ -1,6 +0,0 @@
print("Test where the first line is a statement, and the rule violation follows it.")
def fn():
pass

View File

@@ -1,62 +0,0 @@
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

View File

@@ -1,62 +0,0 @@
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

View File

@@ -1,7 +0,0 @@
import os
os.environ["WORLD_SIZE"] = "1"
os.putenv("CUDA_VISIBLE_DEVICES", "4")
del os.environ["WORLD_SIZE"]
import torch

View File

@@ -1,88 +0,0 @@
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)

View File

@@ -14,6 +14,3 @@ class Chassis(RobotModuleTemplate):
" \ " \
\ \
'''blank line with whitespace
inside a multiline string'''

View File

@@ -1,14 +0,0 @@
# Unix style
def foo() -> None:
pass
def bar() -> None:
pass
if __name__ == '__main__':
foo()
bar()

View File

@@ -1,13 +0,0 @@
# Unix style
def foo() -> None:
pass
def bar() -> None:
pass
if __name__ == '__main__':
foo()
bar()

View File

@@ -1,17 +0,0 @@
# Windows style
def foo() -> None:
pass
def bar() -> None:
pass
if __name__ == '__main__':
foo()
bar()

View File

@@ -1,13 +0,0 @@
# Windows style
def foo() -> None:
pass
def bar() -> None:
pass
if __name__ == '__main__':
foo()
bar()

View File

@@ -1,5 +0,0 @@
# This is fine
def foo():
pass
# Some comment

View File

@@ -10,7 +10,7 @@ def f1():
# Here's a standalone comment that's over the limit. # Here's a standalone comment that's over the limit.
x = 2 x = 2
# Another standalone that is preceded by a newline and indent token and is over the limit. # Another standalone that is preceded by a newline and indent toke and is over the limit.
print("Here's a string that's over the limit, but it's not a docstring.") print("Here's a string that's over the limit, but it's not a docstring.")

View File

@@ -10,7 +10,7 @@ def f1():
# Here's a standalone comment that's over theß9💣2. # Here's a standalone comment that's over theß9💣2.
x = 2 x = 2
# Another standalone that is preceded by a newline and indent token and is over theß9💣2. # Another standalone that is preceded by a newline and indent toke and is over theß9💣2.
print("Here's a string that's over theß9💣2, but it's not a ß9💣2ing.") print("Here's a string that's over theß9💣2, but it's not a ß9💣2ing.")

View File

@@ -57,15 +57,3 @@ def func():
Returns: Returns:
the value""" the value"""
def func():
"""Do something.
Args:
x: the value
with a hanging indent
Returns:
the value
"""

View File

@@ -1,7 +0,0 @@
"""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

View File

@@ -1,6 +0,0 @@
"""Regression test for: https://github.com/astral-sh/ruff/issues/10384"""
import datetime
from datetime import datetime
datetime(1, 2, 3)

View File

@@ -1,16 +0,0 @@
"""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

View File

@@ -1,44 +0,0 @@
"""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

View File

@@ -1,44 +0,0 @@
"""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

View File

@@ -1,48 +0,0 @@
"""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

View File

@@ -1,10 +0,0 @@
"""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

View File

@@ -1,4 +0,0 @@
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

View File

@@ -1,32 +0,0 @@
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

View File

@@ -1,37 +0,0 @@
# 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

View File

@@ -1,36 +1,28 @@
# These testcases should raise errors class Str:
def __str__(self):
return 1
class Float: class Float:
def __str__(self): def __str__(self):
return 3.05 return 3.05
class Int: class Int:
def __str__(self):
return 1
class Int2:
def __str__(self): def __str__(self):
return 0 return 0
class Bool: class Bool:
def __str__(self): def __str__(self):
return False return False
# TODO: Once Ruff has better type checking class Str2:
def __str__(self):
x = "ruff"
return x
# TODO fixme once Ruff has better type checking
def return_int(): def return_int():
return 3 return 3
class ComplexReturn: class ComplexReturn:
def __str__(self): def __str__(self):
return return_int() return return_int()
# These testcases should NOT raise errors
class Str:
def __str__(self):
return "ruff"
class Str2:
def __str__(self):
x = "ruff"
return x

View File

@@ -17,14 +17,3 @@ class Fruit:
return choice(Fruit.COLORS) return choice(Fruit.COLORS)
pick_one_color = staticmethod(pick_one_color) 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);

View File

@@ -54,15 +54,3 @@ class StudentE(StudentD):
def setup(self): def setup(self):
pass pass
class StudentF(object):
__slots__ = ("name", "__dict__")
def __init__(self, name, middle_name):
self.name = name
self.middle_name = middle_name # [assigning-non-slot]
self.setup()
def setup(self):
pass

View File

@@ -51,7 +51,3 @@ foo == foo or foo == bar # Self-comparison.
foo[0] == "a" or foo[0] == "b" # Subscripts. foo[0] == "a" or foo[0] == "b" # Subscripts.
foo() == "a" or foo() == "b" # Calls. foo() == "a" or foo() == "b" # Calls.
import sys
sys.platform == "win32" or sys.platform == "emscripten" # sys attributes

View File

@@ -1,39 +0,0 @@
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]}"

View File

@@ -1,6 +1,6 @@
from typing import Any from typing import Any
a = 2
print((3.0).__add__(4.0)) # PLC2801 print((3.0).__add__(4.0)) # PLC2801
print((3.0).__sub__(4.0)) # PLC2801 print((3.0).__sub__(4.0)) # PLC2801
print((3.0).__mul__(4.0)) # PLC2801 print((3.0).__mul__(4.0)) # PLC2801
@@ -17,67 +17,6 @@ print((3.0).__str__()) # PLC2801
print((3.0).__repr__()) # PLC2801 print((3.0).__repr__()) # PLC2801
print([1, 2, 3].__len__()) # PLC2801 print([1, 2, 3].__len__()) # PLC2801
print((1).__neg__()) # PLC2801 print((1).__neg__()) # PLC2801
print(-a.__sub__(1)) # PLC2801
print(-(a).__sub__(1)) # PLC2801
print(-(-a.__sub__(1))) # PLC2801
print((5 - a).__sub__(1)) # PLC2801
print(-(5 - a).__sub__(1)) # PLC2801
print(-(-5 - a).__sub__(1)) # PLC2801
print(+-+-+-a.__sub__(1)) # PLC2801
print(a.__rsub__(2 - 1)) # PLC2801
print(a.__sub__(((((1)))))) # PLC2801
print(a.__sub__(((((2 - 1)))))) # PLC2801
print(a.__sub__(
3
+
4
))
print(a.__rsub__(
3
+
4
))
print(2 * a.__add__(3)) # PLC2801
x = 2 * a.__add__(3) # PLC2801
x = 2 * -a.__add__(3) # PLC2801
x = a.__add__(3) # PLC2801
x = -a.__add__(3) # PLC2801
x = (-a).__add__(3) # PLC2801
x = -(-a).__add__(3) # PLC2801
# Calls
print(a.__call__()) # PLC2801 (no fix, intentional)
# Lambda expressions
blah = lambda: a.__add__(1) # PLC2801
# If expressions
print(a.__add__(1) if a > 0 else a.__sub__(1)) # PLC2801
# Dict/Set/List/Tuple
print({"a": a.__add__(1)}) # PLC2801
print({a.__add__(1)}) # PLC2801
print([a.__add__(1)]) # PLC2801
print((a.__add__(1),)) # PLC2801
# Comprehension variants
print({i: i.__add__(1) for i in range(5)}) # PLC2801
print({i.__add__(1) for i in range(5)}) # PLC2801
print([i.__add__(1) for i in range(5)]) # PLC2801
print((i.__add__(1) for i in range(5))) # PLC2801
# Generators
gen = (i.__add__(1) for i in range(5)) # PLC2801
print(next(gen))
# Subscripts
print({"a": a.__add__(1)}["a"]) # PLC2801
# Starred
print(*[a.__add__(1)]) # PLC2801
# Slices
print([a.__add__(1), a.__sub__(1)][0:1]) # PLC2801
class Thing: class Thing:

View File

@@ -1,121 +0,0 @@
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

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