Compare commits

..

1 Commits

Author SHA1 Message Date
konstin
7f97547b5f Add increment/decrement 2024-03-14 16:56:06 +01:00
1699 changed files with 116901 additions and 144993 deletions

2
.gitattributes vendored
View File

@@ -8,7 +8,5 @@ crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py text eol=crlf
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf
crates/ruff_python_parser/resources/inline linguist-generated=true
ruff.schema.json linguist-generated=true text=auto eol=lf ruff.schema.json linguist-generated=true text=auto eol=lf
*.md.snap linguist-language=Markdown *.md.snap linguist-language=Markdown

8
.github/CODEOWNERS vendored
View File

@@ -5,13 +5,11 @@
# - The '*' pattern is global owners. # - The '*' pattern is global owners.
# - Order is important. The last matching pattern has the most precedence. # - Order is important. The last matching pattern has the most precedence.
/crates/ruff_notebook/ @dhruvmanila # Jupyter
/crates/ruff_linter/src/jupyter/ @dhruvmanila
/crates/ruff_formatter/ @MichaReiser /crates/ruff_formatter/ @MichaReiser
/crates/ruff_python_formatter/ @MichaReiser /crates/ruff_python_formatter/ @MichaReiser
/crates/ruff_python_parser/ @MichaReiser @dhruvmanila /crates/ruff_python_parser/ @MichaReiser
# flake8-pyi # flake8-pyi
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood /crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood
# Script for fuzzing the parser
/scripts/fuzz-parser/ @AlexWaygood

21
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels: ["internal"]
groups:
actions:
patterns:
- "*"
ignore:
# The latest versions of these are not compatible with our release workflow
- dependency-name: "actions/upload-artifact"
- dependency-name: "actions/download-artifact"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
labels: ["internal"]

View File

@@ -1,68 +0,0 @@
{
$schema: "https://docs.renovatebot.com/renovate-schema.json",
dependencyDashboard: true,
suppressNotifications: ["prEditedNotification"],
extends: ["config:recommended"],
labels: ["internal"],
schedule: ["before 4am on Monday"],
semanticCommits: "disabled",
separateMajorMinor: false,
prHourlyLimit: 10,
enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "npm"],
cargo: {
// See https://docs.renovatebot.com/configuration-options/#rangestrategy
rangeStrategy: "update-lockfile",
},
pep621: {
fileMatch: ["^(python|scripts)/.*pyproject\\.toml$"],
},
npm: {
fileMatch: ["^playground/.*package\\.json$"],
},
"pre-commit": {
enabled: true,
},
packageRules: [
{
// Group upload/download artifact updates, the versions are dependent
groupName: "Artifact GitHub Actions dependencies",
matchManagers: ["github-actions"],
matchPackagePatterns: ["actions/.*-artifact"],
description: "Weekly update of artifact-related GitHub Actions dependencies",
},
{
groupName: "pre-commit dependencies",
matchManagers: ["pre-commit"],
description: "Weekly update of pre-commit dependencies",
},
{
groupName: "NPM Development dependencies",
matchManagers: ["npm"],
matchDepTypes: ["devDependencies"],
description: "Weekly update of NPM development dependencies",
},
{
groupName: "Monaco",
matchManagers: ["npm"],
matchPackagePatterns: ["monaco"],
description: "Weekly update of the Monaco editor",
},
{
groupName: "strum",
matchManagers: ["cargo"],
matchPackagePatterns: ["strum"],
description: "Weekly update of strum dependencies",
},
{
groupName: "ESLint",
matchManagers: ["npm"],
matchPackageNames: ["eslint"],
allowedVersions: "<9",
description: "Constraint ESLint to version 8 until TypeScript-eslint supports ESLint 9", // https://github.com/typescript-eslint/typescript-eslint/issues/8211
},
],
vulnerabilityAlerts: {
commitMessageSuffix: "",
labels: ["internal", "security"],
},
}

View File

