Compare commits

..

3 Commits

Author SHA1 Message Date
Charlie Marsh
4e23ee0e1d Make Arguments boxed 2024-02-09 22:06:05 -05:00
Charlie Marsh
7a349660e4 Reduce expression size 2024-02-09 21:37:28 -05:00
Charlie Marsh
a9d13e6bc9 Box identifier 2024-02-09 21:29:43 -05:00
571 changed files with 6449 additions and 15578 deletions

View File

@@ -1,8 +0,0 @@
[profile.ci]
# Print out output for failing tests as soon as they fail, and also at the end
# of the run (for easy scrollability).
failure-output = "immediate-final"
# Do not cancel the test run on the first failure.
fail-fast = false
status-level = "skip"

6
.github/CODEOWNERS vendored
View File

@@ -7,9 +7,3 @@
# Jupyter
/crates/ruff_linter/src/jupyter/ @dhruvmanila
/crates/ruff_formatter/ @MichaReiser
/crates/ruff_python_formatter/ @MichaReiser
/crates/ruff_python_parser/ @MichaReiser
# flake8-pyi
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood

View File

@@ -111,29 +111,19 @@ jobs:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@v2
with:
tool: cargo-insta
- uses: Swatinem/rust-cache@v2
- name: "Run tests"
shell: bash
env:
NEXTEST_PROFILE: "ci"
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
run: cargo insta test --all --all-features --unreferenced reject
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
RUSTDOCFLAGS: "-D warnings"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: ruff
path: target/debug/ruff
@@ -148,16 +138,15 @@ jobs:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
- name: "Install cargo insta"
uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
tool: cargo-insta
- uses: Swatinem/rust-cache@v2
- name: "Run tests"
shell: bash
run: |
cargo nextest run --all-features --profile ci
cargo test --all-features --doc
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
run: cargo insta test --all --exclude ruff_dev --all-features
cargo-test-wasm:
name: "cargo test (wasm)"
@@ -238,7 +227,7 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
name: Download comparison Ruff binary
id: ruff-target
with:
@@ -250,7 +239,6 @@ jobs:
with:
name: ruff
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
- name: Install ruff-ecosystem
@@ -325,13 +313,13 @@ jobs:
run: |
echo ${{ github.event.number }} > pr-number
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
name: Upload Results
with:
name: ecosystem-result
@@ -419,7 +407,7 @@ jobs:
- uses: actions/setup-python@v5
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@v0.9.0
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
- name: "Install Rust toolchain"
@@ -485,7 +473,7 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
name: Download development ruff binary
id: ruff-target
with:

View File

@@ -23,7 +23,7 @@ jobs:
- uses: actions/setup-python@v5
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@v0.9.0
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
- name: "Install Rust toolchain"

View File

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

View File