@@ -23,8 +23,6 @@ jobs:
name: "Determine changes" name: "Determine changes"
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
# Flag that is raised when any code that affects parser is changed
parser: ${{ steps.changed.outputs.parser_any_changed }}
# Flag that is raised when any code that affects linter is changed # Flag that is raised when any code that affects linter is changed
linter: ${{ steps.changed.outputs.linter_any_changed }} linter: ${{ steps.changed.outputs.linter_any_changed }}
# Flag that is raised when any code that affects formatter is changed # Flag that is raised when any code that affects formatter is changed
@@ -37,21 +35,10 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: tj-actions/changed-files@v44 - uses: tj-actions/changed-files@v42
id: changed id: changed
with: with:
files_yaml: | files_yaml: |
parser:
- Cargo.toml
- Cargo.lock
- crates/ruff_python_trivia/**
- crates/ruff_source_file/**
- crates/ruff_text_size/**
- crates/ruff_python_ast/**
- crates/ruff_python_parser/**
- scripts/fuzz-parser/**
- .github/workflows/ci.yaml
linter: linter:
- Cargo.toml - Cargo.toml
- Cargo.lock - Cargo.lock
@@ -213,38 +200,6 @@ jobs:
tool: cargo-fuzz@0.11.2 tool: cargo-fuzz@0.11.2
- run: cargo fuzz build -s none - run: cargo fuzz build -s none
fuzz-parser:
name: "Fuzz the parser"
runs-on: ubuntu-latest
needs:
- cargo-test-linux
- determine_changes
if: ${{ needs.determine_changes.outputs.parser == 'true' }}
timeout-minutes: 20
env:
FORCE_COLOR: 1
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Install Python requirements
run: uv pip install -r scripts/fuzz-parser/requirements.txt --system
- uses: actions/download-artifact@v4
name: Download Ruff binary to test
id: download-cached-binary
with:
name: ruff
path: ruff-to-test
- name: Fuzz
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ${{ steps.download-cached-binary.outputs.download-path }}/ruff
python scripts/fuzz-parser/fuzz.py 0-500 --test-executable ${{ steps.download-cached-binary.outputs.download-path }}/ruff
scripts: scripts:
name: "test scripts" name: "test scripts"
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -273,7 +228,9 @@ jobs:
- determine_changes - determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison. # Only runs on pull requests, since that is the only we way we can find the base version for comparison.
# Ecosystem check needs linter and/or formatter changes. # Ecosystem check needs linter and/or formatter changes.
if: ${{ github.event_name == 'pull_request' && needs.determine_changes.outputs.code == 'true' }} if: github.event_name == 'pull_request' && ${{
needs.determine_changes.outputs.code == 'true'
}}
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -552,7 +509,7 @@ jobs:
benchmarks: benchmarks:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: determine_changes needs: determine_changes
if: ${{ github.repository == 'astral-sh/ruff' && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- name: "Checkout Branch" - name: "Checkout Branch"
@@ -568,23 +525,8 @@ jobs:
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
# Codspeed comes with a very ancient cargo version (1.66) that resolves features flags differently than what we use now.
# This can result in build failures; see https://github.com/astral-sh/ruff/pull/10700.
# There's a pending codspeed PR to upgrade to a newer cargo version, but until that's merged, we need to use the workaround below.
# https://github.com/CodSpeedHQ/codspeed-rust/pull/31
# What we do is to call cargo build manually with the correct feature flags and RUSTC settings. We'll have to
# manually maintain the list of benchmarks to run with codspeed (the benefit is that we could detect which benchmarks to run and build based on the changes).
# This is inspired by https://github.com/oxc-project/oxc/blob/a0532adc654039a0c7ead7b35216dfa0b0cb8e8f/.github/workflows/benchmark.yml
- name: "Build benchmarks" - name: "Build benchmarks"
env: run: cargo codspeed build --features codspeed -p ruff_benchmark
RUSTFLAGS: "-C debuginfo=2 -C strip=none -g --cfg codspeed"
shell: bash
# Build all benchmarks, copy the binary to the codspeed directory, remove any `*.d` files that might have been created.
run: |
cargo build --release -p ruff_benchmark --bench parser --bench linter --bench formatter --bench lexer --features=codspeed
mkdir -p ./target/codspeed/ruff_benchmark
cp ./target/release/deps/{lexer,parser,linter,formatter}* target/codspeed/ruff_benchmark/
rm -rf ./target/codspeed/ruff_benchmark/*.d
- name: "Run benchmarks" - name: "Run benchmarks"
uses: CodSpeedHQ/action@v2 uses: CodSpeedHQ/action@v2

1
.gitignore vendored
View File

@@ -92,7 +92,6 @@ coverage.xml
.hypothesis/ .hypothesis/
.pytest_cache/ .pytest_cache/
cover/ cover/
repos/
# Translations # Translations
*.mo *.mo

View File

@@ -17,4 +17,4 @@ MD013: false
# MD024/no-duplicate-heading # MD024/no-duplicate-heading
MD024: MD024:
# Allow when nested under different parents e.g. CHANGELOG.md # Allow when nested under different parents e.g. CHANGELOG.md
siblings_only: true allow_different_nesting: true

View File

@@ -13,7 +13,7 @@ exclude: |
repos: repos:
- repo: https://github.com/abravalheri/validate-pyproject - repo: https://github.com/abravalheri/validate-pyproject
rev: v0.16 rev: v0.15
hooks: hooks:
- id: validate-pyproject - id: validate-pyproject
@@ -31,7 +31,7 @@ repos:
)$ )$
- repo: https://github.com/igorshubovych/markdownlint-cli - repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.39.0 rev: v0.37.0
hooks: hooks:
- id: markdownlint-fix - id: markdownlint-fix
exclude: | exclude: |
@@ -41,7 +41,7 @@ repos:
)$ )$
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: v1.20.9 rev: v1.16.22
hooks: hooks:
- id: typos - id: typos
@@ -55,7 +55,7 @@ repos:
pass_filenames: false # This makes it a lot faster pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.1 rev: v0.1.4
hooks: hooks:
- id: ruff-format - id: ruff-format
- id: ruff - id: ruff
@@ -70,7 +70,7 @@ repos:
# Prettier # Prettier
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0 rev: v3.0.3
hooks: hooks:
- id: prettier - id: prettier
types: [yaml] types: [yaml]

View File

@@ -1,281 +1,5 @@
# Changelog # Changelog
## 0.4.1
### Preview features
- \[`pylint`\] Implement `invalid-hash-returned` (`PLE0309`) ([#10961](https://github.com/astral-sh/ruff/pull/10961))
- \[`pylint`\] Implement `invalid-index-returned` (`PLE0305`) ([#10962](https://github.com/astral-sh/ruff/pull/10962))
### Bug fixes
- \[`pylint`\] Allow `NoReturn`-like functions for `__str__`, `__len__`, etc. (`PLE0307`) ([#11017](https://github.com/astral-sh/ruff/pull/11017))
- Parser: Use empty range when there's "gap" in token source ([#11032](https://github.com/astral-sh/ruff/pull/11032))
- \[`ruff`\] Ignore stub functions in `unused-async` (`RUF029`) ([#11026](https://github.com/astral-sh/ruff/pull/11026))
- Parser: Expect indented case block instead of match stmt ([#11033](https://github.com/astral-sh/ruff/pull/11033))
## 0.4.0
### A new, hand-written parser
Ruff's new parser is **>2x faster**, which translates to a **20-40% speedup** for all linting and formatting invocations.
There's a lot to say about this exciting change, so check out the [blog post](https://astral.sh/blog/ruff-v0.4.0) for more details!
See [#10036](https://github.com/astral-sh/ruff/pull/10036) for implementation details.
### A new language server in Rust
With this release, we also want to highlight our new language server. `ruff server` is a Rust-powered language
server that comes built-in with Ruff. It can be used with any editor that supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) (LSP).
It uses a multi-threaded, lock-free architecture inspired by `rust-analyzer` and it will open the door for a lot
of exciting features. Its also faster than our previous [Python-based language server](https://github.com/astral-sh/ruff-lsp)
-- but you probably guessed that already.
`ruff server` is only in alpha, but it has a lot of features that you can try out today:
- Lints Python files automatically and shows quick-fixes when available
- Formats Python files, with support for range formatting
- Comes with commands for quickly performing actions: `ruff.applyAutofix`, `ruff.applyFormat`, and `ruff.applyOrganizeImports`
- Supports `source.fixAll` and `source.organizeImports` source actions
- Automatically reloads your project configuration when you change it
To setup `ruff server` with your editor, refer to the [README.md](https://github.com/astral-sh/ruff/blob/main/crates/ruff_server/README.md).
### Preview features
- \[`pycodestyle`\] Do not trigger `E3` rules on `def`s following a function/method with a dummy body ([#10704](https://github.com/astral-sh/ruff/pull/10704))
- \[`pylint`\] Implement `invalid-bytes-returned` (`E0308`) ([#10959](https://github.com/astral-sh/ruff/pull/10959))
- \[`pylint`\] Implement `invalid-length-returned` (`E0303`) ([#10963](https://github.com/astral-sh/ruff/pull/10963))
- \[`pylint`\] Implement `self-cls-assignment` (`W0642`) ([#9267](https://github.com/astral-sh/ruff/pull/9267))
- \[`pylint`\] Omit stubs from `invalid-bool` and `invalid-str-return-type` ([#11008](https://github.com/astral-sh/ruff/pull/11008))
- \[`ruff`\] New rule `unused-async` (`RUF029`) to detect unneeded `async` keywords on functions ([#9966](https://github.com/astral-sh/ruff/pull/9966))
### Rule changes
- \[`flake8-bandit`\] Allow `urllib.request.urlopen` calls with static `Request` argument (`S310`) ([#10964](https://github.com/astral-sh/ruff/pull/10964))
- \[`flake8-bugbear`\] Treat `raise NotImplemented`-only bodies as stub functions (`B006`) ([#10990](https://github.com/astral-sh/ruff/pull/10990))
- \[`flake8-slots`\] Respect same-file `Enum` subclasses (`SLOT000`) ([#11006](https://github.com/astral-sh/ruff/pull/11006))
- \[`pylint`\] Support inverted comparisons (`PLR1730`) ([#10920](https://github.com/astral-sh/ruff/pull/10920))
### Linter
- Improve handling of builtin symbols in linter rules ([#10919](https://github.com/astral-sh/ruff/pull/10919))
- Improve display of rules in `--show-settings` ([#11003](https://github.com/astral-sh/ruff/pull/11003))
- Improve inference capabilities of the `BuiltinTypeChecker` ([#10976](https://github.com/astral-sh/ruff/pull/10976))
- Resolve classes and functions relative to script name ([#10965](https://github.com/astral-sh/ruff/pull/10965))
- Improve performance of `RuleTable::any_enabled` ([#10971](https://github.com/astral-sh/ruff/pull/10971))
### Server
*This section is devoted to updates for our new language server, written in Rust.*
- Enable ruff-specific source actions ([#10916](https://github.com/astral-sh/ruff/pull/10916))
- Refreshes diagnostics for open files when file configuration is changed ([#10988](https://github.com/astral-sh/ruff/pull/10988))
- Important errors are now shown as popups ([#10951](https://github.com/astral-sh/ruff/pull/10951))
- Introduce settings for directly configuring the linter and formatter ([#10984](https://github.com/astral-sh/ruff/pull/10984))
- Resolve configuration for each document individually ([#10950](https://github.com/astral-sh/ruff/pull/10950))
- Write a setup guide for Neovim ([#10987](https://github.com/astral-sh/ruff/pull/10987))
### Configuration
- Add `RUFF_OUTPUT_FILE` environment variable support ([#10992](https://github.com/astral-sh/ruff/pull/10992))
### Bug fixes
- Avoid `non-augmented-assignment` for reversed, non-commutative operators (`PLR6104`) ([#10909](https://github.com/astral-sh/ruff/pull/10909))
- Limit commutative non-augmented-assignments to primitive data types (`PLR6104`) ([#10912](https://github.com/astral-sh/ruff/pull/10912))
- Respect `per-file-ignores` for `RUF100` on blanket `# noqa` ([#10908](https://github.com/astral-sh/ruff/pull/10908))
- Consider `if` expression for parenthesized with items parsing ([#11010](https://github.com/astral-sh/ruff/pull/11010))
- Consider binary expr for parenthesized with items parsing ([#11012](https://github.com/astral-sh/ruff/pull/11012))
- Reset `FOR_TARGET` context for all kinds of parentheses ([#11009](https://github.com/astral-sh/ruff/pull/11009))
## 0.3.7
### Preview features
- \[`flake8-bugbear`\] Implement `loop-iterator-mutation` (`B909`) ([#9578](https://github.com/astral-sh/ruff/pull/9578))
- \[`pylint`\] Implement rule to prefer augmented assignment (`PLR6104`) ([#9932](https://github.com/astral-sh/ruff/pull/9932))
### Bug fixes
- Avoid TOCTOU errors in cache initialization ([#10884](https://github.com/astral-sh/ruff/pull/10884))
- \[`pylint`\] Recode `nan-comparison` rule to `W0177` ([#10894](https://github.com/astral-sh/ruff/pull/10894))
- \[`pylint`\] Reverse min-max logic in `if-stmt-min-max` ([#10890](https://github.com/astral-sh/ruff/pull/10890))
## 0.3.6
### Preview features
- \[`pylint`\] Implement `bad-staticmethod-argument` (`PLW0211`) ([#10781](https://github.com/astral-sh/ruff/pull/10781))
- \[`pylint`\] Implement `if-stmt-min-max` (`PLR1730`, `PLR1731`) ([#10002](https://github.com/astral-sh/ruff/pull/10002))
- \[`pyupgrade`\] Replace `str,Enum` multiple inheritance with `StrEnum` `UP042` ([#10713](https://github.com/astral-sh/ruff/pull/10713))
- \[`refurb`\] Implement `if-expr-instead-of-or-operator` (`FURB110`) ([#10687](https://github.com/astral-sh/ruff/pull/10687))
- \[`refurb`\] Implement `int-on-sliced-str` (`FURB166`) ([#10650](https://github.com/astral-sh/ruff/pull/10650))
- \[`refurb`\] Implement `write-whole-file` (`FURB103`) ([#10802](https://github.com/astral-sh/ruff/pull/10802))
- \[`refurb`\] Support `itemgetter` in `reimplemented-operator` (`FURB118`) ([#10526](https://github.com/astral-sh/ruff/pull/10526))
- \[`flake8_comprehensions`\] Add `sum`/`min`/`max` to unnecessary comprehension check (`C419`) ([#10759](https://github.com/astral-sh/ruff/pull/10759))
### Rule changes
- \[`pydocstyle`\] Require capitalizing docstrings where the first sentence is a single word (`D403`) ([#10776](https://github.com/astral-sh/ruff/pull/10776))
- \[`pycodestyle`\] Ignore annotated lambdas in class scopes (`E731`) ([#10720](https://github.com/astral-sh/ruff/pull/10720))
- \[`flake8-pyi`\] Various improvements to PYI034 ([#10807](https://github.com/astral-sh/ruff/pull/10807))
- \[`flake8-slots`\] Flag subclasses of call-based `typing.NamedTuple`s as well as subclasses of `collections.namedtuple()` (`SLOT002`) ([#10808](https://github.com/astral-sh/ruff/pull/10808))
- \[`pyflakes`\] Allow forward references in class bases in stub files (`F821`) ([#10779](https://github.com/astral-sh/ruff/pull/10779))
- \[`pygrep-hooks`\] Improve `blanket-noqa` error message (`PGH004`) ([#10851](https://github.com/astral-sh/ruff/pull/10851))
### CLI
- Support `FORCE_COLOR` env var ([#10839](https://github.com/astral-sh/ruff/pull/10839))
### Configuration
- Support negated patterns in `[extend-]per-file-ignores` ([#10852](https://github.com/astral-sh/ruff/pull/10852))
### Bug fixes
- \[`flake8-import-conventions`\] Accept non-aliased (but correct) import in `unconventional-import-alias` (`ICN001`) ([#10729](https://github.com/astral-sh/ruff/pull/10729))
- \[`flake8-quotes`\] Add semantic model flag when inside f-string replacement field ([#10766](https://github.com/astral-sh/ruff/pull/10766))
- \[`pep8-naming`\] Recursively resolve `TypeDicts` for N815 violations ([#10719](https://github.com/astral-sh/ruff/pull/10719))
- \[`flake8-quotes`\] Respect `Q00*` ignores in `flake8-quotes` rules ([#10728](https://github.com/astral-sh/ruff/pull/10728))
- \[`flake8-simplify`\] Show negated condition in `needless-bool` diagnostics (`SIM103`) ([#10854](https://github.com/astral-sh/ruff/pull/10854))
- \[`ruff`\] Use within-scope shadowed bindings in `asyncio-dangling-task` (`RUF006`) ([#10793](https://github.com/astral-sh/ruff/pull/10793))
- \[`flake8-pytest-style`\] Fix single-tuple conversion in `pytest-parametrize-values-wrong-type` (`PT007`) ([#10862](https://github.com/astral-sh/ruff/pull/10862))
- \[`flake8-return`\] Ignore assignments to annotated variables in `unnecessary-assign` (`RET504`) ([#10741](https://github.com/astral-sh/ruff/pull/10741))
- \[`refurb`\] Do not allow any keyword arguments for `read-whole-file` in `rb` mode (`FURB101`) ([#10803](https://github.com/astral-sh/ruff/pull/10803))
- \[`pylint`\] Don't recommend decorating staticmethods with `@singledispatch` (`PLE1519`, `PLE1520`) ([#10637](https://github.com/astral-sh/ruff/pull/10637))
- \[`pydocstyle`\] Use section name range for all section-related docstring diagnostics ([#10740](https://github.com/astral-sh/ruff/pull/10740))
- Respect `# noqa` directives on `__all__` openers ([#10798](https://github.com/astral-sh/ruff/pull/10798))
## 0.3.5
### Preview features
- \[`pylint`\] Implement `modified-iterating-set` (`E4703`) ([#10473](https://github.com/astral-sh/ruff/pull/10473))
- \[`refurb`\] Implement `for-loop-set-mutations` (`FURB142`) ([#10583](https://github.com/astral-sh/ruff/pull/10583))
- \[`refurb`\] Implement `unnecessary-from-float` (`FURB164`) ([#10647](https://github.com/astral-sh/ruff/pull/10647))
- \[`refurb`\] Implement `verbose-decimal-constructor` (`FURB157`) ([#10533](https://github.com/astral-sh/ruff/pull/10533))
### Rule changes
- \[`flake8-comprehensions`\] Handled special case for `C401` which also matches `C416` ([#10596](https://github.com/astral-sh/ruff/pull/10596))
- \[`flake8-pyi`\] Mark `unaliased-collections-abc-set-import` fix as "safe" for more cases in stub files (`PYI025`) ([#10547](https://github.com/astral-sh/ruff/pull/10547))
- \[`numpy`\] Add `row_stack` to NumPy 2.0 migration rule ([#10646](https://github.com/astral-sh/ruff/pull/10646))
- \[`pycodestyle`\] Allow cell magics before an import (`E402`) ([#10545](https://github.com/astral-sh/ruff/pull/10545))
- \[`pycodestyle`\] Avoid blank line rules for the first logical line in cell ([#10291](https://github.com/astral-sh/ruff/pull/10291))
### Configuration
- Respected nested namespace packages ([#10541](https://github.com/astral-sh/ruff/pull/10541))
- \[`flake8-boolean-trap`\] Add setting for user defined allowed boolean trap ([#10531](https://github.com/astral-sh/ruff/pull/10531))
### Bug fixes
- Correctly handle references in `__all__` definitions when renaming symbols in autofixes ([#10527](https://github.com/astral-sh/ruff/pull/10527))
- Track ranges of names inside `__all__` definitions ([#10525](https://github.com/astral-sh/ruff/pull/10525))
- \[`flake8-bugbear`\] Avoid false positive for usage after `continue` (`B031`) ([#10539](https://github.com/astral-sh/ruff/pull/10539))
- \[`flake8-copyright`\] Accept commas in default copyright pattern ([#9498](https://github.com/astral-sh/ruff/pull/9498))
- \[`flake8-datetimez`\] Allow f-strings with `%z` for `DTZ007` ([#10651](https://github.com/astral-sh/ruff/pull/10651))
- \[`flake8-pytest-style`\] Fix `PT014` autofix for last item in list ([#10532](https://github.com/astral-sh/ruff/pull/10532))
- \[`flake8-quotes`\] Ignore `Q000`, `Q001` when string is inside forward ref ([#10585](https://github.com/astral-sh/ruff/pull/10585))
- \[`isort`\] Always place non-relative imports after relative imports ([#10669](https://github.com/astral-sh/ruff/pull/10669))
- \[`isort`\] Respect Unicode characters in import sorting ([#10529](https://github.com/astral-sh/ruff/pull/10529))
- \[`pyflakes`\] Fix F821 false negatives when `from __future__ import annotations` is active (attempt 2) ([#10524](https://github.com/astral-sh/ruff/pull/10524))
- \[`pyflakes`\] Make `unnecessary-lambda` an always-unsafe fix ([#10668](https://github.com/astral-sh/ruff/pull/10668))
- \[`pylint`\] Fixed false-positive on the rule `PLW1641` (`eq-without-hash`) ([#10566](https://github.com/astral-sh/ruff/pull/10566))
- \[`ruff`\] Fix panic in unused `# noqa` removal with multi-byte space (`RUF100`) ([#10682](https://github.com/astral-sh/ruff/pull/10682))
### Documentation
- Add PR title format to `CONTRIBUTING.md` ([#10665](https://github.com/astral-sh/ruff/pull/10665))
- Fix list markup to include blank lines required ([#10591](https://github.com/astral-sh/ruff/pull/10591))
- Put `flake8-logging` next to the other flake8 plugins in registry ([#10587](https://github.com/astral-sh/ruff/pull/10587))
- \[`flake8-bandit`\] Update warning message for rule `S305` to address insecure block cipher mode use ([#10602](https://github.com/astral-sh/ruff/pull/10602))
- \[`flake8-bugbear`\] Document use of anonymous assignment in `useless-expression` ([#10551](https://github.com/astral-sh/ruff/pull/10551))
- \[`flake8-datetimez`\] Clarify error messages and docs for `DTZ` rules ([#10621](https://github.com/astral-sh/ruff/pull/10621))
- \[`pycodestyle`\] Use same before vs. after numbers for `space-around-operator` ([#10640](https://github.com/astral-sh/ruff/pull/10640))
- \[`ruff`\] Change `quadratic-list-summation` docs to use `iadd` consistently ([#10666](https://github.com/astral-sh/ruff/pull/10666))
## 0.3.4
### Preview features
- \[`flake8-simplify`\] Detect implicit `else` cases in `needless-bool` (`SIM103`) ([#10414](https://github.com/astral-sh/ruff/pull/10414))
- \[`pylint`\] Implement `nan-comparison` (`PLW0117`) ([#10401](https://github.com/astral-sh/ruff/pull/10401))
- \[`pylint`\] Implement `nonlocal-and-global` (`E115`) ([#10407](https://github.com/astral-sh/ruff/pull/10407))
- \[`pylint`\] Implement `singledispatchmethod-function` (`PLE5120`) ([#10428](https://github.com/astral-sh/ruff/pull/10428))
- \[`refurb`\] Implement `list-reverse-copy` (`FURB187`) ([#10212](https://github.com/astral-sh/ruff/pull/10212))
### Rule changes
- \[`flake8-pytest-style`\] Add automatic fix for `pytest-parametrize-values-wrong-type` (`PT007`) ([#10461](https://github.com/astral-sh/ruff/pull/10461))
- \[`pycodestyle`\] Allow SPDX license headers to exceed the line length (`E501`) ([#10481](https://github.com/astral-sh/ruff/pull/10481))
### Formatter
- Fix unstable formatting for trailing subscript end-of-line comment ([#10492](https://github.com/astral-sh/ruff/pull/10492))
### Bug fixes
- Avoid code comment detection in PEP 723 script tags ([#10464](https://github.com/astral-sh/ruff/pull/10464))
- Avoid incorrect tuple transformation in single-element case (`C409`) ([#10491](https://github.com/astral-sh/ruff/pull/10491))
- Bug fix: Prevent fully defined links [`name`](link) from being reformatted ([#10442](https://github.com/astral-sh/ruff/pull/10442))
- Consider raw source code for `W605` ([#10480](https://github.com/astral-sh/ruff/pull/10480))
- Docs: Link inline settings when not part of options section ([#10499](https://github.com/astral-sh/ruff/pull/10499))
- Don't treat annotations as redefinitions in `.pyi` files ([#10512](https://github.com/astral-sh/ruff/pull/10512))
- Fix `E231` bug: Inconsistent catch compared to pycodestyle, such as when dict nested in list ([#10469](https://github.com/astral-sh/ruff/pull/10469))
- Fix pylint upstream categories not showing in docs ([#10441](https://github.com/astral-sh/ruff/pull/10441))
- Add missing `Options` references to blank line docs ([#10498](https://github.com/astral-sh/ruff/pull/10498))
- 'Revert "F821: Fix false negatives in .py files when `from __future__ import annotations` is active (#10362)"' ([#10513](https://github.com/astral-sh/ruff/pull/10513))
- Apply NFKC normalization to unicode identifiers in the lexer ([#10412](https://github.com/astral-sh/ruff/pull/10412))
- Avoid failures due to non-deterministic binding ordering ([#10478](https://github.com/astral-sh/ruff/pull/10478))
- \[`flake8-bugbear`\] Allow tuples of exceptions (`B030`) ([#10437](https://github.com/astral-sh/ruff/pull/10437))
- \[`flake8-quotes`\] Avoid syntax errors due to invalid quotes (`Q000, Q002`) ([#10199](https://github.com/astral-sh/ruff/pull/10199))
## 0.3.3
### Preview features
- \[`flake8-bandit`\]: Implement `S610` rule ([#10316](https://github.com/astral-sh/ruff/pull/10316))
- \[`pycodestyle`\] Implement `blank-line-at-end-of-file` (`W391`) ([#10243](https://github.com/astral-sh/ruff/pull/10243))
- \[`pycodestyle`\] Implement `redundant-backslash` (`E502`) ([#10292](https://github.com/astral-sh/ruff/pull/10292))
- \[`pylint`\] - implement `redeclared-assigned-name` (`W0128`) ([#9268](https://github.com/astral-sh/ruff/pull/9268))
### Rule changes
- \[`flake8_comprehensions`\] Handled special case for `C400` which also matches `C416` ([#10419](https://github.com/astral-sh/ruff/pull/10419))
- \[`flake8-bandit`\] Implement upstream updates for `S311`, `S324` and `S605` ([#10313](https://github.com/astral-sh/ruff/pull/10313))
- \[`pyflakes`\] Remove `F401` fix for `__init__` imports by default and allow opt-in to unsafe fix ([#10365](https://github.com/astral-sh/ruff/pull/10365))
- \[`pylint`\] Implement `invalid-bool-return-type` (`E304`) ([#10377](https://github.com/astral-sh/ruff/pull/10377))
- \[`pylint`\] Include builtin warnings in useless-exception-statement (`PLW0133`) ([#10394](https://github.com/astral-sh/ruff/pull/10394))
### CLI
- Add message on success to `ruff check` ([#8631](https://github.com/astral-sh/ruff/pull/8631))
### Bug fixes
- \[`PIE970`\] Allow trailing ellipsis in `typing.TYPE_CHECKING` ([#10413](https://github.com/astral-sh/ruff/pull/10413))
- Avoid `TRIO115` if the argument is a variable ([#10376](https://github.com/astral-sh/ruff/pull/10376))
- \[`F811`\] Avoid removing shadowed imports that point to different symbols ([#10387](https://github.com/astral-sh/ruff/pull/10387))
- Fix `F821` and `F822` false positives in `.pyi` files ([#10341](https://github.com/astral-sh/ruff/pull/10341))
- Fix `F821` false negatives in `.py` files when `from __future__ import annotations` is active ([#10362](https://github.com/astral-sh/ruff/pull/10362))
- Fix case where `Indexer` fails to identify continuation preceded by newline #10351 ([#10354](https://github.com/astral-sh/ruff/pull/10354))
- Sort hash maps in `Settings` display ([#10370](https://github.com/astral-sh/ruff/pull/10370))
- Track conditional deletions in the semantic model ([#10415](https://github.com/astral-sh/ruff/pull/10415))
- \[`C413`\] Wrap expressions in parentheses when negating ([#10346](https://github.com/astral-sh/ruff/pull/10346))
- \[`pycodestyle`\] Do not ignore lines before the first logical line in blank lines rules. ([#10382](https://github.com/astral-sh/ruff/pull/10382))
- \[`pycodestyle`\] Do not trigger `E225` and `E275` when the next token is a ')' ([#10315](https://github.com/astral-sh/ruff/pull/10315))
- \[`pylint`\] Avoid false-positive slot non-assignment for `__dict__` (`PLE0237`) ([#10348](https://github.com/astral-sh/ruff/pull/10348))
- Gate f-string struct size test for Rustc \< 1.76 ([#10371](https://github.com/astral-sh/ruff/pull/10371))
### Documentation
- Use `ruff.toml` format in README ([#10393](https://github.com/astral-sh/ruff/pull/10393))
- \[`RUF008`\] Make it clearer that a mutable default in a dataclass is only valid if it is typed as a ClassVar ([#10395](https://github.com/astral-sh/ruff/pull/10395))
- \[`pylint`\] Extend docs and test in `invalid-str-return-type` (`E307`) ([#10400](https://github.com/astral-sh/ruff/pull/10400))
- Remove `.` from `check` and `format` commands ([#10217](https://github.com/astral-sh/ruff/pull/10217))
## 0.3.2 ## 0.3.2
### Preview features ### Preview features
@@ -293,7 +17,7 @@ To setup `ruff server` with your editor, refer to the [README.md](https://github
- Fix unstable `with` items formatting ([#10274](https://github.com/astral-sh/ruff/pull/10274)) - Fix unstable `with` items formatting ([#10274](https://github.com/astral-sh/ruff/pull/10274))
- Avoid repeating function calls in f-string conversions ([#10265](https://github.com/astral-sh/ruff/pull/10265)) - Avoid repeating function calls in f-string conversions ([#10265](https://github.com/astral-sh/ruff/pull/10265))
- Fix E203 false positive for slices in format strings ([#10280](https://github.com/astral-sh/ruff/pull/10280)) - Fix E203 false positive for slices in format strings ([#10280](https://github.com/astral-sh/ruff/pull/10280))
- Fix incorrect `Parameter` range for `*args` and `**kwargs` ([#10283](https://github.com/astral-sh/ruff/pull/10283)) - Fix incorrect `Parameter` range for `*args` and `**kwargs` ([#10283](https://github.com/astral-sh/ruff/pull/10283))
- Treat `typing.Annotated` subscripts as type definitions ([#10285](https://github.com/astral-sh/ruff/pull/10285)) - Treat `typing.Annotated` subscripts as type definitions ([#10285](https://github.com/astral-sh/ruff/pull/10285))
## 0.3.1 ## 0.3.1
@@ -401,7 +125,8 @@ This release introduces the Ruff 2024.2 style, stabilizing the following changes
Highlights include: Highlights include:
- Initial support formatting f-strings (in `--preview`). - 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"`). - 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. - Significant performance improvements in Ruff's lexer, parser, and lint rules.
### Preview features ### Preview features
@@ -1049,7 +774,7 @@ docstrings via the `docstring-code-format` setting.
- \[`pylint`\] Default `max-positional-args` to `max-args` ([#8998](https://github.com/astral-sh/ruff/pull/8998)) - \[`pylint`\] Default `max-positional-args` to `max-args` ([#8998](https://github.com/astral-sh/ruff/pull/8998))
- \[`pylint`\] Add `allow-dunder-method-names` setting for `bad-dunder-method-name` (`PLW3201`) ([#8812](https://github.com/astral-sh/ruff/pull/8812)) - \[`pylint`\] Add `allow-dunder-method-names` setting for `bad-dunder-method-name` (`PLW3201`) ([#8812](https://github.com/astral-sh/ruff/pull/8812))
- \[`isort`\] Add support for `from-first` setting ([#8663](https://github.com/astral-sh/ruff/pull/8663)) - \[`isort`\] Add support for `from-first` setting ([#8663](https://github.com/astral-sh/ruff/pull/8663))
- \[`isort`\] Add support for `length-sort` settings ([#8841](https://github.com/astral-sh/ruff/pull/8841)) - \[`isort`\] Add support for `length-sort` settings ([#8841](https://github.com/astral-sh/ruff/pull/8841))
### Bug fixes ### Bug fixes
@@ -1178,7 +903,7 @@ docstrings via the `docstring-code-format` setting.
- \[`flake8-trio`\] Implement `TRIO115` ([#8486](https://github.com/astral-sh/ruff/pull/8486)) - \[`flake8-trio`\] Implement `TRIO115` ([#8486](https://github.com/astral-sh/ruff/pull/8486))
- \[`refurb`\] Implement `type-none-comparison` (`FURB169`) ([#8487](https://github.com/astral-sh/ruff/pull/8487)) - \[`refurb`\] Implement `type-none-comparison` (`FURB169`) ([#8487](https://github.com/astral-sh/ruff/pull/8487))
- Flag all comparisons against builtin types in `E721` ([#8491](https://github.com/astral-sh/ruff/pull/8491)) - Flag all comparisons against builtin types in `E721` ([#8491](https://github.com/astral-sh/ruff/pull/8491))
- Make `SIM118` fix as safe when the expression is a known dictionary ([#8525](https://github.com/astral-sh/ruff/pull/8525)) - Make `SIM118` fix as safe when the expression is a known dictionary ([#8525](https://github.com/astral-sh/ruff/pull/8525))
### Formatter ### Formatter
@@ -1346,7 +1071,7 @@ Try it today with `ruff format`! [Check out the blog post](https://astral.sh/blo
- Add `backports.strenum` to `deprecated-imports` ([#8113](https://github.com/astral-sh/ruff/pull/8113)) - Add `backports.strenum` to `deprecated-imports` ([#8113](https://github.com/astral-sh/ruff/pull/8113))
- Update `SIM112` to ignore `https_proxy`, `http_proxy`, and `no_proxy` ([#8140](https://github.com/astral-sh/ruff/pull/8140)) - Update `SIM112` to ignore `https_proxy`, `http_proxy`, and `no_proxy` ([#8140](https://github.com/astral-sh/ruff/pull/8140))
- Update fix for `literal-membership` (`PLR6201`) to be unsafe ([#8097](https://github.com/astral-sh/ruff/pull/8097)) - Update fix for `literal-membership` (`PLR6201`) to be unsafe ([#8097](https://github.com/astral-sh/ruff/pull/8097))
- Update fix for `mutable-argument-defaults` (`B006`) to be unsafe ([#8108](https://github.com/astral-sh/ruff/pull/8108)) - Update fix for `mutable-argument-defaults` (`B006`) to be unsafe ([#8108](https://github.com/astral-sh/ruff/pull/8108))
### Formatter ### Formatter
@@ -1474,7 +1199,7 @@ Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
- \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815)) - \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815))
- \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811)) - \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811))
*New rules are added in [preview](https://docs.astral.sh/ruff/preview/).* _New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._
### Configuration ### Configuration

View File

@@ -33,18 +33,27 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
## The Basics ## The Basics
Ruff welcomes contributions in the form of pull requests. Ruff welcomes contributions in the form of Pull Requests.
For small changes (e.g., bug fixes), feel free to submit a PR. For small changes (e.g., bug fixes), feel free to submit a PR.
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change. creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
You can also join us on [Discord](https://discord.com/invite/astral-sh) to discuss your idea with the You can also join us on [**Discord**](https://discord.com/invite/astral-sh) to discuss your idea with the
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug) in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted) and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted)
that are ready for contributions. that are ready for contributions.
If you're looking for a place to start, we recommend implementing a new lint rule (see:
[_Adding a new lint rule_](#example-adding-a-new-lint-rule), which will allow you to learn from and
pattern-match against the examples in the existing codebase. Many lint rules are inspired by
existing Python plugins, which can be used as a reference implementation.
As a concrete example: consider taking on one of the rules from the [`flake8-pyi`](https://github.com/astral-sh/ruff/issues/848)
plugin, and looking to the originating [Python source](https://github.com/PyCQA/flake8-pyi) for
guidance.
If you have suggestions on how we might improve the contributing documentation, [let us know](https://github.com/astral-sh/ruff/discussions/5693)! If you have suggestions on how we might improve the contributing documentation, [let us know](https://github.com/astral-sh/ruff/discussions/5693)!
### Prerequisites ### Prerequisites
@@ -98,7 +107,7 @@ RUFF_UPDATE_SCHEMA=1 cargo test # Rust testing and updating ruff.schema.json
pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc. pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc.
``` ```
These checks will run on GitHub Actions when you open your pull request, but running them locally These checks will run on GitHub Actions when you open your Pull Request, but running them locally
will save you time and expedite the merge process. will save you time and expedite the merge process.
Note that many code changes also require updating the snapshot tests, which is done interactively Note that many code changes also require updating the snapshot tests, which is done interactively
@@ -108,14 +117,7 @@ after running `cargo test` like so:
cargo insta review cargo insta review
``` ```
If your pull request relates to a specific lint rule, include the category and rule code in the Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration
title, as in the following examples:
- \[`flake8-bugbear`\] Avoid false positive for usage after `continue` (`B031`)
- \[`flake8-simplify`\] Detect implicit `else` cases in `needless-bool` (`SIM103`)
- \[`pycodestyle`\] Implement `redundant-backslash` (`E502`)
Your pull request will be reviewed by a maintainer, which may involve a few rounds of iteration
prior to merging. prior to merging.
### Project Structure ### Project Structure
@@ -123,8 +125,8 @@ prior to merging.
Ruff is structured as a monorepo with a [flat crate structure](https://matklad.github.io/2021/08/22/large-rust-workspaces.html), Ruff is structured as a monorepo with a [flat crate structure](https://matklad.github.io/2021/08/22/large-rust-workspaces.html),
such that all crates are contained in a flat `crates` directory. such that all crates are contained in a flat `crates` directory.
The vast majority of the code, including all lint rules, lives in the `ruff_linter` crate (located The vast majority of the code, including all lint rules, lives in the `ruff` crate (located at
at `crates/ruff_linter`). As a contributor, that's the crate that'll be most relevant to you. `crates/ruff_linter`). As a contributor, that's the crate that'll be most relevant to you.
At the time of writing, the repository includes the following crates: At the time of writing, the repository includes the following crates:
@@ -197,14 +199,11 @@ and calling out to lint rule analyzer functions as it goes.
If you need to inspect the AST, you can run `cargo dev print-ast` with a Python file. Grep If you need to inspect the AST, you can run `cargo dev print-ast` with a Python file. Grep
for the `Diagnostic::new` invocations to understand how other, similar rules are implemented. for the `Diagnostic::new` invocations to understand how other, similar rules are implemented.
Once you're satisfied with your code, add tests for your rule Once you're satisfied with your code, add tests for your rule. See [rule testing](#rule-testing-fixtures-and-snapshots)
(see: [rule testing](#rule-testing-fixtures-and-snapshots)), and regenerate the documentation and for more details.
associated assets (like our JSON Schema) with `cargo dev generate-all`.
Finally, submit a pull request, and include the category, rule name, and rule code in the title, as Finally, regenerate the documentation and other generated assets (like our JSON Schema) with:
in: `cargo dev generate-all`.
> \[`pycodestyle`\] Implement `redundant-backslash` (`E502`)
#### Rule naming convention #### Rule naming convention
@@ -814,8 +813,8 @@ To understand Ruff's import categorization system, we first need to define two c
"project root".) "project root".)
- "Package root": The top-most directory defining the Python package that includes a given Python - "Package root": The top-most directory defining the Python package that includes a given Python
file. To find the package root for a given Python file, traverse up its parent directories until file. To find the package root for a given Python file, traverse up its parent directories until
you reach a parent directory that doesn't contain an `__init__.py` file (and isn't in a subtree you reach a parent directory that doesn't contain an `__init__.py` file (and isn't marked as
marked as a [namespace package](https://docs.astral.sh/ruff/settings/#namespace-packages)); take the directory a [namespace package](https://docs.astral.sh/ruff/settings/#namespace-packages)); take the directory
just before that, i.e., the first directory in the package. just before that, i.e., the first directory in the package.
For example, given: For example, given:

687
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,20 +12,22 @@ authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
license = "MIT" license = "MIT"
[workspace.dependencies] [workspace.dependencies]
aho-corasick = { version = "1.1.3" } aho-corasick = { version = "1.1.2" }
annotate-snippets = { version = "0.9.2", features = ["color"] } annotate-snippets = { version = "0.9.2", features = ["color"] }
anyhow = { version = "1.0.80" } anyhow = { version = "1.0.80" }
argfile = { version = "0.2.0" } argfile = { version = "0.1.6" }
assert_cmd = { version = "2.0.13" }
bincode = { version = "1.3.3" } bincode = { version = "1.3.3" }
bitflags = { version = "2.5.0" } bitflags = { version = "2.4.1" }
bstr = { version = "1.9.1" } bstr = { version = "1.9.1" }
cachedir = { version = "0.3.1" } cachedir = { version = "0.3.1" }
chrono = { version = "0.4.35", default-features = false, features = ["clock"] } chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
clap = { version = "4.5.3", features = ["derive"] } clap = { version = "4.5.2", features = ["derive"] }
clap_complete_command = { version = "0.5.1" } clap_complete_command = { version = "0.5.1" }
clearscreen = { version = "3.0.0" } clearscreen = { version = "2.0.0" }
codspeed-criterion-compat = { version = "2.4.0", default-features = false } codspeed-criterion-compat = { version = "2.4.0", default-features = false }
colored = { version = "2.1.0" } colored = { version = "2.1.0" }
configparser = { version = "3.0.3" }
console_error_panic_hook = { version = "0.1.7" } console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" } console_log = { version = "1.0.0" }
countme = { version = "3.0.1" } countme = { version = "3.0.1" }
@@ -33,7 +35,7 @@ criterion = { version = "0.5.1", default-features = false }
crossbeam = { version = "0.8.4" } crossbeam = { version = "0.8.4" }
dirs = { version = "5.0.0" } dirs = { version = "5.0.0" }
drop_bomb = { version = "0.1.5" } drop_bomb = { version = "0.1.5" }
env_logger = { version = "0.11.0" } env_logger = { version = "0.10.1" }
fern = { version = "0.6.1" } fern = { version = "0.6.1" }
filetime = { version = "0.2.23" } filetime = { version = "0.2.23" }
fs-err = { version = "2.11.0" } fs-err = { version = "2.11.0" }
@@ -46,12 +48,13 @@ imperative = { version = "1.0.4" }
indicatif = { version = "0.17.8" } indicatif = { version = "0.17.8" }
indoc = { version = "2.0.4" } indoc = { version = "2.0.4" }
insta = { version = "1.35.1", feature = ["filters", "glob"] } insta = { version = "1.35.1", feature = ["filters", "glob"] }
insta-cmd = { version = "0.6.0" } insta-cmd = { version = "0.4.0" }
is-macro = { version = "0.3.5" } is-macro = { version = "0.3.5" }
is-wsl = { version = "0.4.0" } is-wsl = { version = "0.4.0" }
itertools = { version = "0.12.1" } itertools = { version = "0.12.1" }
js-sys = { version = "0.3.69" } js-sys = { version = "0.3.69" }
jod-thread = { version = "0.1.2" } jod-thread = { version = "0.1.2" }
lalrpop-util = { version = "0.20.0", default-features = false }
lexical-parse-float = { version = "0.8.0", features = ["format"] } lexical-parse-float = { version = "0.8.0", features = ["format"] }
libc = { version = "0.2.153" } libc = { version = "0.2.153" }
libcst = { version = "1.1.0", default-features = false } libcst = { version = "1.1.0", default-features = false }
@@ -62,18 +65,17 @@ memchr = { version = "2.7.1" }
mimalloc = { version = "0.1.39" } mimalloc = { version = "0.1.39" }
natord = { version = "1.0.9" } natord = { version = "1.0.9" }
notify = { version = "6.1.1" } notify = { version = "6.1.1" }
num_cpus = { version = "1.16.0" }
once_cell = { version = "1.19.0" } once_cell = { version = "1.19.0" }
path-absolutize = { version = "3.1.1" } path-absolutize = { version = "3.1.1" }
pathdiff = { version = "0.2.1" } pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.6.0", features = ["serde"] } pep440_rs = { version = "0.4.0", features = ["serde"] }
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"
proc-macro2 = { version = "1.0.79" } proc-macro2 = { version = "1.0.78" }
pyproject-toml = { version = "0.9.0" } pyproject-toml = { version = "0.9.0" }
quick-junit = { version = "0.4.0" } quick-junit = { version = "0.3.5" }
quote = { version = "1.0.23" } quote = { version = "1.0.23" }
rand = { version = "0.8.5" } rand = { version = "0.8.5" }
rayon = { version = "1.10.0" } rayon = { version = "1.8.1" }
regex = { version = "1.10.2" } regex = { version = "1.10.2" }
result-like = { version = "0.5.0" } result-like = { version = "0.5.0" }
rustc-hash = { version = "1.1.0" } rustc-hash = { version = "1.1.0" }
@@ -87,32 +89,31 @@ serde_with = { version = "3.6.0", default-features = false, features = ["macros"
shellexpand = { version = "3.0.0" } shellexpand = { version = "3.0.0" }
shlex = { version = "1.3.0" } shlex = { version = "1.3.0" }
similar = { version = "2.4.0", features = ["inline"] } similar = { version = "2.4.0", features = ["inline"] }
smallvec = { version = "1.13.2" } smallvec = { version = "1.13.1" }
static_assertions = "1.1.0" static_assertions = "1.1.0"
strum = { version = "0.26.0", features = ["strum_macros"] } strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.26.0" } strum_macros = { version = "0.25.3" }
syn = { version = "2.0.55" } syn = { version = "2.0.51" }
tempfile = { version = "3.9.0" } tempfile = { version = "3.9.0" }
test-case = { version = "3.3.1" } test-case = { version = "3.3.1" }
thiserror = { version = "1.0.58" } thiserror = { version = "1.0.57" }
tikv-jemallocator = { version = "0.5.0" } tikv-jemallocator = { version = "0.5.0" }
toml = { version = "0.8.11" } toml = { version = "0.8.9" }
tracing = { version = "0.1.40" } tracing = { version = "0.1.40" }
tracing-indicatif = { version = "0.3.6" } tracing-indicatif = { version = "0.3.6" }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-tree = { version = "0.3.0" } tracing-tree = { version = "0.2.4" }
typed-arena = { version = "2.0.2" } typed-arena = { version = "2.0.2" }
unic-ucd-category = { version = "0.9" } unic-ucd-category = { version = "0.9" }
unicode-ident = { version = "1.0.12" } unicode-ident = { version = "1.0.12" }
unicode-width = { version = "0.1.11" } unicode-width = { version = "0.1.11" }
unicode_names2 = { version = "1.2.2" } unicode_names2 = { version = "1.2.2" }
unicode-normalization = { version = "0.1.23" }
ureq = { version = "2.9.6" } ureq = { version = "2.9.6" }
url = { version = "2.5.0" } url = { version = "2.5.0" }
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] } uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
walkdir = { version = "2.3.2" } walkdir = { version = "2.3.2" }
wasm-bindgen = { version = "0.2.92" } wasm-bindgen = { version = "0.2.92" }
wasm-bindgen-test = { version = "0.3.42" } wasm-bindgen-test = { version = "0.3.40" }
wild = { version = "2" } wild = { version = "2" }
[workspace.lints.rust] [workspace.lints.rust]

View File

@@ -32,7 +32,7 @@ An extremely fast Python linter and code formatter, written in Rust.
- ⚖️ Drop-in parity with [Flake8](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8), isort, and Black - ⚖️ Drop-in parity with [Flake8](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8), isort, and Black
- 📦 Built-in caching, to avoid re-analyzing unchanged files - 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports) - 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [800 built-in rules](https://docs.astral.sh/ruff/rules/), with native re-implementations - 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/), with native re-implementations
of popular Flake8 plugins, like flake8-bugbear of popular Flake8 plugins, like flake8-bugbear
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/integrations/) for - ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/integrations/) for
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp) [VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
@@ -151,7 +151,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml ```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.4.1 rev: v0.3.2
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff
@@ -272,7 +272,7 @@ for more on the linting and formatting commands, respectively.
<!-- Begin section: Rules --> <!-- Begin section: Rules -->
**Ruff supports over 800 lint rules**, many of which are inspired by popular tools like Flake8, **Ruff supports over 700 lint rules**, many of which are inspired by popular tools like Flake8,
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
Rust as a first-party feature. Rust as a first-party feature.
@@ -429,7 +429,6 @@ Ruff is used by a number of major open-source projects and companies, including:
- [Mypy](https://github.com/python/mypy) - [Mypy](https://github.com/python/mypy)
- Netflix ([Dispatch](https://github.com/Netflix/dispatch)) - Netflix ([Dispatch](https://github.com/Netflix/dispatch))
- [Neon](https://github.com/neondatabase/neon) - [Neon](https://github.com/neondatabase/neon)
- [Nokia](https://nokia.com/)
- [NoneBot](https://github.com/nonebot/nonebot2) - [NoneBot](https://github.com/nonebot/nonebot2)
- [NumPyro](https://github.com/pyro-ppl/numpyro) - [NumPyro](https://github.com/pyro-ppl/numpyro)
- [ONNX](https://github.com/onnx/onnx) - [ONNX](https://github.com/onnx/onnx)

View File

@@ -3,17 +3,9 @@
extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"] extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"]
[default.extend-words] [default.extend-words]
"arange" = "arange" # e.g. `numpy.arange`
hel = "hel" hel = "hel"
whos = "whos" whos = "whos"
spawnve = "spawnve" spawnve = "spawnve"
ned = "ned" ned = "ned"
pn = "pn" # `import panel as pd` is a thing
poit = "poit" poit = "poit"
BA = "BA" # acronym for "Bad Allowed", used in testing. BA = "BA" # acronym for "Bad Allowed", used in testing.
[default]
extend-ignore-re = [
# Line ignore with trailing "spellchecker:disable-line"
"(?Rm)^.*#\\s*spellchecker:disable-line$"
]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ruff" name = "ruff"
version = "0.4.1" version = "0.3.2"
publish = false publish = false
authors = { workspace = true } authors = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
@@ -41,7 +41,6 @@ is-macro = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
log = { workspace = true } log = { workspace = true }
notify = { workspace = true } notify = { workspace = true }
num_cpus = { workspace = true }
path-absolutize = { workspace = true, features = ["once_cell_cache"] } path-absolutize = { workspace = true, features = ["once_cell_cache"] }
rayon = { workspace = true } rayon = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
@@ -54,7 +53,7 @@ tempfile = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
toml = { workspace = true } toml = { workspace = true }
tracing = { workspace = true, features = ["log"] } tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["registry"] } tracing-subscriber = { workspace = true, features = ["registry"]}
tracing-tree = { workspace = true } tracing-tree = { workspace = true }
walkdir = { workspace = true } walkdir = { workspace = true }
wild = { workspace = true } wild = { workspace = true }
@@ -62,8 +61,9 @@ wild = { workspace = true }
[dev-dependencies] [dev-dependencies]
# Enable test rules during development # Enable test rules during development
ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] } ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] }
assert_cmd = { workspace = true }
# Avoid writing colored snapshots when running tests from the terminal # Avoid writing colored snapshots when running tests from the terminal
colored = { workspace = true, features = ["no-color"] } colored = { workspace = true, features = ["no-color"]}
insta = { workspace = true, features = ["filters", "json"] } insta = { workspace = true, features = ["filters", "json"] }
insta-cmd = { workspace = true } insta-cmd = { workspace = true }
tempfile = { workspace = true } tempfile = { workspace = true }

View File

@@ -190,7 +190,7 @@ pub struct CheckCommand {
pub output_format: Option<SerializationFormat>, pub output_format: Option<SerializationFormat>,
/// Specify file to write the linter output to (default: stdout). /// Specify file to write the linter output to (default: stdout).
#[arg(short, long, env = "RUFF_OUTPUT_FILE")] #[arg(short, long)]
pub output_file: Option<PathBuf>, pub output_file: Option<PathBuf>,
/// The minimum Python version that should be supported. /// The minimum Python version that should be supported.
#[arg(long, value_enum)] #[arg(long, value_enum)]
@@ -496,7 +496,7 @@ pub struct FormatCommand {
pub range: Option<FormatRange>, pub range: Option<FormatRange>,
} }
#[derive(Copy, Clone, Debug, clap::Parser)] #[derive(Clone, Debug, clap::Parser)]
pub struct ServerCommand { pub struct ServerCommand {
/// Enable preview mode; required for regular operation /// Enable preview mode; required for regular operation
#[arg(long)] #[arg(long)]

View File

@@ -375,17 +375,15 @@ pub(crate) fn init(path: &Path) -> Result<()> {
fs::create_dir_all(path.join(VERSION))?; fs::create_dir_all(path.join(VERSION))?;
// Add the CACHEDIR.TAG. // Add the CACHEDIR.TAG.
cachedir::ensure_tag(path)?; if !cachedir::is_tagged(path)? {
cachedir::add_tag(path)?;
}
// Add the .gitignore. // Add the .gitignore.
match fs::OpenOptions::new() let gitignore_path = path.join(".gitignore");
.write(true) if !gitignore_path.exists() {
.create_new(true) let mut file = fs::File::create(gitignore_path)?;
.open(path.join(".gitignore")) file.write_all(b"# Automatically created by ruff.\n*\n")?;
{
Ok(mut file) => file.write_all(b"# Automatically created by ruff.\n*\n")?,
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (),
Err(err) => return Err(err.into()),
} }
Ok(()) Ok(())

View File

@@ -252,7 +252,6 @@ mod test {
for file in [&pyproject_toml, &python_file, &notebook] { for file in [&pyproject_toml, &python_file, &notebook] {
fs::OpenOptions::new() fs::OpenOptions::new()
.create(true) .create(true)
.truncate(true)
.write(true) .write(true)
.mode(0o000) .mode(0o000)
.open(file)?; .open(file)?;

View File

@@ -1,5 +1,3 @@
use std::num::NonZeroUsize;
use crate::ExitStatus; use crate::ExitStatus;
use anyhow::Result; use anyhow::Result;
use ruff_linter::logging::LogLevel; use ruff_linter::logging::LogLevel;
@@ -11,11 +9,7 @@ use tracing_subscriber::{
}; };
use tracing_tree::time::Uptime; use tracing_tree::time::Uptime;
pub(crate) fn run_server( pub(crate) fn run_server(preview: bool, log_level: LogLevel) -> Result<ExitStatus> {
preview: bool,
worker_threads: NonZeroUsize,
log_level: LogLevel,
) -> Result<ExitStatus> {
if !preview { if !preview {
tracing::error!("--preview needs to be provided as a command line argument while the server is still unstable.\nFor example: `ruff server --preview`"); tracing::error!("--preview needs to be provided as a command line argument while the server is still unstable.\nFor example: `ruff server --preview`");
return Ok(ExitStatus::Error); return Ok(ExitStatus::Error);
@@ -39,7 +33,7 @@ pub(crate) fn run_server(
tracing::subscriber::set_global_default(subscriber)?; tracing::subscriber::set_global_default(subscriber)?;
let server = Server::new(worker_threads)?; let server = Server::new()?;
server.run().map(|()| ExitStatus::Success) server.run().map(|()| ExitStatus::Success)
} }

View File

@@ -2,7 +2,6 @@
use std::fs::File; use std::fs::File;
use std::io::{self, stdout, BufWriter, Write}; use std::io::{self, stdout, BufWriter, Write};
use std::num::NonZeroUsize;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::ExitCode; use std::process::ExitCode;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
@@ -149,13 +148,6 @@ pub fn run(
#[cfg(windows)] #[cfg(windows)]
assert!(colored::control::set_virtual_terminal(true).is_ok()); assert!(colored::control::set_virtual_terminal(true).is_ok());
// support FORCE_COLOR env var
if let Some(force_color) = std::env::var_os("FORCE_COLOR") {
if force_color.len() > 0 {
colored::control::set_override(true);
}
}
set_up_logging(global_options.log_level())?; set_up_logging(global_options.log_level())?;
if let Some(deprecated_alias_warning) = deprecated_alias_warning { if let Some(deprecated_alias_warning) = deprecated_alias_warning {
@@ -212,15 +204,10 @@ fn format(args: FormatCommand, global_options: GlobalConfigArgs) -> Result<ExitS
} }
} }
#[allow(clippy::needless_pass_by_value)] // TODO: remove once we start taking arguments from here
fn server(args: ServerCommand, log_level: LogLevel) -> Result<ExitStatus> { fn server(args: ServerCommand, log_level: LogLevel) -> Result<ExitStatus> {
let ServerCommand { preview } = args; let ServerCommand { preview } = args;
// by default, we set the number of worker threads to `num_cpus`, with a maximum of 4. commands::server::run_server(preview, log_level)
let worker_threads = num_cpus::get().max(4);
commands::server::run_server(
preview,
NonZeroUsize::try_from(worker_threads).expect("a non-zero worker thread count"),
log_level,
)
} }
pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> { pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {

View File

@@ -16,7 +16,7 @@ impl std::fmt::Display for PanicError {
} }
thread_local! { thread_local! {
static LAST_PANIC: std::cell::Cell<Option<PanicError>> = const { std::cell::Cell::new(None) }; static LAST_PANIC: std::cell::Cell<Option<PanicError>> = std::cell::Cell::new(None);
} }
/// [`catch_unwind`](std::panic::catch_unwind) wrapper that sets a custom [`set_hook`](std::panic::set_hook) /// [`catch_unwind`](std::panic::catch_unwind) wrapper that sets a custom [`set_hook`](std::panic::set_hook)

View File

@@ -70,7 +70,7 @@ pub(crate) fn version() -> VersionInfo {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use insta::{assert_json_snapshot, assert_snapshot}; use insta::{assert_display_snapshot, assert_json_snapshot};
use super::{CommitInfo, VersionInfo}; use super::{CommitInfo, VersionInfo};
@@ -80,7 +80,7 @@ mod tests {
version: "0.0.0".to_string(), version: "0.0.0".to_string(),
commit_info: None, commit_info: None,
}; };
assert_snapshot!(version); assert_display_snapshot!(version);
} }
#[test] #[test]
@@ -95,7 +95,7 @@ mod tests {
commits_since_last_tag: 0, commits_since_last_tag: 0,
}), }),
}; };
assert_snapshot!(version); assert_display_snapshot!(version);
} }
#[test] #[test]
@@ -110,7 +110,7 @@ mod tests {
commits_since_last_tag: 24, commits_since_last_tag: 24,
}), }),
}; };
assert_snapshot!(version); assert_display_snapshot!(version);
} }
#[test] #[test]

View File

@@ -523,7 +523,7 @@ from module import =
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
error: Failed to parse main.py:2:20: Expected an import name error: Failed to parse main.py:2:20: Unexpected token '='
"###); "###);
Ok(()) Ok(())

View File

@@ -731,11 +731,11 @@ fn stdin_parse_error() {
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
-:1:17: E999 SyntaxError: Expected an import name -:1:17: E999 SyntaxError: Unexpected token '='
Found 1 error. Found 1 error.
----- stderr ----- ----- stderr -----
error: Failed to parse at 1:17: Expected an import name error: Failed to parse at 1:17: Unexpected token '='
"###); "###);
} }
@@ -1353,7 +1353,6 @@ fn unreadable_pyproject_toml() -> Result<()> {
// Create an empty file with 000 permissions // Create an empty file with 000 permissions
fs::OpenOptions::new() fs::OpenOptions::new()
.create(true) .create(true)
.truncate(true)
.write(true) .write(true)
.mode(0o000) .mode(0o000)
.open(pyproject_toml)?; .open(pyproject_toml)?;

View File

@@ -1168,119 +1168,3 @@ def func():
Ok(()) Ok(())
} }
/// Per-file selects via ! negation in per-file-ignores
#[test]
fn negated_per_file_ignores() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint.per-file-ignores]
"!selected.py" = ["RUF"]
"#,
)?;
let selected = tempdir.path().join("selected.py");
fs::write(selected, "")?;
let ignored = tempdir.path().join("ignored.py");
fs::write(ignored, "")?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("--select")
.arg("RUF901")
.current_dir(&tempdir)
, @r###"
success: false
exit_code: 1
----- stdout -----
selected.py:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
Ok(())
}
#[test]
fn negated_per_file_ignores_absolute() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint.per-file-ignores]
"!src/**.py" = ["RUF"]
"#,
)?;
let src_dir = tempdir.path().join("src");
fs::create_dir(&src_dir)?;
let selected = src_dir.join("selected.py");
fs::write(selected, "")?;
let ignored = tempdir.path().join("ignored.py");
fs::write(ignored, "")?;
insta::with_settings!({filters => vec![
// Replace windows paths
(r"\\", "/"),
]}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("--select")
.arg("RUF901")
.current_dir(&tempdir)
, @r###"
success: false
exit_code: 1
----- stdout -----
src/selected.py:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
});
Ok(())
}
/// patterns are additive, can't use negative patterns to "un-ignore"
#[test]
fn negated_per_file_ignores_overlap() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint.per-file-ignores]
"*.py" = ["RUF"]
"!foo.py" = ["RUF"]
"#,
)?;
let foo_file = tempdir.path().join("foo.py");
fs::write(foo_file, "")?;
let bar_file = tempdir.path().join("bar.py");
fs::write(bar_file, "")?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("--select")
.arg("RUF901")
.current_dir(&tempdir)
, @r###"
success: true
exit_code: 0
----- stdout -----
All checks passed!
----- stderr -----
"###);
Ok(())
}

View File

@@ -50,10 +50,8 @@ file_resolver.exclude = [
"venv", "venv",
] ]
file_resolver.extend_exclude = [ file_resolver.extend_exclude = [
"crates/ruff/resources/",
"crates/ruff_linter/resources/", "crates/ruff_linter/resources/",
"crates/ruff_python_formatter/resources/", "crates/ruff_python_formatter/resources/",
"crates/ruff_python_parser/resources/",
] ]
file_resolver.force_exclude = false file_resolver.force_exclude = false
file_resolver.include = [ file_resolver.include = [
@@ -69,128 +67,128 @@ file_resolver.project_root = "[BASEPATH]"
linter.exclude = [] linter.exclude = []
linter.project_root = "[BASEPATH]" linter.project_root = "[BASEPATH]"
linter.rules.enabled = [ linter.rules.enabled = [
multiple-imports-on-one-line (E401), MultipleImportsOnOneLine,
module-import-not-at-top-of-file (E402), ModuleImportNotAtTopOfFile,
multiple-statements-on-one-line-colon (E701), MultipleStatementsOnOneLineColon,
multiple-statements-on-one-line-semicolon (E702), MultipleStatementsOnOneLineSemicolon,
useless-semicolon (E703), UselessSemicolon,
none-comparison (E711), NoneComparison,
true-false-comparison (E712), TrueFalseComparison,
not-in-test (E713), NotInTest,
not-is-test (E714), NotIsTest,
type-comparison (E721), TypeComparison,
bare-except (E722), BareExcept,
lambda-assignment (E731), LambdaAssignment,
ambiguous-variable-name (E741), AmbiguousVariableName,
ambiguous-class-name (E742), AmbiguousClassName,
ambiguous-function-name (E743), AmbiguousFunctionName,
io-error (E902), IOError,
syntax-error (E999), SyntaxError,
unused-import (F401), UnusedImport,
import-shadowed-by-loop-var (F402), ImportShadowedByLoopVar,
undefined-local-with-import-star (F403), UndefinedLocalWithImportStar,
late-future-import (F404), LateFutureImport,
undefined-local-with-import-star-usage (F405), UndefinedLocalWithImportStarUsage,
undefined-local-with-nested-import-star-usage (F406), UndefinedLocalWithNestedImportStarUsage,
future-feature-not-defined (F407), FutureFeatureNotDefined,
percent-format-invalid-format (F501), PercentFormatInvalidFormat,
percent-format-expected-mapping (F502), PercentFormatExpectedMapping,
percent-format-expected-sequence (F503), PercentFormatExpectedSequence,
percent-format-extra-named-arguments (F504), PercentFormatExtraNamedArguments,
percent-format-missing-argument (F505), PercentFormatMissingArgument,
percent-format-mixed-positional-and-named (F506), PercentFormatMixedPositionalAndNamed,
percent-format-positional-count-mismatch (F507), PercentFormatPositionalCountMismatch,
percent-format-star-requires-sequence (F508), PercentFormatStarRequiresSequence,
percent-format-unsupported-format-character (F509), PercentFormatUnsupportedFormatCharacter,
string-dot-format-invalid-format (F521), StringDotFormatInvalidFormat,
string-dot-format-extra-named-arguments (F522), StringDotFormatExtraNamedArguments,
string-dot-format-extra-positional-arguments (F523), StringDotFormatExtraPositionalArguments,
string-dot-format-missing-arguments (F524), StringDotFormatMissingArguments,
string-dot-format-mixing-automatic (F525), StringDotFormatMixingAutomatic,
f-string-missing-placeholders (F541), FStringMissingPlaceholders,
multi-value-repeated-key-literal (F601), MultiValueRepeatedKeyLiteral,
multi-value-repeated-key-variable (F602), MultiValueRepeatedKeyVariable,
expressions-in-star-assignment (F621), ExpressionsInStarAssignment,
multiple-starred-expressions (F622), MultipleStarredExpressions,
assert-tuple (F631), AssertTuple,
is-literal (F632), IsLiteral,
invalid-print-syntax (F633), InvalidPrintSyntax,
if-tuple (F634), IfTuple,
break-outside-loop (F701), BreakOutsideLoop,
continue-outside-loop (F702), ContinueOutsideLoop,
yield-outside-function (F704), YieldOutsideFunction,
return-outside-function (F706), ReturnOutsideFunction,
default-except-not-last (F707), DefaultExceptNotLast,
forward-annotation-syntax-error (F722), ForwardAnnotationSyntaxError,
redefined-while-unused (F811), RedefinedWhileUnused,
undefined-name (F821), UndefinedName,
undefined-export (F822), UndefinedExport,
undefined-local (F823), UndefinedLocal,
unused-variable (F841), UnusedVariable,
unused-annotation (F842), UnusedAnnotation,
raise-not-implemented (F901), RaiseNotImplemented,
] ]
linter.rules.should_fix = [ linter.rules.should_fix = [
multiple-imports-on-one-line (E401), MultipleImportsOnOneLine,
module-import-not-at-top-of-file (E402), ModuleImportNotAtTopOfFile,
multiple-statements-on-one-line-colon (E701), MultipleStatementsOnOneLineColon,
multiple-statements-on-one-line-semicolon (E702), MultipleStatementsOnOneLineSemicolon,
useless-semicolon (E703), UselessSemicolon,
none-comparison (E711), NoneComparison,
true-false-comparison (E712), TrueFalseComparison,
not-in-test (E713), NotInTest,
not-is-test (E714), NotIsTest,
type-comparison (E721), TypeComparison,
bare-except (E722), BareExcept,
lambda-assignment (E731), LambdaAssignment,
ambiguous-variable-name (E741), AmbiguousVariableName,
ambiguous-class-name (E742), AmbiguousClassName,
ambiguous-function-name (E743), AmbiguousFunctionName,
io-error (E902), IOError,
syntax-error (E999), SyntaxError,
unused-import (F401), UnusedImport,
import-shadowed-by-loop-var (F402), ImportShadowedByLoopVar,
undefined-local-with-import-star (F403), UndefinedLocalWithImportStar,
late-future-import (F404), LateFutureImport,
undefined-local-with-import-star-usage (F405), UndefinedLocalWithImportStarUsage,
undefined-local-with-nested-import-star-usage (F406), UndefinedLocalWithNestedImportStarUsage,
future-feature-not-defined (F407), FutureFeatureNotDefined,
percent-format-invalid-format (F501), PercentFormatInvalidFormat,
percent-format-expected-mapping (F502), PercentFormatExpectedMapping,
percent-format-expected-sequence (F503), PercentFormatExpectedSequence,
percent-format-extra-named-arguments (F504), PercentFormatExtraNamedArguments,
percent-format-missing-argument (F505), PercentFormatMissingArgument,
percent-format-mixed-positional-and-named (F506), PercentFormatMixedPositionalAndNamed,
percent-format-positional-count-mismatch (F507), PercentFormatPositionalCountMismatch,
percent-format-star-requires-sequence (F508), PercentFormatStarRequiresSequence,
percent-format-unsupported-format-character (F509), PercentFormatUnsupportedFormatCharacter,
string-dot-format-invalid-format (F521), StringDotFormatInvalidFormat,
string-dot-format-extra-named-arguments (F522), StringDotFormatExtraNamedArguments,
string-dot-format-extra-positional-arguments (F523), StringDotFormatExtraPositionalArguments,
string-dot-format-missing-arguments (F524), StringDotFormatMissingArguments,
string-dot-format-mixing-automatic (F525), StringDotFormatMixingAutomatic,
f-string-missing-placeholders (F541), FStringMissingPlaceholders,
multi-value-repeated-key-literal (F601), MultiValueRepeatedKeyLiteral,
multi-value-repeated-key-variable (F602), MultiValueRepeatedKeyVariable,
expressions-in-star-assignment (F621), ExpressionsInStarAssignment,
multiple-starred-expressions (F622), MultipleStarredExpressions,
assert-tuple (F631), AssertTuple,
is-literal (F632), IsLiteral,
invalid-print-syntax (F633), InvalidPrintSyntax,
if-tuple (F634), IfTuple,
break-outside-loop (F701), BreakOutsideLoop,
continue-outside-loop (F702), ContinueOutsideLoop,
yield-outside-function (F704), YieldOutsideFunction,
return-outside-function (F706), ReturnOutsideFunction,
default-except-not-last (F707), DefaultExceptNotLast,
forward-annotation-syntax-error (F722), ForwardAnnotationSyntaxError,
redefined-while-unused (F811), RedefinedWhileUnused,
undefined-name (F821), UndefinedName,
undefined-export (F822), UndefinedExport,
undefined-local (F823), UndefinedLocal,
unused-variable (F841), UnusedVariable,
unused-annotation (F842), UnusedAnnotation,
raise-not-implemented (F901), RaiseNotImplemented,
] ]
linter.per_file_ignores = {} linter.per_file_ignores = {}
linter.safety_table.forced_safe = [] linter.safety_table.forced_safe = []
@@ -233,7 +231,7 @@ linter.flake8_bandit.check_typed_exception = false
linter.flake8_bugbear.extend_immutable_calls = [] linter.flake8_bugbear.extend_immutable_calls = []
linter.flake8_builtins.builtins_ignorelist = [] linter.flake8_builtins.builtins_ignorelist = []
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|,\s)\d{4})* linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}(-\d{4})*
linter.flake8_copyright.author = none linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0 linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0 linter.flake8_errmsg.max_string_length = 0

View File

@@ -65,7 +65,7 @@ use seahash::SeaHasher;
/// The main reason is that hashes and cache keys have different constraints: /// The main reason is that hashes and cache keys have different constraints:
/// ///
/// * Cache keys are less performance sensitive: Hashes must be super fast to compute for performant hashed-collections. That's /// * Cache keys are less performance sensitive: Hashes must be super fast to compute for performant hashed-collections. That's
/// why some standard types don't implement [`Hash`] where it would be safe to implement [`CacheKey`], e.g. `HashSet` /// why some standard types don't implement [`Hash`] where it would be safe to to implement [`CacheKey`], e.g. `HashSet`
/// * Cache keys must be deterministic where hash keys do not have this constraint. That's why pointers don't implement [`CacheKey`] but they implement [`Hash`]. /// * Cache keys must be deterministic where hash keys do not have this constraint. That's why pointers don't implement [`CacheKey`] but they implement [`Hash`].
/// * Ideally, cache keys are portable /// * Ideally, cache keys are portable
/// ///

View File

@@ -22,7 +22,7 @@ ruff_python_formatter = { path = "../ruff_python_formatter" }
ruff_python_parser = { path = "../ruff_python_parser" } ruff_python_parser = { path = "../ruff_python_parser" }
ruff_python_stdlib = { path = "../ruff_python_stdlib" } ruff_python_stdlib = { path = "../ruff_python_stdlib" }
ruff_python_trivia = { path = "../ruff_python_trivia" } ruff_python_trivia = { path = "../ruff_python_trivia" }
ruff_workspace = { path = "../ruff_workspace", features = ["schemars"] } ruff_workspace = { path = "../ruff_workspace", features = ["schemars"]}
anyhow = { workspace = true } anyhow = { workspace = true }
clap = { workspace = true, features = ["wrap_help"] } clap = { workspace = true, features = ["wrap_help"] }
@@ -31,6 +31,7 @@ imara-diff = { workspace = true }
indicatif = { workspace = true } indicatif = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
libcst = { workspace = true } libcst = { workspace = true }
once_cell = { workspace = true }
pretty_assertions = { workspace = true } pretty_assertions = { workspace = true }
rayon = { workspace = true } rayon = { workspace = true }
regex = { workspace = true } regex = { workspace = true }

View File

@@ -134,7 +134,7 @@ impl Statistics {
} }
} }
/// We currently prefer the similarity index, but i'd like to keep this around /// We currently prefer the the similarity index, but i'd like to keep this around
#[allow(clippy::cast_precision_loss, unused)] #[allow(clippy::cast_precision_loss, unused)]
pub(crate) fn jaccard_index(&self) -> f32 { pub(crate) fn jaccard_index(&self) -> f32 {
self.intersection as f32 / (self.black_input + self.ruff_output + self.intersection) as f32 self.intersection as f32 / (self.black_input + self.ruff_output + self.intersection) as f32

View File

@@ -1,7 +1,6 @@
//! Generate Markdown documentation for applicable rules. //! Generate Markdown documentation for applicable rules.
#![allow(clippy::print_stdout, clippy::print_stderr)] #![allow(clippy::print_stdout, clippy::print_stderr)]
use std::collections::HashSet;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
@@ -98,13 +97,12 @@ pub(crate) fn main(args: &Args) -> Result<()> {
fn process_documentation(documentation: &str, out: &mut String, rule_name: &str) { fn process_documentation(documentation: &str, out: &mut String, rule_name: &str) {
let mut in_options = false; let mut in_options = false;
let mut after = String::new(); let mut after = String::new();
let mut referenced_options = HashSet::new();
// HACK: This is an ugly regex hack that's necessary because mkdocs uses // HACK: This is an ugly regex hack that's necessary because mkdocs uses
// a non-CommonMark-compliant Markdown parser, which doesn't support code // a non-CommonMark-compliant Markdown parser, which doesn't support code
// tags in link definitions // tags in link definitions
// (see https://github.com/Python-Markdown/markdown/issues/280). // (see https://github.com/Python-Markdown/markdown/issues/280).
let documentation = Regex::new(r"\[`([^`]*?)`]($|[^\[(])").unwrap().replace_all( let documentation = Regex::new(r"\[`([^`]*?)`]($|[^\[])").unwrap().replace_all(
documentation, documentation,
|caps: &Captures| { |caps: &Captures| {
format!( format!(
@@ -137,7 +135,6 @@ fn process_documentation(documentation: &str, out: &mut String, rule_name: &str)
let anchor = option.replace('.', "_"); let anchor = option.replace('.', "_");
out.push_str(&format!("- [`{option}`][{option}]\n")); out.push_str(&format!("- [`{option}`][{option}]\n"));
after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n")); after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n"));
referenced_options.insert(option);
continue; continue;
} }
@@ -145,20 +142,6 @@ fn process_documentation(documentation: &str, out: &mut String, rule_name: &str)
out.push_str(line); out.push_str(line);
} }
let re = Regex::new(r"\[`([^`]*?)`]\[(.*?)]").unwrap();
for (_, [option, _]) in re.captures_iter(&documentation).map(|c| c.extract()) {
if let Some(OptionEntry::Field(field)) = Options::metadata().find(option) {
if referenced_options.insert(option) {
let anchor = option.replace('.', "_");
after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n"));
}
if field.deprecated.is_some() {
eprintln!("Rule {rule_name} references deprecated option {option}.");
}
}
}
if !after.is_empty() { if !after.is_empty() {
out.push('\n'); out.push('\n');
out.push('\n'); out.push('\n');
@@ -176,7 +159,7 @@ mod tests {
process_documentation( process_documentation(
" "
See also [`lint.mccabe.max-complexity`] and [`lint.task-tags`]. See also [`lint.mccabe.max-complexity`] and [`lint.task-tags`].
Something [`else`][other]. Some [link](https://example.com). Something [`else`][other].
## Options ## Options
@@ -191,7 +174,7 @@ Something [`else`][other]. Some [link](https://example.com).
output, output,
" "
See also [`lint.mccabe.max-complexity`][lint.mccabe.max-complexity] and [`lint.task-tags`][lint.task-tags]. See also [`lint.mccabe.max-complexity`][lint.mccabe.max-complexity] and [`lint.task-tags`][lint.task-tags].
Something [`else`][other]. Some [link](https://example.com). Something [`else`][other].
## Options ## Options

View File

@@ -180,22 +180,8 @@ pub(crate) fn generate() -> String {
.map(|rule| (rule.upstream_category(&linter), rule)) .map(|rule| (rule.upstream_category(&linter), rule))
.into_group_map(); .into_group_map();
let mut rules_by_upstream_category: Vec<_> = rules_by_upstream_category.iter().collect();
// Sort the upstream categories alphabetically by prefix.
rules_by_upstream_category.sort_by(|(a, _), (b, _)| {
a.as_ref()
.map(|category| category.prefix)
.unwrap_or_default()
.cmp(
b.as_ref()
.map(|category| category.prefix)
.unwrap_or_default(),
)
});
if rules_by_upstream_category.len() > 1 { if rules_by_upstream_category.len() > 1 {
for (opt, rules) in rules_by_upstream_category { for (opt, rules) in &rules_by_upstream_category {
if opt.is_some() { if opt.is_some() {
let UpstreamCategoryAndPrefix { category, prefix } = opt.unwrap(); let UpstreamCategoryAndPrefix { category, prefix } = opt.unwrap();
table_out.push_str(&format!("#### {category} ({prefix})")); table_out.push_str(&format!("#### {category} ({prefix})"));

View File

@@ -71,14 +71,6 @@ impl Diagnostic {
} }
} }
/// Consumes `self` and returns a new `Diagnostic` with the given parent node.
#[inline]
#[must_use]
pub fn with_parent(mut self, parent: TextSize) -> Self {
self.set_parent(parent);
self
}
/// Set the location of the diagnostic's parent node. /// Set the location of the diagnostic's parent node.
#[inline] #[inline]
pub fn set_parent(&mut self, parent: TextSize) { pub fn set_parent(&mut self, parent: TextSize) {

View File

@@ -25,6 +25,11 @@ pub trait Violation: Debug + PartialEq + Eq {
/// The message used to describe the violation. /// The message used to describe the violation.
fn message(&self) -> String; fn message(&self) -> String;
/// The explanation used in documentation and elsewhere.
fn explanation() -> Option<&'static str> {
None
}
// TODO(micha): Move `fix_title` to `Fix`, add new `advice` method that is shown as an advice. // TODO(micha): Move `fix_title` to `Fix`, add new `advice` method that is shown as an advice.
// Change the `Diagnostic` renderer to show the advice, and render the fix message after the `Suggested fix: <here>` // Change the `Diagnostic` renderer to show the advice, and render the fix message after the `Suggested fix: <here>`
@@ -45,6 +50,11 @@ pub trait AlwaysFixableViolation: Debug + PartialEq + Eq {
/// The message used to describe the violation. /// The message used to describe the violation.
fn message(&self) -> String; fn message(&self) -> String;
/// The explanation used in documentation and elsewhere.
fn explanation() -> Option<&'static str> {
None
}
/// The title displayed for the available fix. /// The title displayed for the available fix.
fn fix_title(&self) -> String; fn fix_title(&self) -> String;
@@ -61,6 +71,10 @@ impl<V: AlwaysFixableViolation> Violation for V {
<Self as AlwaysFixableViolation>::message(self) <Self as AlwaysFixableViolation>::message(self)
} }
fn explanation() -> Option<&'static str> {
<Self as AlwaysFixableViolation>::explanation()
}
fn fix_title(&self) -> Option<String> { fn fix_title(&self) -> Option<String> {
Some(<Self as AlwaysFixableViolation>::fix_title(self)) Some(<Self as AlwaysFixableViolation>::fix_title(self))
} }

View File

@@ -24,6 +24,7 @@ tracing = { workspace = true }
unicode-width = { workspace = true } unicode-width = { workspace = true }
[dev-dependencies] [dev-dependencies]
insta = { workspace = true }
[features] [features]
serde = ["dep:serde", "ruff_text_size/serde"] serde = ["dep:serde", "ruff_text_size/serde"]

View File

@@ -138,7 +138,7 @@ pub const fn empty_line() -> Line {
/// ///
/// # Examples /// # Examples
/// ///
/// The line breaks are emitted as spaces if the enclosing `Group` fits on a single line: /// The line breaks are emitted as spaces if the enclosing `Group` fits on a a single line:
/// ``` /// ```
/// use ruff_formatter::{format, format_args}; /// use ruff_formatter::{format, format_args};
/// use ruff_formatter::prelude::*; /// use ruff_formatter::prelude::*;

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ruff_linter" name = "ruff_linter"
version = "0.4.1" version = "0.3.2"
publish = false publish = false
authors = { workspace = true } authors = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
@@ -15,6 +15,7 @@ license = { workspace = true }
[dependencies] [dependencies]
ruff_cache = { path = "../ruff_cache" } ruff_cache = { path = "../ruff_cache" }
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] } ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
ruff_index = { path = "../ruff_index" }
ruff_notebook = { path = "../ruff_notebook" } ruff_notebook = { path = "../ruff_notebook" }
ruff_macros = { path = "../ruff_macros" } ruff_macros = { path = "../ruff_macros" }
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] } ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
@@ -74,9 +75,11 @@ url = { workspace = true }
[dev-dependencies] [dev-dependencies]
insta = { workspace = true } insta = { workspace = true }
pretty_assertions = { workspace = true }
test-case = { workspace = true } test-case = { workspace = true }
# Disable colored output in tests # Disable colored output in tests
colored = { workspace = true, features = ["no-color"] } colored = { workspace = true, features = ["no-color"] }
tempfile = { workspace = true }
[features] [features]
default = [] default = []

View File

@@ -36,32 +36,3 @@ dictionary = {
# except: # except:
# except Foo: # except Foo:
# except Exception as e: print(e) # except Exception as e: print(e)
# Script tag without an opening tag (Error)
# requires-python = ">=3.11"
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
# Script tag (OK)
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
# Script tag without a closing tag (OK)
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests<3",
# "rich",
# ]

View File

@@ -17,9 +17,3 @@ urllib.request.URLopener().open(fullurl='http://www.google.com')
urllib.request.URLopener().open('http://www.google.com') urllib.request.URLopener().open('http://www.google.com')
urllib.request.URLopener().open('file:///foo/bar/baz') urllib.request.URLopener().open('file:///foo/bar/baz')
urllib.request.URLopener().open(url) urllib.request.URLopener().open(url)
urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'))
urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
urllib.request.urlopen(urllib.request.Request(url))

View File

@@ -31,7 +31,8 @@ def function(
kwonly_nonboolvalued_boolhint: bool = 1, kwonly_nonboolvalued_boolhint: bool = 1,
kwonly_nonboolvalued_boolstrhint: "bool" = 1, kwonly_nonboolvalued_boolstrhint: "bool" = 1,
**kw, **kw,
): ... ):
...
def used(do): def used(do):
@@ -130,27 +131,4 @@ class Fit:
def __post_init__(self, force: bool) -> None: def __post_init__(self, force: bool) -> None:
print(force) print(force)
Fit(force=True) Fit(force=True)
# https://github.com/astral-sh/ruff/issues/10356
from django.db.models import Case, Q, Value, When
qs.annotate(
is_foo_or_bar=Case(
When(Q(is_foo=True) | Q(is_bar=True)),
then=Value(True),
),
default=Value(False),
)
# https://github.com/astral-sh/ruff/issues/10485
from pydantic import Field
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
foo: bool = Field(True, exclude=True)

View File

@@ -6,25 +6,6 @@ def this_is_a_bug():
print("Ooh, callable! Or is it?") print("Ooh, callable! Or is it?")
def still_a_bug():
import builtins
o = object()
if builtins.hasattr(o, "__call__"):
print("B U G")
if builtins.getattr(o, "__call__", False):
print("B U G")
def trickier_fix_for_this_one():
o = object()
def callable(x):
return True
if hasattr(o, "__call__"):
print("STILL a bug!")
def this_is_fine(): def this_is_fine():
o = object() o = object()
if callable(o): if callable(o):

View File

@@ -1,20 +0,0 @@
def foo(a: list = []):
raise NotImplementedError("")
def bar(a: dict = {}):
""" This one also has a docstring"""
raise NotImplementedError("and has some text in here")
def baz(a: list = []):
"""This one raises a different exception"""
raise IndexError()
def qux(a: list = []):
raise NotImplementedError
def quux(a: list = []):
raise NotImplemented

View File

@@ -1,13 +0,0 @@
LIST = []
def foo(a: list = LIST): # B006
raise NotImplementedError("")
def foo(a: list = None): # OK
raise NotImplementedError("")
def foo(a: list | None = LIST): # OK
raise NotImplementedError("")

View File

@@ -23,15 +23,3 @@ def okay(data: custom.ImmutableTypeA = foo()):
def error_due_to_missing_import(data: List[str] = Depends(None)): def error_due_to_missing_import(data: List[str] = Depends(None)):
... ...
class Class:
pass
def okay(obj=Class()):
...
def error(obj=OtherClass()):
...

View File

@@ -64,6 +64,3 @@ setattr(*foo, "bar", None)
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1739800901 # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1739800901
getattr(self. getattr(self.
registration.registry, '__name__') registration.registry, '__name__')
import builtins
builtins.getattr(foo, "bar")

View File

@@ -9,69 +9,62 @@ B030:
try: try:
pass pass
except 1: # Error except 1: # error
pass pass
try: try:
pass pass
except (1, ValueError): # Error except (1, ValueError): # error
pass pass
try: try:
pass pass
except (ValueError, (RuntimeError, (KeyError, TypeError))): # Error except (ValueError, (RuntimeError, (KeyError, TypeError))): # error
pass pass
try: try:
pass pass
except (ValueError, *(RuntimeError, (KeyError, TypeError))): # Error except (ValueError, *(RuntimeError, (KeyError, TypeError))): # error
pass pass
try: try:
pass pass
except (*a, *(RuntimeError, (KeyError, TypeError))): # Error except (*a, *(RuntimeError, (KeyError, TypeError))): # error
pass
try:
pass
except (ValueError, *(RuntimeError, TypeError)): # ok
pass
try:
pass
except (ValueError, *[RuntimeError, *(TypeError,)]): # ok
pass pass
try: try:
pass pass
except* a + (RuntimeError, (KeyError, TypeError)): # Error except (*a, *b): # ok
pass pass
try: try:
pass pass
except (ValueError, *(RuntimeError, TypeError)): # OK except (*a, *(RuntimeError, TypeError)): # ok
pass
try:
pass
except (ValueError, *[RuntimeError, *(TypeError,)]): # OK
pass pass
try: try:
pass pass
except (*a, *b): # OK except (*a, *(b, c)): # ok
pass pass
try: try:
pass pass
except (*a, *(RuntimeError, TypeError)): # OK except (*a, *(*b, *c)): # ok
pass
try:
pass
except (*a, *(b, c)): # OK
pass
try:
pass
except (*a, *(*b, *c)): # OK
pass pass
@@ -81,52 +74,5 @@ def what_to_catch():
try: try:
pass pass
except what_to_catch(): # OK except what_to_catch(): # ok
pass
try:
pass
except (a, b) + (c, d): # OK
pass
try:
pass
except* (a, b) + (c, d): # OK
pass
try:
pass
except* (a, (b) + (c)): # OK
pass
try:
pass
except (a, b) + (c, d) + (e, f): # OK
pass
try:
pass
except a + (b, c): # OK
pass
try:
pass
except (ValueError, *(RuntimeError, TypeError), *((ArithmeticError,) + (EOFError,))):
pass
try:
pass
except ((a, b) + (c, d)) + ((e, f) + (g)): # OK
pass
try:
pass
except (a, b) * (c, d): # B030
pass pass

View File

@@ -174,49 +174,6 @@ for (_key1, _key2), (_value1, _value2) in groupby(
collect_shop_items("Jane", group[1]) collect_shop_items("Jane", group[1])
collect_shop_items("Joe", group[1]) collect_shop_items("Joe", group[1])
# Shouldn't trigger the warning when there is a continue, break statement.
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
continue
elif _section == "frozen items":
collect_shop_items(shopper, section_items)
break
collect_shop_items(shopper, section_items)
# Shouldn't trigger the warning when there is a return statement.
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
return
elif _section == "frozen items":
return section_items
collect_shop_items(shopper, section_items)
# Should trigger the warning for duplicate access, even if is a return statement after.
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items)
return
# Should trigger the warning for duplicate access, even if is a return in another branch.
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
return
elif _section == "frozen items":
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items)
# Should trigger, since only one branch has a return statement.
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
return
elif _section == "frozen items":
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items) # B031
# Let's redefine the `groupby` function to make sure we pick up the correct one. # Let's redefine the `groupby` function to make sure we pick up the correct one.
# NOTE: This should always be at the end of the file. # NOTE: This should always be at the end of the file.

View File

@@ -23,7 +23,3 @@ zip([1, 2, 3], repeat(1, times=None))
# Errors (limited iterators). # Errors (limited iterators).
zip([1, 2, 3], repeat(1, 1)) zip([1, 2, 3], repeat(1, 1))
zip([1, 2, 3], repeat(1, times=4)) zip([1, 2, 3], repeat(1, times=4))
import builtins
# Still an error even though it uses the qualified name
builtins.zip([1, 2, 3])

View File

@@ -1,160 +0,0 @@
"""
Should emit:
B909 - on lines 11, 25, 26, 40, 46
"""
# lists
some_list = [1, 2, 3]
some_other_list = [1, 2, 3]
for elem in some_list:
# errors
some_list.remove(0)
del some_list[2]
some_list.append(elem)
some_list.sort()
some_list.reverse()
some_list.clear()
some_list.extend([1, 2])
some_list.insert(1, 1)
some_list.pop(1)
some_list.pop()
# conditional break should error
if elem == 2:
some_list.remove(0)
if elem == 3:
break
# non-errors
some_other_list.remove(elem)
del some_list
del some_other_list
found_idx = some_list.index(elem)
some_list = 3
# unconditional break should not error
if elem == 2:
some_list.remove(elem)
break
# dicts
mydicts = {"a": {"foo": 1, "bar": 2}}
for elem in mydicts:
# errors
mydicts.popitem()
mydicts.setdefault("foo", 1)
mydicts.update({"foo": "bar"})
# no errors
elem.popitem()
elem.setdefault("foo", 1)
elem.update({"foo": "bar"})
# sets
myset = {1, 2, 3}
for _ in myset:
# errors
myset.update({4, 5})
myset.intersection_update({4, 5})
myset.difference_update({4, 5})
myset.symmetric_difference_update({4, 5})
myset.add(4)
myset.discard(3)
# no errors
del myset
# members
class A:
some_list: list
def __init__(self, ls):
self.some_list = list(ls)
a = A((1, 2, 3))
# ensure member accesses are handled as errors
for elem in a.some_list:
a.some_list.remove(0)
del a.some_list[2]
# Augassign should error
foo = [1, 2, 3]
bar = [4, 5, 6]
for _ in foo:
foo *= 2
foo += bar
foo[1] = 9
foo[1:2] = bar
foo[1:2:3] = bar
foo = {1, 2, 3}
bar = {4, 5, 6}
for _ in foo: # should error
foo |= bar
foo &= bar
foo -= bar
foo ^= bar
# more tests for unconditional breaks - should not error
for _ in foo:
foo.remove(1)
for _ in bar:
bar.remove(1)
break
break
# should not error
for _ in foo:
foo.remove(1)
for _ in bar:
...
break
# should error (?)
for _ in foo:
foo.remove(1)
if bar:
bar.remove(1)
break
break
# should error
for _ in foo:
if bar:
pass
else:
foo.remove(1)
# should error
for elem in some_list:
if some_list.pop() == 2:
pass
# should not error
for elem in some_list:
if some_list.pop() == 2:
break
# should error
for elem in some_list:
if some_list.pop() == 2:
pass
else:
break
# should not error
for elem in some_list:
del some_list[elem]
some_list[elem] = 1
some_list.remove(elem)
some_list.discard(elem)

View File

@@ -1,20 +1,11 @@
# Cannot combine with C416. Should use list comprehension here.
even_nums = list(2 * x for x in range(3))
odd_nums = list(
2 * x + 1 for x in range(3)
)
# Short-circuit case, combine with C416 and should produce x = list(range(3))
x = list(x for x in range(3)) x = list(x for x in range(3))
x = list( x = list(
x for x in range(3) x for x in range(3)
) )
# Not built-in list.
def list(*args, **kwargs): def list(*args, **kwargs):
return None return None
list(2 * x for x in range(3))
list(x for x in range(3)) list(x for x in range(3))

View File

@@ -1,30 +1,20 @@
# Cannot conbime with C416. Should use set comprehension here. x = set(x for x in range(3))
even_nums = set(2 * x for x in range(3)) x = set(x for x in range(3))
odd_nums = set( y = f"{set(a if a < 6 else 0 for a in range(3))}"
2 * x + 1 for x in range(3) _ = "{}".format(set(a if a < 6 else 0 for a in range(3)))
) print(f"Hello {set(a for a in range(3))} World")
small_nums = f"{set(a if a < 6 else 0 for a in range(3))}"
def f(x): def f(x):
return x return x
print(f'Hello {set(a for a in "abc")} World')
print(f"Hello {set(a for a in 'abc')} World")
print(f"Hello {set(f(a) for a in 'abc')} World") print(f"Hello {set(f(a) for a in 'abc')} World")
print(f"Hello { set(f(a) for a in 'abc') } World")
# Short-circuit case, combine with C416 and should produce x = set(range(3))
x = set(x for x in range(3))
x = set(
x for x in range(3)
)
print(f"Hello {set(a for a in range(3))} World")
print(f"{set(a for a in 'abc') - set(a for a in 'ab')}") print(f"{set(a for a in 'abc') - set(a for a in 'ab')}")
print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }") print(f"{ set(a for a in 'abc') - set(a for a in 'ab') }")
# The fix generated for this diagnostic is incorrect, as we add additional space
# Not built-in set. # around the set comprehension.
def set(*args, **kwargs): print(f"{ {set(a for a in 'abc')} }")
return None
set(2 * x for x in range(3))
set(x for x in range(3))

View File

@@ -16,11 +16,3 @@ tuple( # comment
tuple([ # comment tuple([ # comment
1, 2 1, 2
]) ])
tuple((
1,
))
t6 = tuple([1])
t7 = tuple((1,))
t8 = tuple([1,])

View File

@@ -13,10 +13,6 @@ all(x.id for x in bar)
all(x.id for x in bar) all(x.id for x in bar)
any(x.id for x in bar) any(x.id for x in bar)
all((x.id for x in bar)) all((x.id for x in bar))
# we don't lint on these in stable yet
sum([x.val for x in bar])
min([x.val for x in bar])
max([x.val for x in bar])
async def f() -> bool: async def f() -> bool:

View File

@@ -1,8 +0,0 @@
sum([x.val for x in bar])
min([x.val for x in bar])
max([x.val for x in bar])
# Ok
sum(x.val for x in bar)
min(x.val for x in bar)
max(x.val for x in bar)

View File

@@ -1,3 +0,0 @@
# no lint if shadowed
def all(x): pass
all([x.id for x in bar])

View File

@@ -33,9 +33,3 @@ from datetime import datetime
# no replace orastimezone unqualified # no replace orastimezone unqualified
datetime.strptime("something", "something") datetime.strptime("something", "something")
# F-strings
datetime.strptime("something", f"%Y-%m-%dT%H:%M:%S{('.%f' if millis else '')}%z")
datetime.strptime("something", f"%Y-%m-%d %H:%M:%S%z")
# F-string is implicitly concatenated to another string
datetime.strptime("something", f"%Y-%m-%dT%H:%M:%S{('.%f' if millis else '')}" "%z")

View File

@@ -21,7 +21,6 @@ def unconventional_aliases():
import tkinter as tkr import tkinter as tkr
import networkx as nxy import networkx as nxy
def conventional_aliases(): def conventional_aliases():
import altair as alt import altair as alt
import matplotlib.pyplot as plt import matplotlib.pyplot as plt

View File

@@ -1,10 +0,0 @@
def no_alias():
from django.conf import settings
def conventional_alias():
from django.conf import settings as settings
def unconventional_alias():
from django.conf import settings as s

View File

@@ -1,12 +1,9 @@
def func(): import logging
import logging
logging.WARN # LOG009 logging.WARN # LOG009
logging.WARNING # OK logging.WARNING # OK
from logging import WARN, WARNING
def func(): WARN # LOG009
from logging import WARN, WARNING WARNING # OK
WARN # LOG009
WARNING # OK

View File

@@ -227,11 +227,3 @@ class Repro[int](Protocol):
def impl(self) -> str: def impl(self) -> str:
"""Docstring""" """Docstring"""
return self.func() return self.func()
import typing
if typing.TYPE_CHECKING:
def contains_meaningful_ellipsis() -> list[int]:
"""Allow this in a TYPE_CHECKING block."""
...

View File

@@ -1,9 +1,6 @@
# PIE808 # PIE808
range(0, 10) range(0, 10)
import builtins
builtins.range(0, 10)
# OK # OK
range(x, 10) range(x, 10)
range(-15, 10) range(-15, 10)

View File

@@ -1,14 +0,0 @@
"""Tests to ensure we correctly rename references inside `__all__`"""
from collections.abc import Set
__all__ = ["Set"]
if True:
__all__ += [r'''Set''']
if 1:
__all__ += ["S" "e" "t"]
if not False:
__all__ += ["Se" 't']

View File

@@ -1,14 +0,0 @@
"""Tests to ensure we correctly rename references inside `__all__`"""
from collections.abc import Set
__all__ = ["Set"]
if True:
__all__ += [r'''Set''']
if 1:
__all__ += ["S" "e" "t"]
if not False:
__all__ += ["Se" 't']

View File

@@ -1,6 +0,0 @@
"""
Tests for PYI025 where the import is marked as re-exported
through usage of a "redundant" `import Set as Set` alias
"""
from collections.abc import Set as Set # PYI025 triggered but fix is not marked as safe

View File

@@ -1,6 +0,0 @@
"""
Tests for PYI025 where the import is marked as re-exported
through usage of a "redundant" `import Set as Set` alias
"""
from collections.abc import Set as Set # PYI025 triggered but fix is not marked as safe

View File

@@ -195,13 +195,6 @@ class BadAsyncIterator(collections.abc.AsyncIterator[str]):
def __aiter__(self) -> typing.AsyncIterator[str]: def __aiter__(self) -> typing.AsyncIterator[str]:
... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax) ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
class SubclassOfBadIterator3(BadIterator3):
def __iter__(self) -> Iterator[int]: # Y034
...
class SubclassOfBadAsyncIterator(BadAsyncIterator):
def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034
...
class AsyncIteratorReturningAsyncIterable: class AsyncIteratorReturningAsyncIterable:
def __aiter__(self) -> AsyncIterable[str]: def __aiter__(self) -> AsyncIterable[str]:
@@ -232,11 +225,6 @@ class MetaclassInWhichSelfCannotBeUsed4(ABCMeta):
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ... async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ... def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ...
class SubclassOfMetaclassInWhichSelfCannotBeUsed(MetaclassInWhichSelfCannotBeUsed4):
def __new__(cls) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
def __enter__(self) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
async def __aenter__(self) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
def __isub__(self, other: SubclassOfMetaclassInWhichSelfCannotBeUsed) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
class Abstract(Iterator[str]): class Abstract(Iterator[str]):
@abstractmethod @abstractmethod

View File

@@ -73,10 +73,3 @@ class BadFive:
class BadSix: class BadSix:
def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default
def isolated_scope():
from builtins import type as Type
class ShouldNotError:
def __exit__(self, typ: Type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...

View File

@@ -79,14 +79,5 @@ def test_single_list_of_lists(param):
@pytest.mark.parametrize("a", [1, 2]) @pytest.mark.parametrize("a", [1, 2])
@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6))) @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
@pytest.mark.parametrize("d", [3,]) def test_multiple_decorators(a, b, c):
@pytest.mark.parametrize(
"d",
[("3", "4")],
)
@pytest.mark.parametrize(
"e",
[("3", "4"),],
)
def test_multiple_decorators(a, b, c, d, e):
pass pass

View File

@@ -51,8 +51,3 @@ def test_error_parentheses_trailing_comma(x):
@pytest.mark.parametrize("x", [1, 2]) @pytest.mark.parametrize("x", [1, 2])
def test_ok(x): def test_ok(x):
... ...
@pytest.mark.parametrize('data, spec', [(1.0, 1.0), (1.0, 1.0)])
def test_numbers(data, spec):
...

View File

@@ -1,9 +0,0 @@
class SingleLineDocstrings():
""'Start with empty string' ' and lint docstring safely'
""" Not a docstring """
def foo(self, bar="""not a docstring"""):
""'Start with empty string' ' and lint docstring safely'
pass
class Nested(foo()[:]): ""'Start with empty string' ' and lint docstring safely'; pass

View File

@@ -1,9 +0,0 @@
class SingleLineDocstrings():
"Do not"' start with empty string' ' and lint docstring safely'
""" Not a docstring """
def foo(self, bar="""not a docstring"""):
"Do not"' start with empty string' ' and lint docstring safely'
pass
class Nested(foo()[:]): "Do not"' start with empty string' ' and lint docstring safely'; pass

View File

@@ -1,5 +0,0 @@
""'Start with empty string' ' and lint docstring safely'
def foo():
pass
""" this is not a docstring """

View File

@@ -1,5 +0,0 @@
"Do not"' start with empty string' ' and lint docstring safely'
def foo():
pass
""" this is not a docstring """

View File

@@ -1,9 +0,0 @@
class SingleLineDocstrings():
''"Start with empty string" ' and lint docstring safely'
''' Not a docstring '''
def foo(self, bar='''not a docstring'''):
''"Start with empty string" ' and lint docstring safely'
pass
class Nested(foo()[:]): ''"Start with empty string" ' and lint docstring safely'; pass

View File

@@ -1,9 +0,0 @@
class SingleLineDocstrings():
'Do not'" start with empty string" ' and lint docstring safely'
''' Not a docstring '''
def foo(self, bar='''not a docstring'''):
'Do not'" start with empty string" ' and lint docstring safely'
pass
class Nested(foo()[:]): 'Do not'" start with empty string" ' and lint docstring safely'; pass

View File

@@ -1,5 +0,0 @@
''"Start with empty string" ' and lint docstring safely'
def foo():
pass
""" this is not a docstring """

View File

@@ -1,5 +0,0 @@
'Do not'" start with empty string" ' and lint docstring safely'
def foo():
pass
""" this is not a docstring """

View File

@@ -1,7 +0,0 @@
"""This is a docstring."""
this_is_an_inline_string = "double quote string"
this_is_a_multiline_string = """
double quote string
"""

View File

@@ -1,2 +0,0 @@
s = ""'Start with empty string' ' and lint docstring safely'
s = "Do not"' start with empty string' ' and lint docstring safely'

View File

@@ -2,8 +2,3 @@ this_should_be_linted = 'single quote string'
this_should_be_linted = u'double quote string' this_should_be_linted = u'double quote string'
this_should_be_linted = f'double quote string' this_should_be_linted = f'double quote string'
this_should_be_linted = f'double {"quote"} string' this_should_be_linted = f'double {"quote"} string'
# https://github.com/astral-sh/ruff/issues/10546
x: "Literal['foo', 'bar']"
# https://github.com/astral-sh/ruff/issues/10761
f"Before {f'x {x}' if y else f'foo {z}'} after"

View File

@@ -1,2 +0,0 @@
s = ''"Start with empty string" ' and lint docstring safely'
s = 'Do not'" start with empty string" ' and lint docstring safely'

View File

@@ -406,18 +406,3 @@ def foo():
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
y = 2 y = 2
return y return y
# See: https://github.com/astral-sh/ruff/issues/10732
def func(a: dict[str, int]) -> list[dict[str, int]]:
services: list[dict[str, int]]
if "services" in a:
services = a["services"]
return services
# See: https://github.com/astral-sh/ruff/issues/10732
def func(a: dict[str, int]) -> list[dict[str, int]]:
if "services" in a:
services = a["services"]
return services

View File

@@ -52,62 +52,35 @@ def f():
return False return False
def f():
# SIM103
if a:
return False
else:
return True
def f():
# OK
if a:
return False
else:
return False
def f():
# OK
if a:
return True
else:
return True
def f(): def f():
# SIM103 (but not fixable) # SIM103 (but not fixable)
if a:
return False
else:
return True
def f():
# OK
if a:
return False
else:
return False
def f():
# OK
if a:
return True
else:
return True
def f():
# OK
def bool(): def bool():
return False return False
if a: if a:
return True return True
else: else:
return False return False
def f():
# SIM103
if keys is not None and notice.key not in keys:
return False
else:
return True
###
# Positive cases (preview)
###
def f():
# SIM103
if a:
return True
return False
def f():
# SIM103
if a:
return False
return True

View File

@@ -58,8 +58,3 @@ for key in (
.keys() .keys()
): ):
continue continue
from builtins import dict as SneakyDict
d = SneakyDict()
key in d.keys() # SIM118

View File

@@ -11,11 +11,3 @@ from enum import Enum
class Fine(str, Enum): # Ok class Fine(str, Enum): # Ok
__slots__ = ["foo"] __slots__ = ["foo"]
class SubEnum(Enum):
pass
class Ok(str, SubEnum): # Ok
pass

View File

@@ -19,9 +19,3 @@ class Bad(Tuple[str, int, float]): # SLOT001
class Good(Tuple[str, int, float]): # OK class Good(Tuple[str, int, float]): # OK
__slots__ = ("foo",) __slots__ = ("foo",)
import builtins
class AlsoBad(builtins.tuple[int, int]): # SLOT001
pass

View File

@@ -6,14 +6,6 @@ class Bad(namedtuple("foo", ["str", "int"])): # SLOT002
pass pass
class UnusualButStillBad(NamedTuple("foo", [("x", int, "y", int)])): # SLOT002
pass
class UnusualButOkay(NamedTuple("foo", [("x", int, "y", int)])):
__slots__ = ()
class Good(namedtuple("foo", ["str", "int"])): # OK class Good(namedtuple("foo", ["str", "int"])): # OK
__slots__ = ("foo",) __slots__ = ("foo",)

View File

@@ -3,5 +3,3 @@ import ruff
import leading_prefix import leading_prefix
import os import os
from . import leading_prefix from . import leading_prefix
from .. import trailing_prefix
from ruff import check

View File

@@ -1,6 +0,0 @@
from astropy.constants import hbar as
from numpy import pi as π
import numpy as ℂℇℊℋℌℍℎℐℑℒℓℕℤΩℨKÅℬℭℯℰℱℹℴ
import numpy as CƐgHHHhIILlNZΩZKÅBCeEFio
h = 2 * π *

View File

@@ -104,5 +104,3 @@ def func():
np.unicode_("asf") np.unicode_("asf")
np.who() np.who()
np.row_stack(([1,2], [3,4]))

View File

@@ -21,10 +21,3 @@ class D(TypedDict):
mixedCase: bool mixedCase: bool
_mixedCase: list _mixedCase: list
mixed_Case: set mixed_Case: set
class E(D):
lower: int
CONSTANT: str
mixedCase: bool
_mixedCase: list
mixed_Case: set

View File

@@ -80,8 +80,3 @@ for i in list(foo_list): # OK
for i in list(foo_list): # OK for i in list(foo_list): # OK
if True: if True:
del foo_list[i + 1] del foo_list[i + 1]
import builtins
for i in builtins.list(nested_tuple): # PERF101
pass

View File

@@ -81,11 +81,3 @@ def foo():
result = {} result = {}
for idx, name in enumerate(fruit): for idx, name in enumerate(fruit):
result[name] = idx # PERF403 result[name] = idx # PERF403
def foo():
from builtins import dict as SneakyDict
fruit = ["apple", "pear", "orange"]
result = SneakyDict()
for idx, name in enumerate(fruit):
result[name] = idx # PERF403

View File

@@ -47,60 +47,4 @@ snapshot.file_uri[len(f's3://{self.s3_bucket_name}/'):]
{len(f's3://{self.s3_bucket_name}/'):1} {len(f's3://{self.s3_bucket_name}/'):1}
#: Okay #: Okay
a = (1,) a = (1,
# https://github.com/astral-sh/ruff/issues/10113
"""Minimal repo."""
def main() -> None:
"""Primary function."""
results = {
"k1": [1],
"k2":[2],
}
results_in_tuple = (
{
"k1": [1],
"k2":[2],
},
)
results_in_list = [
{
"k1": [1],
"k2":[2],
}
]
results_in_list_first = [
{
"k2":[2],
}
]
x = [
{
"k1":[2], # E231
"k2": [2:4],
"k3":[2], # E231
"k4": [2],
"k5": [2],
"k6": [1, 2, 3, 4,5,6,7] # E231
},
{
"k1": [
{
"ka":[2,3], # E231
},
{
"kb": [2,3], # E231
},
{
"ka":[2, 3], # E231
"kb": [2, 3], # Ok
"kc": [2, 3], # Ok
"kd": [2,3], # E231
"ke":[2,3], # E231
},
]
}
]

View File

@@ -1,228 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "palRUQyD-U6u"
},
"outputs": [],
"source": [
"some_string = \"123123\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "UWdDLRyf-Zz0"
},
"outputs": [],
"source": [
"some_computation = 1 + 1"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "YreT1sTr-c32"
},
"outputs": [],
"source": [
"some_computation"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "V48ppml7-h0f"
},
"outputs": [],
"source": [
"def fn():\n",
" print(\"Hey!\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "cscw_8Xv-lYQ"
},
"outputs": [],
"source": [
"fn()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# E301\n",
"class Class:\n",
" \"\"\"Class for minimal repo.\"\"\"\n",
"\n",
" def method(cls) -> None:\n",
" pass\n",
" @classmethod\n",
" def cls_method(cls) -> None:\n",
" pass\n",
"# end"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# E302\n",
"def a():\n",
" pass\n",
"\n",
"def b():\n",
" pass\n",
"# end"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# E303\n",
"def fn():\n",
" _ = None\n",
"\n",
"\n",
" # arbitrary comment\n",
"\n",
" def inner(): # E306 not expected (pycodestyle detects E306)\n",
" pass\n",
"# end"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# E303"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\n",
"\n",
"\n",
"\n",
"def fn():\n",
"\tpass\n",
"# end"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# E304\n",
"@decorator\n",
"\n",
"def function():\n",
" pass\n",
"# end"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# E305:7:1\n",
"def fn():\n",
" print()\n",
"\n",
" # comment\n",
"\n",
" # another comment\n",
"fn()\n",
"# end"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# E306:3:5\n",
"def a():\n",
" x = 1\n",
" def b():\n",
" pass\n",
"# end"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Ok\n",
"def function1():\n",
"\tpass\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\n",
"def function2():\n",
"\tpass\n",
"# end"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -445,39 +445,6 @@ def test():
# end # end
# no error
class Foo:
"""Demo."""
@overload
def bar(self, x: int) -> int: ...
@overload
def bar(self, x: str) -> str: ...
def bar(self, x: int | str) -> int | str:
return x
# end
# no error
@overload
def foo(x: int) -> int: ...
@overload
def foo(x: str) -> str: ...
def foo(x: int | str) -> int | str:
if not isinstance(x, (int, str)):
raise TypeError
return x
# end
# no error
def foo(self, x: int) -> int: ...
def bar(self, x: str) -> str: ...
def baz(self, x: int | str) -> int | str:
return x
# end
# E301 # E301
class Class(object): class Class(object):
@@ -522,20 +489,6 @@ class Class:
# end # end
# E301
class Foo:
"""Demo."""
@overload
def bar(self, x: int) -> int: ...
@overload
def bar(self, x: str) -> str:
...
def bar(self, x: int | str) -> int | str:
return x
# end
# E302 # E302
"""Main module.""" """Main module."""
def fn(): def fn():
@@ -627,23 +580,6 @@ class Test:
# end # end
# E302
class A:...
class B: ...
# end
# E302
@overload
def fn(a: int) -> int: ...
@overload
def fn(a: str) -> str: ...
def fn(a: int | str) -> int | str:
...
# end
# E303 # E303
def fn(): def fn():
_ = None _ = None

View File

@@ -87,37 +87,6 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "a51463ee-091c-44b4-9069-c03bf7e3bf83",
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"import pathlib"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0ddc937e-6c19-475f-b108-9405aa1af4f1",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "285041d2-a76c-4ff3-8ff2-0131bbf66016",
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"%%time\n",
"import pathlib"
]
} }
], ],
"metadata": { "metadata": {

View File

@@ -82,8 +82,3 @@ class Bar:
""" """
This is a long sentence that ends with a shortened URL and, therefore, could easily be broken across multiple lines ([source](https://ruff.rs)) This is a long sentence that ends with a shortened URL and, therefore, could easily be broken across multiple lines ([source](https://ruff.rs))
""" """
# OK
# SPDX-FileCopyrightText: Copyright 2012-2015 Charlie Marsh <very-long-email-address@fake.com>
# SPDX-License-Identifier: a very long license identifier that exceeds the line length limit

View File

@@ -138,8 +138,3 @@ np.dtype(int) == float
#: E721 #: E721
dtype == float dtype == float
import builtins
if builtins.type(res) == memoryview: # E721
pass

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