@@ -1,56 +1,5 @@
# Breaking Changes
## 0.3.0
### Ruff 2024.2 style
The formatter now formats code according to the Ruff 2024.2 style guide. Read the [changelog](./CHANGELOG.md#030) for a detailed list of stabilized style changes.
### `isort`: Use one blank line after imports in typing stub files ([#9971](https://github.com/astral-sh/ruff/pull/9971))
Previously, Ruff used one or two blank lines (or the number configured by `isort.lines-after-imports`) after imports in typing stub files (`.pyi` files).
The [typing style guide for stubs](https://typing.readthedocs.io/en/latest/source/stubs.html#style-guide) recommends using at most 1 blank line for grouping.
As of this release, `isort` now always uses one blank line after imports in stub files, the same as the formatter.
### `build` is no longer excluded by default ([#10093](https://github.com/astral-sh/ruff/pull/10093))
Ruff maintains a list of directories and files that are excluded by default. This list now consists of the following patterns:
- `.bzr`
- `.direnv`
- `.eggs`
- `.git`
- `.git-rewrite`
- `.hg`
- `.ipynb_checkpoints`
- `.mypy_cache`
- `.nox`
- `.pants.d`
- `.pyenv`
- `.pytest_cache`
- `.pytype`
- `.ruff_cache`
- `.svn`
- `.tox`
- `.venv`
- `.vscode`
- `__pypackages__`
- `_build`
- `buck-out`
- `dist`
- `node_modules`
- `site-packages`
- `venv`
Previously, the `build` directory was included in this list. However, the `build` directory tends to be a not-unpopular directory
name, and excluding it by default caused confusion. Ruff now no longer excludes `build` except if it is excluded by a `.gitignore` file
or because it is listed in `extend-exclude`.
### `--format` is no longer a valid `rule` or `linter` command option
Previously, `ruff rule` and `ruff linter` accepted the `--format <FORMAT>` option as an alias for `--output-format`. Ruff no longer
supports this alias. Please use `ruff rule --output-format <FORMAT>` and `ruff linter --output-format <FORMAT>` instead.
## 0.1.9
### `site-packages` is now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))

View File

@@ -1,131 +1,5 @@
# Changelog
## 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
This release includes support for range formatting (i.e., the ability to format specific lines

View File

@@ -26,10 +26,6 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
- [`cargo dev`](#cargo-dev)
- [Subsystems](#subsystems)
- [Compilation Pipeline](#compilation-pipeline)
- [Import Categorization](#import-categorization)
- [Project root](#project-root)
- [Package root](#package-root)
- [Import categorization](#import-categorization-1)
## The Basics
@@ -39,7 +35,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
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)
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)
@@ -67,7 +63,7 @@ You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
cargo install cargo-insta
```
And you'll need pre-commit to run some validation checks:
and pre-commit to run some validation checks:
```shell
pipx install pre-commit # or `pip install pre-commit` if you have a virtualenv
@@ -80,16 +76,6 @@ when making a commit:
pre-commit install
```
We recommend [nextest](https://nexte.st/) to run Ruff's test suite (via `cargo nextest run`),
though it's not strictly necessary:
```shell
cargo install cargo-nextest --locked
```
Throughout this guide, any usages of `cargo test` can be replaced with `cargo nextest run`,
if you choose to install `nextest`.
### Development
After cloning the repository, run Ruff locally from the repository root with:
@@ -316,7 +302,7 @@ To preview any changes to the documentation locally:
```
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
@@ -387,11 +373,6 @@ We have several ways of benchmarking and profiling Ruff:
- Microbenchmarks which run the linter or the formatter on individual files. These run on pull requests.
- Profiling the linter on either the microbenchmarks or entire projects
> \[!NOTE\]
> When running benchmarks, ensure that your CPU is otherwise idle (e.g., close any background
> applications, like web browsers). You may also want to switch your CPU to a "performance"
> mode, if it exists, especially when benchmarking short-lived processes.
### CPython Benchmark
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,

142
Cargo.lock generated
View File

@@ -123,9 +123,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.80"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
[[package]]
name = "argfile"
@@ -217,9 +217,9 @@ checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bstr"
version = "1.9.1"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
dependencies = [
"memchr",
"regex-automata 0.4.5",
@@ -273,9 +273,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.34"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -312,9 +312,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.1"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
dependencies = [
"clap_builder",
"clap_derive",
@@ -322,9 +322,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.1"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
dependencies = [
"anstream",
"anstyle",
@@ -383,7 +383,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -407,9 +407,9 @@ dependencies = [
[[package]]
name = "codspeed"
version = "2.4.0"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b85b056aa0541d1975ebc524149dde72803a5d7352b6aebf9eabc44f9017246"
checksum = "0eb4ab4dcb6554eb4f590fb16f99d3b102ab76f5f56554c9a5340518b32c499b"
dependencies = [
"colored",
"libc",
@@ -418,9 +418,9 @@ dependencies = [
[[package]]
name = "codspeed-criterion-compat"
version = "2.4.0"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02ae9de916d6315a5129bca2fc7957285f0b9f77a2f6a8734a0a146caee2b0b6"
checksum = "cc07a3d3f7e0c8961d0ffdee149d39b231bafdcdc3d978dc5ad790c615f55f3f"
dependencies = [
"codspeed",
"colored",
@@ -592,7 +592,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -603,7 +603,7 @@ checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77"
dependencies = [
"darling_core",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -914,6 +914,35 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "hoot"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df22a4d90f1b0e65fe3e0d6ee6a4608cc4d81f4b2eb3e670f44bb6bde711e452"
dependencies = [
"httparse",
"log",
]
[[package]]
name = "hootbin"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "354e60868e49ea1a39c44b9562ad207c4259dc6eabf9863bf3b0f058c55cfdb2"
dependencies = [
"fastrand",
"hoot",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "humantime"
version = "2.1.0"
@@ -1008,9 +1037,9 @@ dependencies = [
[[package]]
name = "indicatif"
version = "0.17.8"
version = "0.17.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25"
dependencies = [
"console",
"instant",
@@ -1048,9 +1077,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.35.1"
version = "1.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c985c1bef99cf13c58fade470483d81a2bfe846ebde60ed28cc2dddec2df9e2"
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
dependencies = [
"console",
"globset",
@@ -1101,7 +1130,7 @@ dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -1680,7 +1709,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -1931,7 +1960,7 @@ dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -1950,7 +1979,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.3.0"
version = "0.2.1"
dependencies = [
"anyhow",
"argfile",
@@ -2111,7 +2140,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.3.0"
version = "0.2.1"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2183,7 +2212,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -2365,7 +2394,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.3.0"
version = "0.2.1"
dependencies = [
"anyhow",
"clap",
@@ -2583,24 +2612,24 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "semver"
version = "1.0.22"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.197"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.4"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c1432112bce8b966497ac46519535189a3250a3812cd27a999678a69756f79f"
checksum = "b9b713f70513ae1f8d92665bbbbda5c295c2cf1da5542881ae5eefe20c9af132"
dependencies = [
"js-sys",
"serde",
@@ -2609,13 +2638,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.197"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -2678,7 +2707,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -2788,7 +2817,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -2810,9 +2839,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.51"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@@ -2898,7 +2927,7 @@ dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -2909,28 +2938,28 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
"test-case-core",
]
[[package]]
name = "thiserror"
version = "1.0.57"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.57"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -3067,7 +3096,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -3233,12 +3262,13 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "2.9.6"
version = "2.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35"
checksum = "0b52731d03d6bb2fd18289d4028aee361d6c28d44977846793b994b13cdcc64d"
dependencies = [
"base64",
"flate2",
"hootbin",
"log",
"once_cell",
"rustls",
@@ -3286,7 +3316,7 @@ checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -3380,7 +3410,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
"wasm-bindgen-shared",
]
@@ -3414,7 +3444,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3447,7 +3477,7 @@ checksum = "a5211b7550606857312bba1d978a8ec75692eae187becc5e680444fffc5e6f89"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -3712,7 +3742,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]

View File

@@ -14,39 +14,39 @@ license = "MIT"
[workspace.dependencies]
aho-corasick = { version = "1.1.2" }
annotate-snippets = { version = "0.9.2", features = ["color"] }
anyhow = { version = "1.0.80" }
anyhow = { version = "1.0.79" }
argfile = { version = "0.1.6" }
assert_cmd = { version = "2.0.13" }
bincode = { version = "1.3.3" }
bitflags = { version = "2.4.1" }
bstr = { version = "1.9.1" }
bstr = { version = "1.9.0" }
cachedir = { version = "0.3.1" }
chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
clap = { version = "4.5.1", features = ["derive"] }
chrono = { version = "0.4.33", default-features = false, features = ["clock"] }
clap = { version = "4.4.18", features = ["derive"] }
clap_complete_command = { version = "0.5.1" }
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" }
configparser = { version = "3.0.3" }
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" }
countme = { version = "3.0.1" }
countme = { version ="3.0.1"}
criterion = { version = "0.5.1", default-features = false }
dirs = { version = "5.0.0" }
drop_bomb = { version = "0.1.5" }
env_logger = { version = "0.10.1" }
env_logger = { version ="0.10.1"}
fern = { version = "0.6.1" }
filetime = { version = "0.2.23" }
fs-err = { version = "2.11.0" }
fs-err = { version ="2.11.0"}
glob = { version = "0.3.1" }
globset = { version = "0.4.14" }
hexf-parse = { version = "0.2.1" }
hexf-parse = { version ="0.2.1"}
ignore = { version = "0.4.22" }
imara-diff = { version = "0.1.5" }
imara-diff ={ version = "0.1.5"}
imperative = { version = "1.0.4" }
indicatif = { version = "0.17.8" }
indoc = { version = "2.0.4" }
insta = { version = "1.35.1", feature = ["filters", "glob"] }
indicatif ={ version = "0.17.7"}
indoc ={ version = "2.0.4"}
insta = { version = "1.34.0", feature = ["filters", "glob"] }
insta-cmd = { version = "0.4.0" }
is-macro = { version = "0.3.5" }
is-wsl = { version = "0.4.0" }
@@ -57,7 +57,7 @@ lexical-parse-float = { version = "0.8.0", features = ["format"] }
libcst = { version = "1.1.0", default-features = false }
log = { version = "0.4.17" }
memchr = { version = "2.7.1" }
mimalloc = { version = "0.1.39" }
mimalloc = { version ="0.1.39"}
natord = { version = "1.0.9" }
notify = { version = "6.1.1" }
once_cell = { version = "1.19.0" }
@@ -75,35 +75,35 @@ regex = { version = "1.10.2" }
result-like = { version = "0.5.0" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
semver = { version = "1.0.22" }
serde = { version = "1.0.197", features = ["derive"] }
serde-wasm-bindgen = { version = "0.6.4" }
seahash = { version ="4.1.0"}
semver = { version = "1.0.21" }
serde = { version = "1.0.196", features = ["derive"] }
serde-wasm-bindgen = { version = "0.6.3" }
serde_json = { version = "1.0.113" }
serde_test = { version = "1.0.152" }
serde_with = { version = "3.6.0", default-features = false, features = ["macros"] }
shellexpand = { version = "3.0.0" }
shlex = { version = "1.3.0" }
shlex = { version ="1.3.0"}
similar = { version = "2.4.0", features = ["inline"] }
smallvec = { version = "1.13.1" }
static_assertions = "1.1.0"
strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.3" }
syn = { version = "2.0.51" }
tempfile = { version = "3.9.0" }
syn = { version = "2.0.40" }
tempfile = { version ="3.9.0"}
test-case = { version = "3.3.1" }
thiserror = { version = "1.0.57" }
tikv-jemallocator = { version = "0.5.0" }
thiserror = { version = "1.0.51" }
tikv-jemallocator = { version ="0.5.0"}
toml = { version = "0.8.9" }
tracing = { version = "0.1.40" }
tracing-indicatif = { version = "0.3.6" }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
typed-arena = { version = "2.0.2" }
unic-ucd-category = { version = "0.9" }
unic-ucd-category = { version ="0.9"}
unicode-ident = { version = "1.0.12" }
unicode-width = { version = "0.1.11" }
unicode_names2 = { version = "1.2.1" }
ureq = { version = "2.9.6" }
ureq = { version = "2.9.1" }
url = { version = "2.5.0" }
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
walkdir = { version = "2.3.2" }

View File

@@ -8,7 +8,7 @@
[![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)
[**Discord**](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.
@@ -150,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.3.0
rev: v0.2.1
hooks:
# Run the linter.
- id: ruff
@@ -172,7 +172,7 @@ jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: chartboost/ruff-action@v1
```
@@ -341,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
[**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
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).
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
@@ -378,7 +378,6 @@ Ruff is released under the MIT license.
Ruff is used by a number of major open-source projects and companies, including:
- [Albumentations](https://github.com/albumentations-team/albumentations)
- Amazon ([AWS SAM](https://github.com/aws/serverless-application-model))
- Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python))
- [Apache Airflow](https://github.com/apache/airflow)

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.3.0"
version = "0.2.1"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -48,7 +48,6 @@ serde = { workspace = true }
serde_json = { workspace = true }
shellexpand = { workspace = true }
strum = { workspace = true, features = [] }
tempfile = { workspace = true }
thiserror = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true, features = ["log"] }

View File

@@ -63,6 +63,10 @@ pub enum Command {
/// Output format
#[arg(long, value_enum, default_value = "text")]
output_format: HelpFormat,
/// Output format (Deprecated: Use `--output-format` instead).
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
format: Option<HelpFormat>,
},
/// List or describe the available configuration options.
Config { option: Option<String> },
@@ -71,6 +75,10 @@ pub enum Command {
/// Output format
#[arg(long, value_enum, default_value = "text")]
output_format: HelpFormat,
/// Output format (Deprecated: Use `--output-format` instead).
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
format: Option<HelpFormat>,
},
/// Clear any caches in the current directory and any subdirectories.
#[clap(alias = "--clean")]
@@ -737,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)]
enum InvalidConfigFlagReason {
InvalidToml(toml::de::Error),
/// It was valid TOML, but not a valid ruff config file.
/// E.g. the user tried to select a rule that doesn't exist,
/// or tried to enable a setting that doesn't exist
ValidTomlButInvalidRuffSchema(toml::de::Error),
/// It was a valid ruff config file, but the user tried to pass a
/// value for `extend` as part of the config override.
// `extend` is special, because it affects which config files we look at
/// in the first place. We currently only parse --config overrides *after*
/// we've combined them with all the arguments from the various config files
/// that we found, so trying to override `extend` as part of a --config
/// override is forbidden.
ExtendPassedViaConfigFlag,
enum TomlParseFailureKind {
SyntaxError,
UnknownOption,
}
impl InvalidConfigFlagReason {
const fn description(&self) -> &'static str {
match self {
Self::InvalidToml(_) => "The supplied argument is not valid TOML",
Self::ValidTomlButInvalidRuffSchema(_) => {
impl std::fmt::Display for TomlParseFailureKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let display = match self {
Self::SyntaxError => "The supplied argument is not valid TOML",
Self::UnknownOption => {
"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())
}
}
@@ -815,19 +827,18 @@ impl TypedValueParser for ConfigArgumentParser {
.to_str()
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
let config_parse_error = match toml::Table::from_str(value) {
Ok(table) => match table.try_into::<Options>() {
Ok(option) => {
if option.extend.is_none() {
return Ok(SingleConfigArgument::SettingsOverride(Arc::new(option)));
}
InvalidConfigFlagReason::ExtendPassedViaConfigFlag
}
Err(underlying_error) => {
InvalidConfigFlagReason::ValidTomlButInvalidRuffSchema(underlying_error)
}
let toml_parse_error = match toml::Table::from_str(value) {
Ok(table) => match table.try_into() {
Ok(option) => return Ok(SingleConfigArgument::SettingsOverride(Arc::new(option))),
Err(underlying_error) => TomlParseFailure {
kind: TomlParseFailureKind::UnknownOption,
underlying_error,
},
},
Err(underlying_error) => TomlParseFailure {
kind: TomlParseFailureKind::SyntaxError,
underlying_error,
},
Err(underlying_error) => InvalidConfigFlagReason::InvalidToml(underlying_error),
};
let mut new_error = clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
@@ -842,21 +853,6 @@ impl TypedValueParser for ConfigArgumentParser {
clap::error::ContextValue::String(value.to_string()),
);
let underlying_error = match &config_parse_error {
InvalidConfigFlagReason::ExtendPassedViaConfigFlag => {
let tip = config_parse_error.description().into();
new_error.insert(
clap::error::ContextKind::Suggested,
clap::error::ContextValue::StyledStrs(vec![tip]),
);
return Err(new_error);
}
InvalidConfigFlagReason::InvalidToml(underlying_error)
| InvalidConfigFlagReason::ValidTomlButInvalidRuffSchema(underlying_error) => {
underlying_error
}
};
// small hack so that multiline tips
// have the same indent on the left-hand side:
let tip_indent = " ".repeat(" tip: ".len());
@@ -885,16 +881,12 @@ The path `{value}` does not exist"
));
}
} else if value.contains('=') {
tip.push_str(&format!(
"\n\n{}:\n\n{underlying_error}",
config_parse_error.description()
));
tip.push_str(&format!("\n\n{toml_parse_error}"));
}
let tip = tip.trim_end().to_owned().into();
new_error.insert(
clap::error::ContextKind::Suggested,
clap::error::ContextValue::StyledStrs(vec![tip]),
clap::error::ContextValue::StyledStrs(vec![tip.into()]),
);
Err(new_error)

View File

@@ -1,7 +1,7 @@
use std::fmt::Debug;
use std::fs::{self, File};
use std::hash::Hasher;
use std::io::{self, BufReader, Write};
use std::io::{self, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
@@ -15,7 +15,6 @@ use rayon::iter::ParallelIterator;
use rayon::iter::{IntoParallelIterator, ParallelBridge};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use tempfile::NamedTempFile;
use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_diagnostics::{DiagnosticKind, Fix};
@@ -166,29 +165,15 @@ impl Cache {
return Ok(());
}
// Write the cache to a temporary file first and then rename it for an "atomic" write.
// Protects against data loss if the process is killed during the write and races between different ruff
// processes, resulting in a corrupted cache file. https://github.com/astral-sh/ruff/issues/8147#issuecomment-1943345964
let mut temp_file =
NamedTempFile::new_in(self.path.parent().expect("Write path must have a parent"))
.context("Failed to create temporary file")?;
// Serialize to in-memory buffer because hyperfine benchmark showed that it's faster than
// using a `BufWriter` and our cache files are small enough that streaming isn't necessary.
let serialized =
bincode::serialize(&self.package).context("Failed to serialize cache data")?;
temp_file
.write_all(&serialized)
.context("Failed to write serialized cache to temporary file.")?;
temp_file.persist(&self.path).with_context(|| {
let file = File::create(&self.path)
.with_context(|| format!("Failed to create cache file '{}'", self.path.display()))?;
let writer = BufWriter::new(file);
bincode::serialize_into(writer, &self.package).with_context(|| {
format!(
"Failed to rename temporary cache file to {}",
"Failed to serialise cache to file '{}'",
self.path.display()
)
})?;
Ok(())
})
}
/// Applies the pending changes without storing the cache to disk.

View File

@@ -18,7 +18,7 @@ use ruff_linter::settings::types::SerializationFormat;
use ruff_linter::{fs, warn_user, warn_user_once};
use ruff_workspace::Settings;
use crate::args::{Args, CheckCommand, Command, FormatCommand};
use crate::args::{Args, CheckCommand, Command, FormatCommand, HelpFormat};
use crate::printer::{Flags as PrinterFlags, Printer};
pub mod args;
@@ -114,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(
Args {
command,
log_level_args,
}: Args,
deprecated_alias_warning: Option<&'static str>,
) -> Result<ExitStatus> {
{
let default_panic_hook = std::panic::take_hook();
@@ -150,10 +158,6 @@ pub fn run(
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 {
Command::Version { output_format } => {
commands::version::version(output_format)?;
@@ -162,8 +166,10 @@ pub fn run(
Command::Rule {
rule,
all,
output_format,
format,
mut output_format,
} => {
output_format = resolve_help_output_format(output_format, format);
if all {
commands::rule::rules(output_format)?;
}
@@ -176,7 +182,11 @@ pub fn run(
commands::config::config(option.as_deref())?;
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)?;
Ok(ExitStatus::Success)
}

View File

@@ -27,42 +27,26 @@ pub fn main() -> ExitCode {
let mut args =
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
// and we also don't know if the user runs ruff with quiet.
// Keep the message and pass it to `run` that is responsible for emitting the warning.
let deprecated_alias_warning = match args.get(1).and_then(|arg| arg.to_str()) {
// Deprecated aliases that are handled by clap
Some("--explain") => {
Some("`ruff --explain <RULE>` is deprecated. Use `ruff rule <RULE>` instead.")
}
Some("--clean") => {
Some("`ruff --clean` is deprecated. Use `ruff clean` instead.")
}
Some("--generate-shell-completion") => {
Some("`ruff --generate-shell-completion <SHELL>` is deprecated. Use `ruff generate-shell-completion <SHELL>` instead.")
}
// Deprecated `ruff` alias to `ruff check`
// Clap doesn't support default subcommands but we want to run `check` by
// default for convenience and backwards-compatibility, so we just
// preprocess the arguments accordingly before passing them to Clap.
Some(arg) if !Command::has_subcommand(arg)
// Clap doesn't support default subcommands but we want to run `check` by
// default for convenience and backwards-compatibility, so we just
// preprocess the arguments accordingly before passing them to Clap.
if let Some(arg) = args.get(1) {
if arg
.to_str()
.is_some_and(|arg| !Command::has_subcommand(rewrite_legacy_subcommand(arg)))
&& arg != "-h"
&& arg != "--help"
&& arg != "-V"
&& arg != "--version"
&& arg != "help" => {
{
args.insert(1, "check".into());
Some("`ruff <path>` is deprecated. Use `ruff check <path>` instead.")
}
},
_ => None
};
&& arg != "help"
{
args.insert(1, "check".into());
}
}
let args = Args::parse_from(args);
match run(args, deprecated_alias_warning) {
match run(args) {
Ok(code) => code.into(),
Err(err) => {
#[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

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

View File

@@ -7,15 +7,10 @@ use std::str;
use anyhow::Result;
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
use regex::escape;
use tempfile::TempDir;
const BIN_NAME: &str = "ruff";
fn tempdir_filter(tempdir: &TempDir) -> String {
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
}
#[test]
fn default_options() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
@@ -152,29 +147,28 @@ fn too_many_config_files() -> Result<()> {
let ruff2_dot_toml = tempdir.path().join("ruff2.toml");
fs::File::create(&ruff_dot_toml)?;
fs::File::create(&ruff2_dot_toml)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
let expected_stderr = format!(
"\
ruff failed
Cause: You cannot specify more than one configuration file on the command line.
tip: remove either `--config={}` or `--config={}`.
For more information, try `--help`.
",
ruff_dot_toml.display(),
ruff2_dot_toml.display(),
);
let cmd = Command::new(get_cargo_bin(BIN_NAME))
.arg("format")
.arg("--config")
.arg(&ruff_dot_toml)
.arg("--config")
.arg(&ruff2_dot_toml)
.arg("."), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: You cannot specify more than one configuration file on the command line.
tip: remove either `--config=[TMP]/ruff.toml` or `--config=[TMP]/ruff2.toml`.
For more information, try `--help`.
"###);
});
.arg(".")
.output()?;
let stderr = std::str::from_utf8(&cmd.stderr)?;
assert_eq!(stderr, expected_stderr);
Ok(())
}
@@ -183,29 +177,27 @@ fn config_file_and_isolated() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_dot_toml = tempdir.path().join("ruff.toml");
fs::File::create(&ruff_dot_toml)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
let expected_stderr = format!(
"\
ruff failed
Cause: The argument `--config={}` cannot be used with `--isolated`
tip: You cannot specify a configuration file and also specify `--isolated`,
as `--isolated` causes ruff to ignore all configuration files.
For more information, try `--help`.
",
ruff_dot_toml.display(),
);
let cmd = Command::new(get_cargo_bin(BIN_NAME))
.arg("format")
.arg("--config")
.arg(&ruff_dot_toml)
.arg("--isolated")
.arg("."), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: The argument `--config=[TMP]/ruff.toml` cannot be used with `--isolated`
tip: You cannot specify a configuration file and also specify `--isolated`,
as `--isolated` causes ruff to ignore all configuration files.
For more information, try `--help`.
"###);
});
.arg(".")
.output()?;
let stderr = std::str::from_utf8(&cmd.stderr)?;
assert_eq!(stderr, expected_stderr);
Ok(())
}
@@ -358,52 +350,58 @@ def f(x):
'''
pass
"#), @r###"
success: true
exit_code: 0
----- stdout -----
def f(x):
"""
Something about `f`. And an example:
success: true
exit_code: 0
----- stdout -----
def f(x):
"""
Something about `f`. And an example:
.. code-block:: python
.. code-block:: python
foo, bar, quux = (
this_is_a_long_line(
lion,
hippo,
lemur,
bear,
)
)
Another example:
```py
foo, bar, quux = (
this_is_a_long_line(
lion,
hippo,
lemur,
bear,
)
(
foo,
bar,
quux,
) = this_is_a_long_line(
lion,
hippo,
lemur,
bear,
)
```
And another:
Another example:
>>> foo, bar, quux = (
... this_is_a_long_line(
... lion,
... hippo,
... lemur,
... bear,
... )
... )
"""
pass
```py
(
foo,
bar,
quux,
) = this_is_a_long_line(
lion,
hippo,
lemur,
bear,
)
```
----- stderr -----
"###);
And another:
>>> (
... foo,
... bar,
... quux,
... ) = this_is_a_long_line(
... lion,
... hippo,
... lemur,
... bear,
... )
"""
pass
----- stderr -----
"###);
Ok(())
}

View File

@@ -71,7 +71,6 @@ impl<'a> RuffCheck<'a> {
/// Generate a [`Command`] for the `ruff check` command.
fn build(self) -> Command {
let mut cmd = ruff_cmd();
cmd.arg("check");
if let Some(output_format) = self.output_format {
cmd.args(["--output-format", output_format]);
}
@@ -806,13 +805,13 @@ fn full_output_format() {
}
#[test]
fn rule_f401() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401"]));
fn explain_status_codes_f401() {
assert_cmd_snapshot!(ruff_cmd().args(["--explain", "F401"]));
}
#[test]
fn rule_invalid_rule_name() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404"]), @r###"
fn explain_status_codes_ruf404() {
assert_cmd_snapshot!(ruff_cmd().args(["--explain", "RUF404"]), @r###"
success: false
exit_code: 2
----- stdout -----
@@ -1349,7 +1348,7 @@ fn unreadable_pyproject_toml() -> Result<()> {
// Don't `--isolated` since the configuration discovery is where the error happens
let args = Args::parse_from(["", "check", "--no-cache", tempdir.path().to_str().unwrap()]);
let err = run(args, None).err().context("Unexpected success")?;
let err = run(args).err().context("Unexpected success")?;
assert_eq!(
err.chain()
.map(std::string::ToString::to_string)

View File

@@ -12,7 +12,7 @@ use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
use tempfile::TempDir;
const BIN_NAME: &str = "ruff";
const STDIN_BASE_OPTIONS: &[&str] = &["check", "--no-cache", "--output-format", "concise"];
const STDIN_BASE_OPTIONS: &[&str] = &["--no-cache", "--output-format", "concise"];
fn tempdir_filter(tempdir: &TempDir) -> String {
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
@@ -246,6 +246,7 @@ OTHER = "OTHER"
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
// Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude
@@ -292,6 +293,7 @@ inline-quotes = "single"
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--stdin-filename", "generated.py"])
@@ -384,6 +386,7 @@ inline-quotes = "single"
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--stdin-filename", "generated.py"])
@@ -432,6 +435,7 @@ inline-quotes = "single"
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--stdin-filename", "generated.py"])
@@ -491,6 +495,7 @@ ignore = ["D203", "D212"]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(sub_dir)
.arg("check")
.args(STDIN_BASE_OPTIONS)
, @r###"
success: true
@@ -590,24 +595,6 @@ fn too_many_config_files() -> Result<()> {
Ok(())
}
#[test]
fn extend_passed_via_config_argument() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--config", "extend = 'foo.toml'", "."]), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: invalid value 'extend = 'foo.toml'' for '--config <CONFIG_OPTION>'
tip: Cannot include `extend` in a --config flag value
For more information, try '--help'.
"###);
}
#[test]
fn config_file_and_isolated() -> Result<()> {
let tempdir = TempDir::new()?;
@@ -916,6 +903,7 @@ include = ["*.ipy"]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--extension", "ipy:ipynb"])

View File

@@ -3,7 +3,7 @@ source: crates/ruff/tests/integration_test.rs
info:
program: ruff
args:
- rule
- "--explain"
- F401
---
success: true
@@ -25,15 +25,6 @@ import cycles. They also increase the cognitive load of reading the code.
If an import statement is used to check for the availability or existence
of a module, consider using `importlib.util.find_spec` instead.
If an import statement is used to re-export a symbol as part of a module's
public interface, consider using a "redundant" import alias, which
instructs Ruff (and other tools) to respect the re-export, and avoid
marking it as unused, as in:
```python
from module import member as member
```
## Example
```python
import numpy as np # unused import
@@ -60,11 +51,11 @@ else:
```
## Options
- `lint.ignore-init-module-imports`
- `lint.pyflakes.extend-generics`
## References
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
- [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols)
----- stderr -----

View File

@@ -44,6 +44,7 @@ file_resolver.exclude = [
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
@@ -231,7 +232,7 @@ linter.flake8_bandit.check_typed_exception = false
linter.flake8_bugbear.extend_immutable_calls = []
linter.flake8_builtins.builtins_ignorelist = []
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}(-\d{4})*
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+(\(C\)\s+)?\d{4}(-\d{4})*
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
@@ -365,3 +366,4 @@ formatter.docstring_code_format = disabled
formatter.docstring_code_line_width = dynamic
----- stderr -----

View File

@@ -13,7 +13,6 @@ license = { workspace = true }
[lib]
bench = false
doctest = false
[[bench]]
name = "linter"

View File

@@ -11,7 +11,6 @@ repository = { workspace = true }
license = { workspace = true }
[lib]
doctest = false
[dependencies]
ruff_text_size = { path = "../ruff_text_size" }

View File

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

View File

@@ -11,7 +11,6 @@ repository = { workspace = true }
license = { workspace = true }
[lib]
doctest = false
[dependencies]
ruff_macros = { path = "../ruff_macros" }

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.3.0"
version = "0.2.1"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

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

View File

@@ -119,16 +119,3 @@ def func(x: bool):
settings(True)
from dataclasses import dataclass, InitVar
@dataclass
class Fit:
force: InitVar[bool] = False
def __post_init__(self, force: bool) -> None:
print(force)
Fit(force=True)

View File

@@ -11,25 +11,13 @@ class _UnusedTypedDict2(typing.TypedDict):
class _UsedTypedDict(TypedDict):
foo: bytes
foo: bytes
class _CustomClass(_UsedTypedDict):
bar: list[int]
_UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int})
_UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
def uses_UsedTypedDict3(arg: _UsedTypedDict3) -> None: ...
# In `.py` files, we don't flag unused definitions in class scopes (unlike in `.pyi`
# files).
class _CustomClass3:
class _UnusedTypeDict4(TypedDict):
pass
def method(self) -> None:
_CustomClass3._UnusedTypeDict4()

View File

@@ -35,13 +35,3 @@ _UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int})
_UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
def uses_UsedTypedDict3(arg: _UsedTypedDict3) -> None: ...
# In `.pyi` files, we flag unused definitions in class scopes as well as in the global
# scope (unlike in `.py` files).
class _CustomClass3:
class _UnusedTypeDict4(TypedDict):
pass
def method(self) -> None:
_CustomClass3._UnusedTypeDict4()

View File

@@ -193,11 +193,3 @@ def func():
for y in range(5):
g(x, idx)
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

@@ -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

@@ -36,47 +36,35 @@ for i in list( # Comment
): # PERF101
pass
for i in list(foo_dict): # OK
for i in list(foo_dict): # Ok
pass
for i in list(1): # OK
for i in list(1): # Ok
pass
for i in list(foo_int): # OK
for i in list(foo_int): # Ok
pass
import itertools
for i in itertools.product(foo_int): # OK
for i in itertools.product(foo_int): # Ok
pass
for i in list(foo_list): # OK
for i in list(foo_list): # Ok
foo_list.append(i + 1)
for i in list(foo_list): # PERF101
# Make sure we match the correct list
other_list.append(i + 1)
for i in list(foo_tuple): # OK
for i in list(foo_tuple): # Ok
foo_tuple.append(i + 1)
for i in list(foo_set): # OK
for i in list(foo_set): # Ok
foo_set.append(i + 1)
x, y, nested_tuple = (1, 2, (3, 4, 5))
for i in list(nested_tuple): # PERF101
pass
for i in list(foo_list): # OK
if True:
foo_list.append(i + 1)
for i in list(foo_list): # OK
if True:
foo_list[i] = i + 1
for i in list(foo_list): # OK
if True:
del foo_list[i + 1]

View File

@@ -147,9 +147,3 @@ ham[upper : ]
#: E203:1:10
ham[upper :]
#: Okay
ham[lower +1 :, "columnname"]
#: E203:1:13
ham[lower + 1 :, "columnname"]

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

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

View File

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

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

@@ -4,12 +4,7 @@
1 in (
1, 2, 3
)
fruits = ["cherry", "grapes"]
"cherry" in fruits
_ = {key: value for key, value in {"a": 1, "b": 2}.items() if key in ("a", "b")}
# OK
fruits in [[1, 2, 3], [4, 5, 6]]
fruits in [1, 2, 3]
1 in [[1, 2, 3], [4, 5, 6]]
_ = {key: value for key, value in {"a": 1, "b": 2}.items() if key in (["a", "b"], ["c", "d"])}
fruits = ["cherry", "grapes"]
"cherry" in fruits

View File

@@ -35,15 +35,6 @@ if argc != 0: # correct
if argc != 1: # correct
pass
if argc != -1.0: # correct
pass
if argc != 0.0: # correct
pass
if argc != 1.0: # correct
pass
if argc != 2: # [magic-value-comparison]
pass
@@ -53,12 +44,6 @@ if argc != -2: # [magic-value-comparison]
if argc != +2: # [magic-value-comparison]
pass
if argc != -2.0: # [magic-value-comparison]
pass
if argc != +2.0: # [magic-value-comparison]
pass
if __name__ == "__main__": # correct
pass

View File

@@ -17,14 +17,3 @@ class Fruit:
return choice(Fruit.COLORS)
pick_one_color = staticmethod(pick_one_color)
class Class:
def class_method(cls):
pass
class_method = classmethod(class_method);another_statement
def static_method():
pass
static_method = staticmethod(static_method);

View File

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

View File

@@ -33,7 +33,7 @@ bool(b"")
bool(1.0)
int().denominator
# These become literals
# These become string or byte literals
str()
str("foo")
str("""
@@ -53,9 +53,3 @@ bool(False)
# These become a literal but retain parentheses
int(1).denominator
# These too are literals in spirit
int(+1)
int(-1)
float(+1.0)
float(-1.0)

View File

@@ -1,75 +0,0 @@
import codecs
import io
from pathlib import Path
# Errors
with open("FURB129.py") as f:
for _line in f.readlines():
pass
a = [line.lower() for line in f.readlines()]
b = {line.upper() for line in f.readlines()}
c = {line.lower(): line.upper() for line in f.readlines()}
with Path("FURB129.py").open() as f:
for _line in f.readlines():
pass
for _line in open("FURB129.py").readlines():
pass
for _line in Path("FURB129.py").open().readlines():
pass
def func():
f = Path("FURB129.py").open()
for _line in f.readlines():
pass
f.close()
def func(f: io.BytesIO):
for _line in f.readlines():
pass
def func():
with (open("FURB129.py") as f, foo as bar):
for _line in f.readlines():
pass
for _line in bar.readlines():
pass
# False positives
def func(f):
for _line in f.readlines():
pass
def func(f: codecs.StreamReader):
for _line in f.readlines():
pass
def func():
class A:
def readlines(self) -> list[str]:
return ["a", "b", "c"]
return A()
for _line in func().readlines():
pass
# OK
for _line in ["a", "b", "c"]:
pass
with open("FURB129.py") as f:
for _line in f:
pass
for _line in f.readlines(10):
pass
for _not_line in f.readline():
pass

View File

@@ -162,26 +162,3 @@ async def f(x: bool):
T = asyncio.create_task(asyncio.sleep(1))
else:
T = None
# Error
def f():
loop = asyncio.new_event_loop()
loop.create_task(main()) # Error
# Error
def f():
loop = asyncio.get_event_loop()
loop.create_task(main()) # Error
# OK
def f():
global task
loop = asyncio.new_event_loop()
task = loop.create_task(main()) # Error
# OK
def f():
global task
loop = asyncio.get_event_loop()
task = loop.create_task(main()) # Error

View File

@@ -57,16 +57,6 @@ revision_heads_map_ast = [
list(zip(x, y))[0]
[*zip(x, y)][0]
# RUF015 (pop)
list(x).pop(0)
[i for i in x].pop(0)
list(i for i in x).pop(0)
# OK
list(x).pop(1)
list(x).remove(0)
list(x).remove(1)
def test():
zip = list # Overwrite the builtin zip

View File

@@ -68,7 +68,3 @@ def method_calls():
first = "Wendy"
last = "Appleseed"
value.method("{first} {last}") # RUF027
def format_specifiers():
a = 4
b = "{a:b} {a:^5}"

View File

@@ -25,10 +25,6 @@ def negative_cases():
json3 = "{ 'positive': 'false' }"
alternative_formatter("{a}", a=5)
formatted = "{a}".fmt(a=7)
partial = "partial sentence"
a = _("formatting of {partial} in a translation string is bad practice")
_("formatting of {partial} in a translation string is bad practice")
print(_("formatting of {partial} in a translation string is bad practice"))
print(do_nothing("{a}".format(a=3)))
print(do_nothing(alternative_formatter("{a}", a=5)))
print(format(do_nothing("{a}"), a=5))

View File

@@ -1,2 +0,0 @@
# 测试eval函数,eval()函数用来执行一个字符串表达式并返t表达式的值。另外可以讲字符串转换成列表或元组或字典
a = "{1: 'a', 2: 'b'}"

View File

@@ -1,63 +0,0 @@
def fmt_off_between_lists():
test_list = [
# fmt: off
1,
2,
3,
]
# note: the second `fmt: skip`` should be OK
def fmt_skip_on_own_line():
# fmt: skip
pass # fmt: skip
@fmt_skip_on_own_line
# fmt: off
@fmt_off_between_lists
def fmt_off_between_decorators():
pass
@fmt_off_between_decorators
# fmt: off
class FmtOffBetweenClassDecorators:
...
def fmt_off_in_else():
x = [1, 2, 3]
for val in x:
print(x)
# fmt: off
else:
print("done")
while False:
print("while")
# fmt: off
# fmt: off
else:
print("done")
if len(x) > 3:
print("huh?")
# fmt: on
# fmt: off
else:
print("expected")
class Test:
@classmethod
# fmt: off
def cls_method_a(
# fmt: off
cls,
) -> None: # noqa: test # fmt: skip
pass
def fmt_on_trailing():
# fmt: off
val = 5 # fmt: on
pass # fmt: on

View File

@@ -2,14 +2,11 @@ use ruff_python_ast::Comprehension;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_simplify, refurb};
use crate::rules::flake8_simplify;
/// Run lint rules over a [`Comprehension`] syntax nodes.
pub(crate) fn comprehension(comprehension: &Comprehension, checker: &mut Checker) {
if checker.enabled(Rule::InDictKeys) {
flake8_simplify::rules::key_in_dict_comprehension(checker, comprehension);
}
if checker.enabled(Rule::ReadlinesInFor) {
refurb::rules::readlines_in_comprehension(checker, comprehension);
}
}

View File

@@ -281,21 +281,17 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
}
}
if checker.source_type.is_stub()
|| matches!(scope.kind, ScopeKind::Module | ScopeKind::Function(_))
{
if checker.enabled(Rule::UnusedPrivateTypeVar) {
flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::UnusedPrivateProtocol) {
flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::UnusedPrivateTypedDict) {
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::UnusedPrivateTypeVar) {
flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::UnusedPrivateProtocol) {
flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::UnusedPrivateTypedDict) {
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::AsyncioDanglingTask) {

View File

@@ -116,7 +116,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_simplify::rules::use_capital_environment_variables(checker, expr);
}
if checker.enabled(Rule::UnnecessaryIterableAllocationForFirstElement) {
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, expr);
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, subscript);
}
if checker.enabled(Rule::InvalidIndexType) {
ruff::rules::invalid_index_type(checker, subscript);
@@ -134,7 +134,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
elts,
ctx,
range: _,
parenthesized: _,
})
| Expr::List(ast::ExprList {
elts,
@@ -344,15 +343,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
Expr::Call(
call @ ast::ExprCall {
func,
arguments:
Arguments {
args,
keywords,
range: _,
},
arguments,
range: _,
},
) => {
let Arguments {
args,
keywords,
range: _,
} = &**arguments;
if checker.any_enabled(&[
// pylint
Rule::BadStringFormatCharacter,
@@ -965,9 +965,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::DefaultFactoryKwarg) {
ruff::rules::default_factory_kwarg(checker, call);
}
if checker.enabled(Rule::UnnecessaryIterableAllocationForFirstElement) {
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, expr);
}
}
Expr::Dict(dict) => {
if checker.any_enabled(&[
@@ -1455,7 +1452,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
generators,
elt: _,
range: _,
parenthesized: _,
},
) => {
if checker.enabled(Rule::UnnecessaryListIndexLookup) {

View File

@@ -2,14 +2,11 @@ use ruff_python_ast::Suite;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_bugbear, ruff};
use crate::rules::flake8_bugbear;
/// Run lint rules over a module.
pub(crate) fn module(suite: &Suite, checker: &mut Checker) {
if checker.enabled(Rule::FStringDocstring) {
flake8_bugbear::rules::f_string_docstring(checker, suite);
}
if checker.enabled(Rule::InvalidFormatterSuppressionComment) {
ruff::rules::ignored_formatter_suppression_comment(checker, suite);
}
}

View File

@@ -386,10 +386,10 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
},
) => {
if checker.enabled(Rule::NoClassmethodDecorator) {
pylint::rules::no_classmethod_decorator(checker, stmt);
pylint::rules::no_classmethod_decorator(checker, class_def);
}
if checker.enabled(Rule::NoStaticmethodDecorator) {
pylint::rules::no_staticmethod_decorator(checker, stmt);
pylint::rules::no_staticmethod_decorator(checker, class_def);
}
if checker.enabled(Rule::DjangoNullableModelStringField) {
flake8_django::rules::nullable_model_string_field(checker, body);
@@ -1299,9 +1299,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::IterationOverSet) {
pylint::rules::iteration_over_set(checker, iter);
}
if checker.enabled(Rule::DictIterMissingItems) {
pylint::rules::dict_iter_missing_items(checker, target, iter);
}
if checker.enabled(Rule::ManualListComprehension) {
perflint::rules::manual_list_comprehension(checker, target, body);
}
@@ -1320,9 +1317,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
pylint::rules::unnecessary_dict_index_lookup(checker, for_stmt);
}
if checker.enabled(Rule::ReadlinesInFor) {
refurb::rules::readlines_in_for(checker, for_stmt);
}
if !is_async {
if checker.enabled(Rule::ReimplementedBuiltin) {
flake8_simplify::rules::convert_for_loop_to_any_all(checker, stmt);

View File

@@ -2,16 +2,10 @@ use ruff_python_ast::StringLike;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_bandit, flake8_pyi, ruff};
use crate::rules::{flake8_bandit, flake8_pyi};
/// Run lint rules over a [`StringLike`] syntax nodes.
pub(crate) fn string_like(string_like: StringLike, checker: &mut Checker) {
if checker.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
]) {
ruff::rules::ambiguous_unicode_character_string(checker, string_like);
}
if checker.enabled(Rule::HardcodedBindAllInterfaces) {
flake8_bandit::rules::hardcoded_bind_all_interfaces(checker, string_like);
}

View File

@@ -1,4 +1,3 @@
use ruff_python_ast::StmtFunctionDef;
use ruff_python_semantic::{ScopeKind, SemanticModel};
use crate::rules::flake8_type_checking;
@@ -27,8 +26,6 @@ pub(super) enum AnnotationContext {
}
impl AnnotationContext {
/// Determine the [`AnnotationContext`] for an annotation based on the current scope of the
/// semantic model.
pub(super) fn from_model(semantic: &SemanticModel, settings: &LinterSettings) -> Self {
// If the annotation is in a class scope (e.g., an annotated assignment for a
// class field) or a function scope, and that class or function is marked as
@@ -74,23 +71,4 @@ impl AnnotationContext {
Self::TypingOnly
}
/// Determine the [`AnnotationContext`] to use for annotations in a function signature.
pub(super) fn from_function(
function_def: &StmtFunctionDef,
semantic: &SemanticModel,
settings: &LinterSettings,
) -> Self {
if flake8_type_checking::helpers::runtime_required_function(
function_def,
&settings.flake8_type_checking.runtime_required_decorators,
semantic,
) {
Self::RuntimeRequired
} else if semantic.future_annotations() {
Self::TypingOnly
} else {
Self::RuntimeEvaluated
}
}
}

View File

@@ -40,7 +40,7 @@ use ruff_diagnostics::{Diagnostic, IsolationLevel};
use ruff_notebook::{CellOffsets, NotebookIndex};
use ruff_python_ast::all::{extract_all_names, DunderAllFlags};
use ruff_python_ast::helpers::{
collect_import_from_member, extract_handled_exceptions, is_docstring_stmt, to_module_path,
collect_import_from_member, extract_handled_exceptions, to_module_path,
};
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::str::trailing_quote;
@@ -71,38 +71,6 @@ mod analyze;
mod annotation;
mod deferred;
/// State representing whether a docstring is expected or not for the next statement.
#[derive(Default, Debug, Copy, Clone, PartialEq)]
enum DocstringState {
/// The next statement is expected to be a docstring, but not necessarily so.
///
/// For example, in the following code:
///
/// ```python
/// class Foo:
/// pass
///
///
/// def bar(x, y):
/// """Docstring."""
/// return x + y
/// ```
///
/// For `Foo`, the state is expected when the checker is visiting the class
/// body but isn't going to be present. While, for `bar` function, the docstring
/// is expected and present.
#[default]
Expected,
Other,
}
impl DocstringState {
/// Returns `true` if the next statement is expected to be a docstring.
const fn is_expected(self) -> bool {
matches!(self, DocstringState::Expected)
}
}
pub(crate) struct Checker<'a> {
/// The [`Path`] to the file under analysis.
path: &'a Path,
@@ -146,8 +114,6 @@ pub(crate) struct Checker<'a> {
pub(crate) flake8_bugbear_seen: Vec<TextRange>,
/// The end offset of the last visited statement.
last_stmt_end: TextSize,
/// A state describing if a docstring is expected or not.
docstring_state: DocstringState,
}
impl<'a> Checker<'a> {
@@ -187,7 +153,6 @@ impl<'a> Checker<'a> {
cell_offsets,
notebook_index,
last_stmt_end: TextSize::default(),
docstring_state: DocstringState::default(),
}
}
}
@@ -340,16 +305,19 @@ where
self.semantic.flags -= SemanticModelFlags::IMPORT_BOUNDARY;
}
// Track whether we've seen module docstrings, non-imports, etc.
// Track whether we've seen docstrings, non-imports, etc.
match stmt {
Stmt::Expr(ast::StmtExpr { value, .. })
if !self.semantic.seen_module_docstring_boundary()
if !self
.semantic
.flags
.intersects(SemanticModelFlags::MODULE_DOCSTRING)
&& value.is_string_literal_expr() =>
{
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING_BOUNDARY;
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
}
Stmt::ImportFrom(ast::StmtImportFrom { module, names, .. }) => {
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING_BOUNDARY;
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
// Allow __future__ imports until we see a non-__future__ import.
if let Some("__future__") = module.as_deref() {
@@ -364,19 +332,17 @@ where
}
}
Stmt::Import(_) => {
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING_BOUNDARY;
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
}
_ => {
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING_BOUNDARY;
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
if !(self.semantic.seen_import_boundary()
|| helpers::is_assignment_to_a_dunder(stmt)
|| helpers::in_nested_block(self.semantic.current_statements())
|| imports::is_matplotlib_activation(stmt, self.semantic())
|| imports::is_sys_path_modification(stmt, self.semantic())
|| (self.settings.preview.is_enabled()
&& imports::is_os_environ_modification(stmt, self.semantic())))
|| imports::is_sys_path_modification(stmt, self.semantic()))
{
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
}
@@ -387,16 +353,6 @@ where
// the node.
let flags_snapshot = self.semantic.flags;
// Update the semantic model if it is in a docstring. This should be done after the
// flags snapshot to ensure that it gets reset once the statement is analyzed.
if self.docstring_state.is_expected() {
if is_docstring_stmt(stmt) {
self.semantic.flags |= SemanticModelFlags::DOCSTRING;
}
// Reset the state irrespective of whether the statement is a docstring or not.
self.docstring_state = DocstringState::Other;
}
// Step 1: Binding
match stmt {
Stmt::AugAssign(ast::StmtAugAssign {
@@ -584,8 +540,7 @@ where
// Function annotations are always evaluated at runtime, unless future annotations
// are enabled.
let annotation =
AnnotationContext::from_function(function_def, &self.semantic, self.settings);
let runtime_annotation = !self.semantic.future_annotations();
// The first parameter may be a single dispatch.
let mut singledispatch =
@@ -609,18 +564,10 @@ where
if let Some(expr) = &parameter_with_default.parameter.annotation {
if singledispatch {
self.visit_runtime_required_annotation(expr);
} else if runtime_annotation {
self.visit_runtime_evaluated_annotation(expr);
} else {
match annotation {
AnnotationContext::RuntimeRequired => {
self.visit_runtime_required_annotation(expr);
}
AnnotationContext::RuntimeEvaluated => {
self.visit_runtime_evaluated_annotation(expr);
}
AnnotationContext::TypingOnly => {
self.visit_annotation(expr);
}
}
self.visit_annotation(expr);
};
}
if let Some(expr) = &parameter_with_default.default {
@@ -630,46 +577,28 @@ where
}
if let Some(arg) = &parameters.vararg {
if let Some(expr) = &arg.annotation {
match annotation {
AnnotationContext::RuntimeRequired => {
self.visit_runtime_required_annotation(expr);
}
AnnotationContext::RuntimeEvaluated => {
self.visit_runtime_evaluated_annotation(expr);
}
AnnotationContext::TypingOnly => {
self.visit_annotation(expr);
}
}
if runtime_annotation {
self.visit_runtime_evaluated_annotation(expr);
} else {
self.visit_annotation(expr);
};
}
}
if let Some(arg) = &parameters.kwarg {
if let Some(expr) = &arg.annotation {
match annotation {
AnnotationContext::RuntimeRequired => {
self.visit_runtime_required_annotation(expr);
}
AnnotationContext::RuntimeEvaluated => {
self.visit_runtime_evaluated_annotation(expr);
}
AnnotationContext::TypingOnly => {
self.visit_annotation(expr);
}
}
if runtime_annotation {
self.visit_runtime_evaluated_annotation(expr);
} else {
self.visit_annotation(expr);
};
}
}
for expr in returns {
match annotation {
AnnotationContext::RuntimeRequired => {
self.visit_runtime_required_annotation(expr);
}
AnnotationContext::RuntimeEvaluated => {
self.visit_runtime_evaluated_annotation(expr);
}
AnnotationContext::TypingOnly => {
self.visit_annotation(expr);
}
}
if runtime_annotation {
self.visit_runtime_evaluated_annotation(expr);
} else {
self.visit_annotation(expr);
};
}
let definition = docstrings::extraction::extract_definition(
@@ -725,8 +654,6 @@ where
self.semantic.set_globals(globals);
}
// Set the docstring state before visiting the class body.
self.docstring_state = DocstringState::Expected;
self.visit_body(body);
}
Stmt::TypeAlias(ast::StmtTypeAlias {
@@ -977,7 +904,7 @@ where
range: _,
}) => {
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
if id == "locals" && ctx.is_load() {
if &**id == "locals" && ctx.is_load() {
let scope = self.semantic.current_scope_mut();
scope.set_uses_locals();
}
@@ -1007,7 +934,6 @@ where
elt,
generators,
range: _,
parenthesized: _,
}) => {
self.visit_generators(generators);
self.visit_expr(elt);
@@ -1147,7 +1073,7 @@ where
range: _,
} = keyword;
if let Some(id) = arg {
if id.as_str() == "bound" {
if &**id == "bound" {
self.visit_type_definition(value);
} else {
self.visit_non_type_definition(value);
@@ -1191,7 +1117,7 @@ where
match (arg.as_ref(), value) {
// Ex) NamedTuple("a", **{"a": int})
(None, Expr::Dict(ast::ExprDict { keys, values, .. })) => {
for (key, value) in keys.iter().zip(values) {
for (key, value) in keys.iter().zip(values.iter()) {
if let Some(key) = key.as_ref() {
self.visit_non_type_definition(key);
self.visit_type_definition(value);
@@ -1227,7 +1153,7 @@ where
for key in keys.iter().flatten() {
self.visit_non_type_definition(key);
}
for value in values {
for value in values.iter() {
self.visit_type_definition(value);
}
} else {
@@ -1328,7 +1254,6 @@ where
elts,
ctx,
range: _,
parenthesized: _,
}) = slice.as_ref()
{
let mut iter = elts.iter();
@@ -1363,16 +1288,6 @@ where
self.semantic.flags |= SemanticModelFlags::F_STRING;
visitor::walk_expr(self, expr);
}
Expr::NamedExpr(ast::ExprNamedExpr {
target,
value,
range: _,
}) => {
self.visit_expr(value);
self.semantic.flags |= SemanticModelFlags::NAMED_EXPRESSION_ASSIGNMENT;
self.visit_expr(target);
}
_ => visitor::walk_expr(self, expr),
}
@@ -1589,8 +1504,6 @@ impl<'a> Checker<'a> {
unreachable!("Generator expression must contain at least one generator");
};
let flags = self.semantic.flags;
// Generators are compiled as nested functions. (This may change with PEP 709.)
// As such, the `iter` of the first generator is evaluated in the outer scope, while all
// subsequent nodes are evaluated in the inner scope.
@@ -1620,22 +1533,14 @@ impl<'a> Checker<'a> {
// `x` is local to `foo`, and the `T` in `y=T` skips the class scope when resolving.
self.visit_expr(&generator.iter);
self.semantic.push_scope(ScopeKind::Generator);
self.semantic.flags = flags | SemanticModelFlags::COMPREHENSION_ASSIGNMENT;
self.visit_expr(&generator.target);
self.semantic.flags = flags;
for expr in &generator.ifs {
self.visit_boolean_test(expr);
}
for generator in iterator {
self.visit_expr(&generator.iter);
self.semantic.flags = flags | SemanticModelFlags::COMPREHENSION_ASSIGNMENT;
self.visit_expr(&generator.target);
self.semantic.flags = flags;
for expr in &generator.ifs {
self.visit_boolean_test(expr);
}
@@ -1834,21 +1739,11 @@ impl<'a> Checker<'a> {
return;
}
// A binding within a `for` must be a loop variable, as in:
// ```python
// for x in range(10):
// ...
// ```
if parent.is_for_stmt() {
self.add_binding(id, expr.range(), BindingKind::LoopVar, flags);
return;
}
// A binding within a `with` must be an item, as in:
// ```python
// with open("file.txt") as fp:
// ...
// ```
if parent.is_with_stmt() {
self.add_binding(id, expr.range(), BindingKind::WithItemVar, flags);
return;
@@ -1860,21 +1755,21 @@ impl<'a> Checker<'a> {
&& match parent {
Stmt::Assign(ast::StmtAssign { targets, .. }) => {
if let Some(Expr::Name(ast::ExprName { id, .. })) = targets.first() {
id == "__all__"
&**id == "__all__"
} else {
false
}
}
Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => {
if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
id == "__all__"
&**id == "__all__"
} else {
false
}
}
Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => {
if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
id == "__all__"
&**id == "__all__"
} else {
false
}
@@ -1904,26 +1799,17 @@ impl<'a> Checker<'a> {
}
// If the expression is the left-hand side of a walrus operator, then it's a named
// expression assignment, as in:
// ```python
// if (x := 10) > 5:
// ...
// ```
if self.semantic.in_named_expression_assignment() {
// expression assignment.
if self
.semantic
.current_expressions()
.filter_map(Expr::as_named_expr_expr)
.any(|parent| parent.target.as_ref() == expr)
{
self.add_binding(id, expr.range(), BindingKind::NamedExprAssignment, flags);
return;
}
// If the expression is part of a comprehension target, then it's a comprehension variable
// assignment, as in:
// ```python
// [x for x in range(10)]
// ```
if self.semantic.in_comprehension_assignment() {
self.add_binding(id, expr.range(), BindingKind::ComprehensionVar, flags);
return;
}
self.add_binding(id, expr.range(), BindingKind::Assignment, flags);
}
@@ -2039,8 +1925,6 @@ impl<'a> Checker<'a> {
};
self.visit_parameters(parameters);
// Set the docstring state before visiting the function body.
self.docstring_state = DocstringState::Expected;
self.visit_body(body);
}
}

View File

@@ -46,7 +46,7 @@ fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) ->
}) => {
let level = level.unwrap_or_default() as usize;
let module = if let Some(module) = module {
let module: &String = module.as_ref();
let module: &str = module.as_str();
if level == 0 {
Cow::Borrowed(module)
} else {

View File

@@ -3,13 +3,12 @@
use std::path::Path;
use itertools::Itertools;
use ruff_text_size::Ranged;
use ruff_text_size::{Ranged, TextLen, TextRange};
use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_python_trivia::CommentRanges;
use ruff_python_trivia::{CommentRanges, PythonWhitespace};
use ruff_source_file::Locator;
use crate::fix::edits::delete_comment;
use crate::noqa;
use crate::noqa::{Directive, FileExemption, NoqaDirectives, NoqaMapping};
use crate::registry::{AsRule, Rule, RuleSet};
@@ -112,8 +111,7 @@ pub(crate) fn check_noqa(
if line.matches.is_empty() {
let mut diagnostic =
Diagnostic::new(UnusedNOQA { codes: None }, directive.range());
diagnostic
.set_fix(Fix::safe_edit(delete_comment(directive.range(), locator)));
diagnostic.set_fix(Fix::safe_edit(delete_noqa(directive.range(), locator)));
diagnostics.push(diagnostic);
}
@@ -179,10 +177,8 @@ pub(crate) fn check_noqa(
directive.range(),
);
if valid_codes.is_empty() {
diagnostic.set_fix(Fix::safe_edit(delete_comment(
directive.range(),
locator,
)));
diagnostic
.set_fix(Fix::safe_edit(delete_noqa(directive.range(), locator)));
} else {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("# noqa: {}", valid_codes.join(", ")),
@@ -199,3 +195,48 @@ pub(crate) fn check_noqa(
ignored_diagnostics.sort_unstable();
ignored_diagnostics
}
/// Generate a [`Edit`] to delete a `noqa` directive.
fn delete_noqa(range: TextRange, locator: &Locator) -> Edit {
let line_range = locator.line_range(range.start());
// Compute the leading space.
let prefix = locator.slice(TextRange::new(line_range.start(), range.start()));
let leading_space_len = prefix.text_len() - prefix.trim_whitespace_end().text_len();
// Compute the trailing space.
let suffix = locator.slice(TextRange::new(range.end(), line_range.end()));
let trailing_space_len = suffix.text_len() - suffix.trim_whitespace_start().text_len();
// Ex) `# noqa`
if line_range
== TextRange::new(
range.start() - leading_space_len,
range.end() + trailing_space_len,
)
{
let full_line_end = locator.full_line_end(line_range.end());
Edit::deletion(line_range.start(), full_line_end)
}
// Ex) `x = 1 # noqa`
else if range.end() + trailing_space_len == line_range.end() {
Edit::deletion(range.start() - leading_space_len, line_range.end())
}
// Ex) `x = 1 # noqa # type: ignore`
else if locator
.slice(TextRange::new(
range.end() + trailing_space_len,
line_range.end(),
))
.starts_with('#')
{
Edit::deletion(range.start(), range.end() + trailing_space_len)
}
// Ex) `x = 1 # noqa here`
else {
Edit::deletion(
range.start() + "# ".text_len(),
range.end() + trailing_space_len,
)
}
}

View File

@@ -6,14 +6,17 @@ use ruff_notebook::CellOffsets;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::Tok;
use ruff_diagnostics::Diagnostic;
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
use crate::directives::TodoComment;
use crate::lex::docstring_detection::StateMachine;
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::rules::BlankLinesChecker;
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
flake8_pyi, flake8_quotes, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
@@ -63,15 +66,31 @@ pub(crate) fn check_tokens(
pylint::rules::empty_comments(&mut diagnostics, indexer, locator);
}
if settings
.rules
.enabled(Rule::AmbiguousUnicodeCharacterComment)
{
for range in indexer.comment_ranges() {
ruff::rules::ambiguous_unicode_character_comment(
if settings.rules.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
Rule::AmbiguousUnicodeCharacterComment,
]) {
let mut state_machine = StateMachine::default();
for &(ref tok, range) in tokens.iter().flatten() {
let is_docstring = state_machine.consume(tok);
let context = match tok {
Tok::String { .. } => {
if is_docstring {
Context::Docstring
} else {
Context::String
}
}
Tok::FStringMiddle { .. } => Context::String,
Tok::Comment(_) => Context::Comment,
_ => continue,
};
ruff::rules::ambiguous_unicode_character(
&mut diagnostics,
locator,
*range,
range,
context,
settings,
);
}

View File

@@ -244,7 +244,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "E0643") => (RuleGroup::Preview, rules::pylint::rules::PotentialIndexError),
(Pylint, "E0704") => (RuleGroup::Preview, rules::pylint::rules::MisplacedBareRaise),
(Pylint, "E1132") => (RuleGroup::Preview, rules::pylint::rules::RepeatedKeywordArgument),
(Pylint, "E1141") => (RuleGroup::Preview, rules::pylint::rules::DictIterMissingItems),
(Pylint, "E1142") => (RuleGroup::Stable, rules::pylint::rules::AwaitOutsideAsync),
(Pylint, "E1205") => (RuleGroup::Stable, rules::pylint::rules::LoggingTooManyArgs),
(Pylint, "E1206") => (RuleGroup::Stable, rules::pylint::rules::LoggingTooFewArgs),
@@ -655,7 +654,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Bandit, "407") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousXmlExpatImport),
(Flake8Bandit, "408") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousXmlMinidomImport),
(Flake8Bandit, "409") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousXmlPulldomImport),
(Flake8Bandit, "410") => (RuleGroup::Removed, rules::flake8_bandit::rules::SuspiciousLxmlImport),
(Flake8Bandit, "410") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousLxmlImport),
(Flake8Bandit, "411") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousXmlrpcImport),
(Flake8Bandit, "412") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousHttpoxyImport),
(Flake8Bandit, "413") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousPycryptoImport),
@@ -945,7 +944,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "025") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryDictComprehensionForIterable),
(Ruff, "026") => (RuleGroup::Preview, rules::ruff::rules::DefaultFactoryKwarg),
(Ruff, "027") => (RuleGroup::Preview, rules::ruff::rules::MissingFStringSyntax),
(Ruff, "028") => (RuleGroup::Preview, rules::ruff::rules::InvalidFormatterSuppressionComment),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
(Ruff, "200") => (RuleGroup::Stable, rules::ruff::rules::InvalidPyprojectToml),
#[cfg(feature = "test-rules")]
@@ -1027,7 +1025,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
#[allow(deprecated)]
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
(Refurb, "118") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedOperator),
(Refurb, "129") => (RuleGroup::Preview, rules::refurb::rules::ReadlinesInFor),
#[allow(deprecated)]
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
#[allow(deprecated)]

View File

@@ -62,51 +62,6 @@ pub(crate) fn delete_stmt(
}
}
/// Generate a [`Edit`] to delete a comment (for example: a `noqa` directive).
pub(crate) fn delete_comment(range: TextRange, locator: &Locator) -> Edit {
let line_range = locator.line_range(range.start());
// Compute the leading space.
let prefix = locator.slice(TextRange::new(line_range.start(), range.start()));
let leading_space_len = prefix.text_len() - prefix.trim_whitespace_end().text_len();
// Compute the trailing space.
let suffix = locator.slice(TextRange::new(range.end(), line_range.end()));
let trailing_space_len = suffix.text_len() - suffix.trim_whitespace_start().text_len();
// Ex) `# noqa`
if line_range
== TextRange::new(
range.start() - leading_space_len,
range.end() + trailing_space_len,
)
{
let full_line_end = locator.full_line_end(line_range.end());
Edit::deletion(line_range.start(), full_line_end)
}
// Ex) `x = 1 # noqa`
else if range.end() + trailing_space_len == line_range.end() {
Edit::deletion(range.start() - leading_space_len, line_range.end())
}
// Ex) `x = 1 # noqa # type: ignore`
else if locator
.slice(TextRange::new(
range.end() + trailing_space_len,
line_range.end(),
))
.starts_with('#')
{
Edit::deletion(range.start(), range.end() + trailing_space_len)
}
// Ex) `x = 1 # noqa here`
else {
Edit::deletion(
range.start() + "# ".text_len(),
range.end() + trailing_space_len,
)
}
}
/// Generate a `Fix` to remove the specified imports from an `import` statement.
pub(crate) fn remove_unused_imports<'a>(
member_names: impl Iterator<Item = &'a str>,

View File

@@ -256,6 +256,8 @@ impl Rule {
| Rule::MixedSpacesAndTabs
| Rule::TrailingWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
| Rule::AvoidableEscapedQuote
| Rule::BadQuotesDocstring
| Rule::BadQuotesInlineString

View File

@@ -248,7 +248,6 @@ impl Renamer {
| BindingKind::Assignment
| BindingKind::BoundException
| BindingKind::LoopVar
| BindingKind::ComprehensionVar
| BindingKind::WithItemVar
| BindingKind::Global
| BindingKind::Nonlocal(_)

View File

@@ -81,7 +81,7 @@ pub(crate) fn variable_name_task_id(
let ast::ExprStringLiteral { value: task_id, .. } = keyword.value.as_string_literal_expr()?;
// If the target name is the same as the task_id, no violation.
if task_id == id {
if task_id == &**id {
return None;
}

View File

@@ -26,7 +26,7 @@ static HASH_NUMBER: Lazy<Regex> = Lazy::new(|| Regex::new(r"#\d").unwrap());
static POSITIVE_CASES: Lazy<RegexSet> = Lazy::new(|| {
RegexSet::new([
// Keywords
r"^(?:elif\s+.*\s*:.*|else\s*:.*|try\s*:.*|finally\s*:.*|except.*:.*|case\s+.*\s*:.*)$",
r"^(?:elif\s+.*\s*:|else\s*:|try\s*:|finally\s*:|except\s+.*\s*:)$",
// Partial dictionary
r#"^['"]\w+['"]\s*:.+[,{]\s*(#.*)?$"#,
// Multiline assignment
@@ -147,27 +147,6 @@ mod tests {
assert!(!comment_contains_code("#to print", &[]));
}
#[test]
fn comment_contains_code_single_line() {
assert!(comment_contains_code("# case 1: print()", &[]));
assert!(comment_contains_code("# try: get(1, 2, 3)", &[]));
assert!(comment_contains_code("# else: print()", &[]));
assert!(comment_contains_code("# elif x == 10: print()", &[]));
assert!(comment_contains_code(
"# except Exception as e: print(e)",
&[]
));
assert!(comment_contains_code("# except: print()", &[]));
assert!(comment_contains_code("# finally: close_handle()", &[]));
assert!(!comment_contains_code("# try: use cache", &[]));
assert!(!comment_contains_code("# else: we should return", &[]));
assert!(!comment_contains_code(
"# call function except: without cache",
&[]
));
}
#[test]
fn comment_contains_code_with_multiline() {
assert!(comment_contains_code("#else:", &[]));
@@ -176,15 +155,11 @@ mod tests {
assert!(comment_contains_code("#elif True:", &[]));
assert!(comment_contains_code("#x = foo(", &[]));
assert!(comment_contains_code("#except Exception:", &[]));
assert!(comment_contains_code("# case 1:", &[]));
assert!(comment_contains_code("#case 1:", &[]));
assert!(comment_contains_code("# try:", &[]));
assert!(!comment_contains_code("# this is = to that :(", &[]));
assert!(!comment_contains_code("#else", &[]));
assert!(!comment_contains_code("#or else:", &[]));
assert!(!comment_contains_code("#else True:", &[]));
assert!(!comment_contains_code("# in that case:", &[]));
// Unpacking assignments
assert!(comment_contains_code(

View File

@@ -47,8 +47,7 @@ fn is_standalone_comment(line: &str) -> bool {
for char in line.chars() {
if char == '#' {
return true;
}
if !char.is_whitespace() {
} else if !char.is_whitespace() {
return false;
}
}

View File

@@ -148,132 +148,4 @@ ERA001.py:27:5: ERA001 Found commented-out code
29 28 |
30 29 | #import os # noqa
ERA001.py:32:1: ERA001 Found commented-out code
|
30 | #import os # noqa
31 |
32 | # case 1:
| ^^^^^^^^^ ERA001
33 | # try:
34 | # try: # with comment
|
= help: Remove commented-out code
Display-only fix
29 29 |
30 30 | #import os # noqa
31 31 |
32 |-# case 1:
33 32 | # try:
34 33 | # try: # with comment
35 34 | # try: print()
ERA001.py:33:1: ERA001 Found commented-out code
|
32 | # case 1:
33 | # try:
| ^^^^^^ ERA001
34 | # try: # with comment
35 | # try: print()
|
= help: Remove commented-out code
Display-only fix
30 30 | #import os # noqa
31 31 |
32 32 | # case 1:
33 |-# try:
34 33 | # try: # with comment
35 34 | # try: print()
36 35 | # except:
ERA001.py:34:1: ERA001 Found commented-out code
|
32 | # case 1:
33 | # try:
34 | # try: # with comment
| ^^^^^^^^^^^^^^^^^^^^^^ ERA001
35 | # try: print()
36 | # except:
|
= help: Remove commented-out code
Display-only fix
31 31 |
32 32 | # case 1:
33 33 | # try:
34 |-# try: # with comment
35 34 | # try: print()
36 35 | # except:
37 36 | # except Foo:
ERA001.py:35:1: ERA001 Found commented-out code
|
33 | # try:
34 | # try: # with comment
35 | # try: print()
| ^^^^^^^^^^^^^^ ERA001
36 | # except:
37 | # except Foo:
|
= help: Remove commented-out code
Display-only fix
32 32 | # case 1:
33 33 | # try:
34 34 | # try: # with comment
35 |-# try: print()
36 35 | # except:
37 36 | # except Foo:
38 37 | # except Exception as e: print(e)
ERA001.py:36:1: ERA001 Found commented-out code
|
34 | # try: # with comment
35 | # try: print()
36 | # except:
| ^^^^^^^^^ ERA001
37 | # except Foo:
38 | # except Exception as e: print(e)
|
= help: Remove commented-out code
Display-only fix
33 33 | # try:
34 34 | # try: # with comment
35 35 | # try: print()
36 |-# except:
37 36 | # except Foo:
38 37 | # except Exception as e: print(e)
ERA001.py:37:1: ERA001 Found commented-out code
|
35 | # try: print()
36 | # except:
37 | # except Foo:
| ^^^^^^^^^^^^^ ERA001
38 | # except Exception as e: print(e)
|
= help: Remove commented-out code
Display-only fix
34 34 | # try: # with comment
35 35 | # try: print()
36 36 | # except:
37 |-# except Foo:
38 37 | # except Exception as e: print(e)
ERA001.py:38:1: ERA001 Found commented-out code
|
36 | # except:
37 | # except Foo:
38 | # except Exception as e: print(e)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ERA001
|
= help: Remove commented-out code
Display-only fix
35 35 | # try: print()
36 36 | # except:
37 37 | # except Foo:
38 |-# except Exception as e: print(e)

View File

@@ -137,7 +137,7 @@ impl AutoPythonType {
)
.ok()?;
let expr = Expr::Name(ast::ExprName {
id: binding,
id: binding.into_boxed_str(),
range: TextRange::default(),
ctx: ExprContext::Load,
});

View File

@@ -118,7 +118,8 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
let binding = semantic.binding(binding_id);
let Some(Expr::Call(call)) = analyze::typing::find_binding_value(binding, semantic) else {
let Some(Expr::Call(call)) = analyze::typing::find_binding_value(&name.id, binding, semantic)
else {
return false;
};

View File

@@ -52,7 +52,7 @@ impl Violation for HardcodedPasswordString {
fn password_target(target: &Expr) -> Option<&str> {
let target_name = match target {
// variable = "s3cr3t"
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
Expr::Name(ast::ExprName { id, .. }) => &**id,
// d["password"] = "s3cr3t"
Expr::Subscript(ast::ExprSubscript { slice, .. }) => match slice.as_ref() {
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.to_str(),

View File

@@ -69,7 +69,7 @@ pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCal
Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: true, .. }) => (),
Expr::Call(ast::ExprCall { func, .. }) => {
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
if id != "select_autoescape" {
if &**id != "select_autoescape" {
checker.diagnostics.push(Diagnostic::new(
Jinja2AutoescapeFalse { value: true },
keyword.range(),

View File

@@ -64,7 +64,7 @@ pub(crate) fn ssl_with_bad_defaults(checker: &mut Checker, function_def: &StmtFu
if let Some(default) = &param.default {
match default.as_ref() {
Expr::Name(ast::ExprName { id, range, .. }) => {
if is_insecure_protocol(id.as_str()) {
if is_insecure_protocol(id) {
checker.diagnostics.push(Diagnostic::new(
SslWithBadDefaults {
protocol: id.to_string(),

View File

@@ -211,14 +211,8 @@ impl Violation for SuspiciousXmlPulldomImport {
}
}
/// ## Removed
/// This rule was removed as the `lxml` library has been modified to address
/// known vulnerabilities and unsafe defaults. As such, the `defusedxml`
/// library is no longer necessary, `defusedxml` has [deprecated] its `lxml`
/// module.
///
/// ## What it does
/// Checks for imports of the `lxml` module.
/// Checks for imports of the`lxml` module.
///
/// ## Why is this bad?
/// Using various methods from the `lxml` module to parse untrusted XML data is
@@ -229,8 +223,6 @@ impl Violation for SuspiciousXmlPulldomImport {
/// ```python
/// import lxml
/// ```
///
/// [deprecated]: https://github.com/tiran/defusedxml/blob/c7445887f5e1bcea470a16f61369d29870cfcfe1/README.md#defusedxmllxml
#[violation]
pub struct SuspiciousLxmlImport;

View File

@@ -83,7 +83,7 @@ pub(crate) fn blind_except(
return;
};
if !matches!(id.as_str(), "BaseException" | "Exception") {
if !matches!(&**id, "BaseException" | "Exception") {
return;
}
@@ -96,7 +96,7 @@ pub(crate) fn blind_except(
if let Stmt::Raise(ast::StmtRaise { exc, .. }) = stmt {
if let Some(exc) = exc {
if let Expr::Name(ast::ExprName { id, .. }) = exc.as_ref() {
name.is_some_and(|name| id == name)
name.is_some_and(|name| &**id == name)
} else {
false
}

View File

@@ -45,7 +45,7 @@ pub(super) fn is_allowed_func_call(name: &str) -> bool {
/// Returns `true` if a function definition is allowed to use a boolean trap.
pub(super) fn is_allowed_func_def(name: &str) -> bool {
matches!(name, "__setitem__" | "__post_init__")
matches!(name, "__setitem__")
}
/// Returns `true` if an argument is allowed to use a boolean trap. To return
@@ -54,7 +54,7 @@ pub(super) fn is_allowed_func_def(name: &str) -> bool {
pub(super) fn allow_boolean_trap(call: &ast::ExprCall) -> bool {
let func_name = match call.func.as_ref() {
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr.as_str(),
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
Expr::Name(ast::ExprName { id, .. }) => &**id,
_ => return false,
};

View File

@@ -164,7 +164,7 @@ pub(crate) fn boolean_type_hint_positional_argument(
fn match_annotation_to_literal_bool(annotation: &Expr) -> bool {
match annotation {
// Ex) `True`
Expr::Name(name) => &name.id == "bool",
Expr::Name(name) => &*name.id == "bool",
// Ex) `"True"`
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool",
_ => false,
@@ -176,7 +176,7 @@ fn match_annotation_to_literal_bool(annotation: &Expr) -> bool {
fn match_annotation_to_complex_bool(annotation: &Expr, semantic: &SemanticModel) -> bool {
match annotation {
// Ex) `bool`
Expr::Name(name) => &name.id == "bool",
Expr::Name(name) => &*name.id == "bool",
// Ex) `"bool"`
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool",
// Ex) `bool | int`

View File

@@ -57,7 +57,7 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
ctx: ExprContext::Load,
range: TextRange::default(),
})),
arguments: Arguments {
arguments: Box::new(Arguments {
args: if let Some(msg) = msg {
Box::from([msg.clone()])
} else {
@@ -65,7 +65,7 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
},
keywords: Box::from([]),
range: TextRange::default(),
},
}),
range: TextRange::default(),
}))),
cause: None,

View File

@@ -63,7 +63,7 @@ pub(crate) fn assignment_to_os_environ(checker: &mut Checker, targets: &[Expr])
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
return;
};
if id != "os" {
if &**id != "os" {
return;
}
checker

View File

@@ -85,7 +85,7 @@ pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[De
// TODO(charlie): This should take into account `classmethod-decorators` and
// `staticmethod-decorators`.
if let Expr::Name(ast::ExprName { id, .. }) = &decorator.expression {
if id == "classmethod" || id == "staticmethod" {
if &**id == "classmethod" || &**id == "staticmethod" {
return;
}
}

View File

@@ -109,7 +109,6 @@ fn type_pattern(elts: Vec<&Expr>) -> Expr {
elts: elts.into_iter().cloned().collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
parenthesized: true,
}
.into()
}

View File

@@ -131,7 +131,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
}) => {
match func.as_ref() {
Expr::Name(ast::ExprName { id, .. }) => {
if matches!(id.as_str(), "filter" | "reduce" | "map") {
if matches!(&**id, "filter" | "reduce" | "map") {
for arg in arguments.args.iter() {
if arg.is_lambda_expr() {
self.safe_functions.push(arg);
@@ -142,7 +142,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
if attr == "reduce" {
if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() {
if id == "functools" {
if &**id == "functools" {
for arg in arguments.args.iter() {
if arg.is_lambda_expr() {
self.safe_functions.push(arg);
@@ -209,7 +209,7 @@ impl<'a> Visitor<'a> for NamesFromAssignmentsVisitor<'a> {
fn visit_expr(&mut self, expr: &'a Expr) {
match expr {
Expr::Name(ast::ExprName { id, .. }) => {
self.names.push(id.as_str());
self.names.push(id);
}
Expr::Starred(ast::ExprStarred { value, .. }) => {
self.visit_expr(value);
@@ -303,7 +303,7 @@ pub(crate) fn function_uses_loop_variable(checker: &mut Checker, node: &Node) {
// If a variable was used in a function or lambda body, and assigned in the
// loop, flag it.
for name in suspicious_variables {
if reassigned_in_loop.contains(&name.id.as_str()) {
if reassigned_in_loop.contains(&&*name.id) {
if !checker.flake8_bugbear_seen.contains(&name.range()) {
checker.flake8_bugbear_seen.push(name.range());
checker.diagnostics.push(Diagnostic::new(

View File

@@ -57,7 +57,7 @@ pub(crate) fn getattr_with_constant(
let Expr::Name(ast::ExprName { id, .. }) = func else {
return;
};
if id != "getattr" {
if &**id != "getattr" {
return;
}
let [obj, arg] = args else {

View File

@@ -2,7 +2,7 @@ use ast::call_path::{from_qualified_name, CallPath};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_docstring_stmt;
use ruff_python_ast::{self as ast, Expr, Parameter, ParameterWithDefault, Stmt};
use ruff_python_ast::{self as ast, Expr, Parameter, ParameterWithDefault};
use ruff_python_codegen::{Generator, Stylist};
use ruff_python_index::Indexer;
use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr};
@@ -152,11 +152,6 @@ fn move_initialization(
// Set the default argument value to `None`.
let default_edit = Edit::range_replacement("None".to_string(), default.range());
// If the function is a stub, this is the only necessary edit.
if is_stub(function_def) {
return Some(Fix::unsafe_edit(default_edit));
}
// Add an `if`, to set the argument to its original value if still `None`.
let mut content = String::new();
content.push_str(&format!("if {} is None:", parameter.name.as_str()));
@@ -209,20 +204,3 @@ fn move_initialization(
let initialization_edit = Edit::insertion(content, pos);
Some(Fix::unsafe_edits(default_edit, [initialization_edit]))
}
/// Returns `true` if a function has an empty body, and is therefore a stub.
///
/// A function body is considered to be empty if it contains only `pass` statements, `...` literals,
/// and docstrings.
fn is_stub(function_def: &ast::StmtFunctionDef) -> bool {
function_def.body.iter().all(|stmt| match stmt {
Stmt::Pass(_) => true,
Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
matches!(
value.as_ref(),
Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
)
}
_ => false,
})
}

View File

@@ -87,7 +87,7 @@ pub(crate) fn raise_without_from_inside_except(
if let Some(name) = name {
if exc
.as_name_expr()
.is_some_and(|ast::ExprName { id, .. }| name == id)
.is_some_and(|ast::ExprName { id, .. }| &**id == name)
{
continue;
}

View File

@@ -83,7 +83,7 @@ impl<'a> GroupNameFinder<'a> {
fn name_matches(&self, expr: &Expr) -> bool {
if let Expr::Name(ast::ExprName { id, .. }) = expr {
id == self.group_name
&**id == self.group_name
} else {
false
}

View File

@@ -71,7 +71,7 @@ pub(crate) fn setattr_with_constant(
let Expr::Name(ast::ExprName { id, .. }) = func else {
return;
};
if id != "setattr" {
if &**id != "setattr" {
return;
}
let [obj, name, value] = args else {

View File

@@ -73,7 +73,7 @@ pub(crate) fn static_key_dict_comprehension(checker: &mut Checker, dict_comp: &a
fn is_constant(key: &Expr, names: &FxHashMap<&str, &ast::ExprName>) -> bool {
match key {
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().all(|elt| is_constant(elt, names)),
Expr::Name(ast::ExprName { id, .. }) => !names.contains_key(id.as_str()),
Expr::Name(ast::ExprName { id, .. }) => !names.contains_key(&**id),
Expr::Attribute(ast::ExprAttribute { value, .. }) => is_constant(value, names),
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
is_constant(value, names) && is_constant(slice, names)

View File

@@ -54,7 +54,7 @@ pub(crate) fn unintentional_type_annotation(
}
Expr::Attribute(ast::ExprAttribute { value, .. }) => {
if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() {
if id != "self" {
if &**id != "self" {
checker
.diagnostics
.push(Diagnostic::new(UnintentionalTypeAnnotation, stmt.range()));

View File

@@ -61,7 +61,7 @@ pub(crate) fn unreliable_callable_check(
let Expr::Name(ast::ExprName { id, .. }) = func else {
return;
};
if !matches!(id.as_str(), "hasattr" | "getattr") {
if !matches!(&**id, "hasattr" | "getattr") {
return;
}
let [obj, attr, ..] = args else {
@@ -75,7 +75,7 @@ pub(crate) fn unreliable_callable_check(
}
let mut diagnostic = Diagnostic::new(UnreliableCallableCheck, expr.range());
if id == "hasattr" {
if &**id == "hasattr" {
if checker.semantic().is_builtin("callable") {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("callable({})", checker.locator().slice(obj)),

View File

@@ -1,7 +1,7 @@
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Arguments, Expr};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
@@ -53,7 +53,7 @@ impl AlwaysFixableViolation for ZipWithoutExplicitStrict {
/// B905
pub(crate) fn zip_without_explicit_strict(checker: &mut Checker, call: &ast::ExprCall) {
if let Expr::Name(ast::ExprName { id, .. }) = call.func.as_ref() {
if id == "zip"
if &**id == "zip"
&& checker.semantic().is_builtin("zip")
&& call.arguments.find_keyword("strict").is_none()
&& !call
@@ -91,9 +91,7 @@ pub(crate) fn zip_without_explicit_strict(checker: &mut Checker, call: &ast::Exp
/// `itertools.cycle` or similar).
fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
let Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, keywords, .. },
..
func, arguments, ..
}) = &arg
else {
return false;
@@ -104,17 +102,17 @@ fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
["itertools", "cycle" | "count"] => true,
["itertools", "repeat"] => {
// Ex) `itertools.repeat(1)`
if keywords.is_empty() && args.len() == 1 {
if arguments.keywords.is_empty() && arguments.args.len() == 1 {
return true;
}
// Ex) `itertools.repeat(1, None)`
if args.len() == 2 && args[1].is_none_literal_expr() {
if arguments.args.len() == 2 && arguments.args[1].is_none_literal_expr() {
return true;
}
// Ex) `iterools.repeat(1, times=None)`
for keyword in keywords.iter() {
for keyword in arguments.keywords.iter() {
if keyword.arg.as_ref().is_some_and(|name| name == "times") {
if keyword.value.is_none_literal_expr() {
return true;

View File

@@ -19,3 +19,8 @@ B006_1.py:3:22: B006 [*] Do not use mutable data structures for argument default
3 |+def foobar(foor, bar=None):
4 4 | """
5 5 | """
6 |+
7 |+ if bar is None:
8 |+ bar = {}

View File

@@ -19,4 +19,9 @@ B006_2.py:4:22: B006 [*] Do not use mutable data structures for argument default
4 |-def foobar(foor, bar={}):
4 |+def foobar(foor, bar=None):
5 5 | """
6 6 | """
6 |- """
6 |+ """
7 |+ if bar is None:
8 |+ bar = {}

View File

@@ -16,5 +16,10 @@ B006_3.py:4:22: B006 [*] Do not use mutable data structures for argument default
3 3 |
4 |-def foobar(foor, bar={}):
4 |+def foobar(foor, bar=None):
5 5 | """
6 6 | """
5 |+ """
5 6 | """
6 |- """
7 |+ if bar is None:
8 |+ bar = {}

View File

@@ -15,9 +15,11 @@ B006_B008.py:63:25: B006 [*] Do not use mutable data structures for argument def
62 62 |
63 |-def this_is_wrong(value=[1, 2, 3]):
63 |+def this_is_wrong(value=None):
64 64 | ...
65 65 |
66 66 |
64 |+ if value is None:
65 |+ value = [1, 2, 3]
64 66 | ...
65 67 |
66 68 |
B006_B008.py:67:30: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -33,9 +35,11 @@ B006_B008.py:67:30: B006 [*] Do not use mutable data structures for argument def
66 66 |
67 |-def this_is_also_wrong(value={}):
67 |+def this_is_also_wrong(value=None):
68 68 | ...
69 69 |
70 70 |
68 |+ if value is None:
69 |+ value = {}
68 70 | ...
69 71 |
70 72 |
B006_B008.py:73:52: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -53,9 +57,11 @@ B006_B008.py:73:52: B006 [*] Do not use mutable data structures for argument def
72 72 | @staticmethod
73 |- def this_is_also_wrong_and_more_indented(value={}):
73 |+ def this_is_also_wrong_and_more_indented(value=None):
74 74 | pass
75 75 |
76 76 |
74 |+ if value is None:
75 |+ value = {}
74 76 | pass
75 77 |
76 78 |
B006_B008.py:77:31: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -76,9 +82,11 @@ B006_B008.py:77:31: B006 [*] Do not use mutable data structures for argument def
78 |-
79 |-}):
77 |+def multiline_arg_wrong(value=None):
80 78 | ...
81 79 |
82 80 | def single_line_func_wrong(value = {}): ...
78 |+ if value is None:
79 |+ value = {}
80 80 | ...
81 81 |
82 82 | def single_line_func_wrong(value = {}): ...
B006_B008.py:82:36: B006 Do not use mutable data structures for argument defaults
|
@@ -103,9 +111,11 @@ B006_B008.py:85:20: B006 [*] Do not use mutable data structures for argument def
84 84 |
85 |-def and_this(value=set()):
85 |+def and_this(value=None):
86 86 | ...
87 87 |
88 88 |
86 |+ if value is None:
87 |+ value = set()
86 88 | ...
87 89 |
88 90 |
B006_B008.py:89:20: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -121,9 +131,11 @@ B006_B008.py:89:20: B006 [*] Do not use mutable data structures for argument def
88 88 |
89 |-def this_too(value=collections.OrderedDict()):
89 |+def this_too(value=None):
90 90 | ...
91 91 |
92 92 |
90 |+ if value is None:
91 |+ value = collections.OrderedDict()
90 92 | ...
91 93 |
92 94 |
B006_B008.py:93:32: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -139,9 +151,11 @@ B006_B008.py:93:32: B006 [*] Do not use mutable data structures for argument def
92 92 |
93 |-async def async_this_too(value=collections.defaultdict()):
93 |+async def async_this_too(value=None):
94 94 | ...
95 95 |
96 96 |
94 |+ if value is None:
95 |+ value = collections.defaultdict()
94 96 | ...
95 97 |
96 98 |
B006_B008.py:97:26: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -152,14 +166,16 @@ B006_B008.py:97:26: B006 [*] Do not use mutable data structures for argument def
= help: Replace with `None`; initialize within function
Unsafe fix
94 94 | ...
95 95 |
96 96 |
97 |-def dont_forget_me(value=collections.deque()):
97 |+def dont_forget_me(value=None):
98 98 | ...
99 99 |
100 100 |
94 94 | ...
95 95 |
96 96 |
97 |-def dont_forget_me(value=collections.deque()):
97 |+def dont_forget_me(value=None):
98 |+ if value is None:
99 |+ value = collections.deque()
98 100 | ...
99 101 |
100 102 |
B006_B008.py:102:46: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -176,9 +192,11 @@ B006_B008.py:102:46: B006 [*] Do not use mutable data structures for argument de
101 101 | # N.B. we're also flagging the function call in the comprehension
102 |-def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
102 |+def list_comprehension_also_not_okay(default=None):
103 103 | pass
104 104 |
105 105 |
103 |+ if default is None:
104 |+ default = [i ** 2 for i in range(3)]
103 105 | pass
104 106 |
105 107 |
B006_B008.py:106:46: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -194,9 +212,11 @@ B006_B008.py:106:46: B006 [*] Do not use mutable data structures for argument de
105 105 |
106 |-def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
106 |+def dict_comprehension_also_not_okay(default=None):
107 107 | pass
108 108 |
109 109 |
107 |+ if default is None:
108 |+ default = {i: i ** 2 for i in range(3)}
107 109 | pass
108 110 |
109 111 |
B006_B008.py:110:45: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -212,9 +232,11 @@ B006_B008.py:110:45: B006 [*] Do not use mutable data structures for argument de
109 109 |
110 |-def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
110 |+def set_comprehension_also_not_okay(default=None):
111 111 | pass
112 112 |
113 113 |
111 |+ if default is None:
112 |+ default = {i ** 2 for i in range(3)}
111 113 | pass
112 114 |
113 115 |
B006_B008.py:114:33: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -230,9 +252,11 @@ B006_B008.py:114:33: B006 [*] Do not use mutable data structures for argument de
113 113 |
114 |-def kwonlyargs_mutable(*, value=[]):
114 |+def kwonlyargs_mutable(*, value=None):
115 115 | ...
116 116 |
117 117 |
115 |+ if value is None:
116 |+ value = []
115 117 | ...
116 118 |
117 119 |
B006_B008.py:239:20: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -250,9 +274,11 @@ B006_B008.py:239:20: B006 [*] Do not use mutable data structures for argument de
238 238 | # We should handle arbitrary nesting of these B008.
239 |-def nested_combo(a=[float(3), dt.datetime.now()]):
239 |+def nested_combo(a=None):
240 240 | pass
241 241 |
242 242 |
240 |+ if a is None:
241 |+ a = [float(3), dt.datetime.now()]
240 242 | pass
241 243 |
242 244 |
B006_B008.py:276:27: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -273,6 +299,12 @@ B006_B008.py:276:27: B006 [*] Do not use mutable data structures for argument de
277 277 | b: Optional[Dict[int, int]] = {},
278 278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
280 280 | ):
281 |+ if a is None:
282 |+ a = []
281 283 | pass
282 284 |
283 285 |
B006_B008.py:277:35: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -294,6 +326,11 @@ B006_B008.py:277:35: B006 [*] Do not use mutable data structures for argument de
278 278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
280 280 | ):
281 |+ if b is None:
282 |+ b = {}
281 283 | pass
282 284 |
283 285 |
B006_B008.py:278:62: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -314,7 +351,11 @@ B006_B008.py:278:62: B006 [*] Do not use mutable data structures for argument de
278 |+ c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
279 279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
280 280 | ):
281 281 | pass
281 |+ if c is None:
282 |+ c = set()
281 283 | pass
282 284 |
283 285 |
B006_B008.py:279:80: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -334,8 +375,11 @@ B006_B008.py:279:80: B006 [*] Do not use mutable data structures for argument de
279 |- d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 |+ d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
280 280 | ):
281 281 | pass
282 282 |
281 |+ if d is None:
282 |+ d = set()
281 283 | pass
282 284 |
283 285 |
B006_B008.py:284:52: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -352,8 +396,11 @@ B006_B008.py:284:52: B006 [*] Do not use mutable data structures for argument de
284 |-def single_line_func_wrong(value: dict[str, str] = {}):
284 |+def single_line_func_wrong(value: dict[str, str] = None):
285 285 | """Docstring"""
286 286 |
287 287 |
286 |+ if value is None:
287 |+ value = {}
286 288 |
287 289 |
288 290 | def single_line_func_wrong(value: dict[str, str] = {}):
B006_B008.py:288:52: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -371,8 +418,11 @@ B006_B008.py:288:52: B006 [*] Do not use mutable data structures for argument de
288 |-def single_line_func_wrong(value: dict[str, str] = {}):
288 |+def single_line_func_wrong(value: dict[str, str] = None):
289 289 | """Docstring"""
290 290 | ...
291 291 |
290 |+ if value is None:
291 |+ value = {}
290 292 | ...
291 293 |
292 294 |
B006_B008.py:293:52: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -388,9 +438,11 @@ B006_B008.py:293:52: B006 [*] Do not use mutable data structures for argument de
292 292 |
293 |-def single_line_func_wrong(value: dict[str, str] = {}):
293 |+def single_line_func_wrong(value: dict[str, str] = None):
294 294 | """Docstring"""; ...
295 295 |
296 296 |
294 |+ if value is None:
295 |+ value = {}
294 296 | """Docstring"""; ...
295 297 |
296 298 |
B006_B008.py:297:52: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -407,9 +459,11 @@ B006_B008.py:297:52: B006 [*] Do not use mutable data structures for argument de
296 296 |
297 |-def single_line_func_wrong(value: dict[str, str] = {}):
297 |+def single_line_func_wrong(value: dict[str, str] = None):
298 298 | """Docstring"""; \
299 299 | ...
300 300 |
298 |+ if value is None:
299 |+ value = {}
298 300 | """Docstring"""; \
299 301 | ...
300 302 |
B006_B008.py:302:52: B006 [*] Do not use mutable data structures for argument defaults
|
@@ -431,8 +485,11 @@ B006_B008.py:302:52: B006 [*] Do not use mutable data structures for argument de
304 |-}):
302 |+def single_line_func_wrong(value: dict[str, str] = None):
305 303 | """Docstring"""
306 304 |
307 305 |
304 |+ if value is None:
305 |+ value = {}
306 306 |
307 307 |
308 308 | def single_line_func_wrong(value: dict[str, str] = {}) \
B006_B008.py:308:52: B006 Do not use mutable data structures for argument defaults
|
@@ -456,5 +513,10 @@ B006_B008.py:313:52: B006 [*] Do not use mutable data structures for argument de
311 311 |
312 312 |
313 |-def single_line_func_wrong(value: dict[str, str] = {}):
314 |- """Docstring without newline"""
313 |+def single_line_func_wrong(value: dict[str, str] = None):
314 314 | """Docstring without newline"""
314 |+ """Docstring without newline"""
315 |+ if value is None:
316 |+ value = {}

View File

@@ -15,4 +15,8 @@ B006_extended.py:17:55: B006 [*] Do not use mutable data structures for argument
16 16 |
17 |-def error_due_to_missing_import(foo: ImmutableTypeA = []):
17 |+def error_due_to_missing_import(foo: ImmutableTypeA = None):
18 18 | ...
18 |+ if foo is None:
19 |+ foo = []
18 20 | ...

View File

@@ -29,7 +29,7 @@ enum TokenType {
/// Simplified token specialized for the task.
#[derive(Copy, Clone)]
struct Token {
ty: TokenType,
r#type: TokenType,
range: TextRange,
}
@@ -40,13 +40,13 @@ impl Ranged for Token {
}
impl Token {
fn new(ty: TokenType, range: TextRange) -> Self {
Self { ty, range }
fn new(r#type: TokenType, range: TextRange) -> Self {
Self { r#type, range }
}
fn irrelevant() -> Token {
Token {
ty: TokenType::Irrelevant,
r#type: TokenType::Irrelevant,
range: TextRange::default(),
}
}
@@ -54,7 +54,7 @@ impl Token {
impl From<(&Tok, TextRange)> for Token {
fn from((tok, range): (&Tok, TextRange)) -> Self {
let ty = match tok {
let r#type = match tok {
Tok::Name { .. } => TokenType::Named,
Tok::String { .. } => TokenType::String,
Tok::Newline => TokenType::Newline,
@@ -75,7 +75,7 @@ impl From<(&Tok, TextRange)> for Token {
_ => TokenType::Irrelevant,
};
#[allow(clippy::inconsistent_struct_constructor)]
Self { range, ty }
Self { range, r#type }
}
}
@@ -102,13 +102,16 @@ enum ContextType {
/// Comma context - described a comma-delimited "situation".
#[derive(Copy, Clone)]
struct Context {
ty: ContextType,
r#type: ContextType,
num_commas: u32,
}
impl Context {
const fn new(ty: ContextType) -> Self {
Self { ty, num_commas: 0 }
const fn new(r#type: ContextType) -> Self {
Self {
r#type,
num_commas: 0,
}
}
fn inc(&mut self) {
@@ -274,7 +277,9 @@ pub(crate) fn trailing_commas(
let mut stack = vec![Context::new(ContextType::No)];
for token in tokens {
if prev.ty == TokenType::NonLogicalNewline && token.ty == TokenType::NonLogicalNewline {
if prev.r#type == TokenType::NonLogicalNewline
&& token.r#type == TokenType::NonLogicalNewline
{
// Collapse consecutive newlines to the first one -- trailing commas are
// added before the first newline.
continue;
@@ -283,18 +288,87 @@ pub(crate) fn trailing_commas(
// Update the comma context stack.
let context = update_context(token, prev, prev_prev, &mut stack);
if let Some(diagnostic) = check_token(token, prev, prev_prev, context, locator) {
// Is it allowed to have a trailing comma before this token?
let comma_allowed = token.r#type == TokenType::ClosingBracket
&& match context.r#type {
ContextType::No => false,
ContextType::FunctionParameters => true,
ContextType::CallArguments => true,
// `(1)` is not equivalent to `(1,)`.
ContextType::Tuple => context.num_commas != 0,
// `x[1]` is not equivalent to `x[1,]`.
ContextType::Subscript => context.num_commas != 0,
ContextType::List => true,
ContextType::Dict => true,
// Lambdas are required to be a single line, trailing comma never makes sense.
ContextType::LambdaParameters => false,
};
// Is prev a prohibited trailing comma?
let comma_prohibited = prev.r#type == TokenType::Comma && {
// Is `(1,)` or `x[1,]`?
let is_singleton_tuplish =
matches!(context.r#type, ContextType::Subscript | ContextType::Tuple)
&& context.num_commas <= 1;
// There was no non-logical newline, so prohibit (except in `(1,)` or `x[1,]`).
if comma_allowed && !is_singleton_tuplish {
true
// Lambdas not handled by comma_allowed so handle it specially.
} else {
context.r#type == ContextType::LambdaParameters && token.r#type == TokenType::Colon
}
};
if comma_prohibited {
let mut diagnostic = Diagnostic::new(ProhibitedTrailingComma, prev.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range())));
diagnostics.push(diagnostic);
}
// Is prev a prohibited trailing comma on a bare tuple?
// Approximation: any comma followed by a statement-ending newline.
let bare_comma_prohibited =
prev.r#type == TokenType::Comma && token.r#type == TokenType::Newline;
if bare_comma_prohibited {
diagnostics.push(Diagnostic::new(TrailingCommaOnBareTuple, prev.range()));
}
// Comma is required if:
// - It is allowed,
// - Followed by a newline,
// - Not already present,
// - Not on an empty (), {}, [].
let comma_required = comma_allowed
&& prev.r#type == TokenType::NonLogicalNewline
&& !matches!(
prev_prev.r#type,
TokenType::Comma
| TokenType::OpeningBracket
| TokenType::OpeningSquareBracket
| TokenType::OpeningCurlyBracket
);
if comma_required {
let mut diagnostic =
Diagnostic::new(MissingTrailingComma, TextRange::empty(prev_prev.end()));
// Create a replacement that includes the final bracket (or other token),
// rather than just inserting a comma at the end. This prevents the UP034 fix
// removing any brackets in the same linter pass - doing both at the same time could
// lead to a syntax error.
let contents = locator.slice(prev_prev.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("{contents},"),
prev_prev.range(),
)));
diagnostics.push(diagnostic);
}
// Pop the current context if the current token ended it.
// The top context is never popped (if unbalanced closing brackets).
let pop_context = match context.ty {
let pop_context = match context.r#type {
// Lambda terminated by `:`.
ContextType::LambdaParameters => token.ty == TokenType::Colon,
ContextType::LambdaParameters => token.r#type == TokenType::Colon,
// All others terminated by a closing bracket.
// flake8-commas doesn't verify that it matches the opening...
_ => token.ty == TokenType::ClosingBracket,
_ => token.r#type == TokenType::ClosingBracket,
};
if pop_context && stack.len() > 1 {
stack.pop();
@@ -305,107 +379,21 @@ pub(crate) fn trailing_commas(
}
}
fn check_token(
token: Token,
prev: Token,
prev_prev: Token,
context: Context,
locator: &Locator,
) -> Option<Diagnostic> {
// Is it allowed to have a trailing comma before this token?
let comma_allowed = token.ty == TokenType::ClosingBracket
&& match context.ty {
ContextType::No => false,
ContextType::FunctionParameters => true,
ContextType::CallArguments => true,
// `(1)` is not equivalent to `(1,)`.
ContextType::Tuple => context.num_commas != 0,
// `x[1]` is not equivalent to `x[1,]`.
ContextType::Subscript => context.num_commas != 0,
ContextType::List => true,
ContextType::Dict => true,
// Lambdas are required to be a single line, trailing comma never makes sense.
ContextType::LambdaParameters => false,
};
// Is prev a prohibited trailing comma?
let comma_prohibited = prev.ty == TokenType::Comma && {
// Is `(1,)` or `x[1,]`?
let is_singleton_tuplish =
matches!(context.ty, ContextType::Subscript | ContextType::Tuple)
&& context.num_commas <= 1;
// There was no non-logical newline, so prohibit (except in `(1,)` or `x[1,]`).
if comma_allowed && !is_singleton_tuplish {
true
// Lambdas not handled by comma_allowed so handle it specially.
} else {
context.ty == ContextType::LambdaParameters && token.ty == TokenType::Colon
}
};
if comma_prohibited {
let mut diagnostic = Diagnostic::new(ProhibitedTrailingComma, prev.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range())));
return Some(diagnostic);
}
// Is prev a prohibited trailing comma on a bare tuple?
// Approximation: any comma followed by a statement-ending newline.
let bare_comma_prohibited = prev.ty == TokenType::Comma && token.ty == TokenType::Newline;
if bare_comma_prohibited {
return Some(Diagnostic::new(TrailingCommaOnBareTuple, prev.range()));
}
if !comma_allowed {
return None;
}
// Comma is required if:
// - It is allowed,
// - Followed by a newline,
// - Not already present,
// - Not on an empty (), {}, [].
let comma_required = prev.ty == TokenType::NonLogicalNewline
&& !matches!(
prev_prev.ty,
TokenType::Comma
| TokenType::OpeningBracket
| TokenType::OpeningSquareBracket
| TokenType::OpeningCurlyBracket
);
if comma_required {
let mut diagnostic =
Diagnostic::new(MissingTrailingComma, TextRange::empty(prev_prev.end()));
// Create a replacement that includes the final bracket (or other token),
// rather than just inserting a comma at the end. This prevents the UP034 fix
// removing any brackets in the same linter pass - doing both at the same time could
// lead to a syntax error.
let contents = locator.slice(prev_prev.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("{contents},"),
prev_prev.range(),
)));
Some(diagnostic)
} else {
None
}
}
fn update_context(
token: Token,
prev: Token,
prev_prev: Token,
stack: &mut Vec<Context>,
) -> Context {
let new_context = match token.ty {
TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) {
let new_context = match token.r#type {
TokenType::OpeningBracket => match (prev.r#type, prev_prev.r#type) {
(TokenType::Named, TokenType::Def) => Context::new(ContextType::FunctionParameters),
(TokenType::Named | TokenType::ClosingBracket, _) => {
Context::new(ContextType::CallArguments)
}
_ => Context::new(ContextType::Tuple),
},
TokenType::OpeningSquareBracket => match prev.ty {
TokenType::OpeningSquareBracket => match prev.r#type {
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
Context::new(ContextType::Subscript)
}

View File

@@ -13,7 +13,7 @@ pub(super) fn exactly_one_argument_with_matching_function<'a>(
return None;
}
let func = func.as_name_expr()?;
if func.id != name {
if &*func.id != name {
return None;
}
Some(arg)
@@ -24,7 +24,7 @@ pub(super) fn first_argument_with_matching_function<'a>(
func: &Expr,
args: &'a [Expr],
) -> Option<&'a Expr> {
if func.as_name_expr().is_some_and(|func| func.id == name) {
if func.as_name_expr().is_some_and(|func| &*func.id == name) {
args.first()
} else {
None

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