Compare commits

..

9 Commits

Author SHA1 Message Date
Charlie Marsh
e9fc63331a Reverts 2023-05-21 15:22:52 -04:00
Charlie Marsh
df3b95a73d clean up 2023-05-21 14:52:04 -04:00
Charlie Marsh
a4432102f1 Theres enough here for a proposal 2023-05-21 14:19:41 -04:00
Charlie Marsh
3295ccfbc4 Rename some stuff 2023-05-21 14:19:41 -04:00
Charlie Marsh
7b315b84e2 Make generic 2023-05-21 14:19:41 -04:00
Charlie Marsh
bb2adb3017 Introduce traits 2023-05-21 14:19:41 -04:00
Charlie Marsh
5536d2befc Benchmark UP 2023-05-21 14:19:41 -04:00
Charlie Marsh
b4824979b0 Separate struct 2023-05-21 14:19:41 -04:00
Charlie Marsh
8a2f58065e Dispatch rules off a vector 2023-05-21 14:19:41 -04:00
2116 changed files with 56582 additions and 97624 deletions

View File

@@ -1,6 +1,6 @@
[alias]
dev = "run --package ruff_dev --bin ruff_dev"
benchmark = "bench -p ruff_benchmark --bench linter --bench formatter --"
benchmark = "bench -p ruff_benchmark --"
[target.'cfg(all())']
rustflags = [

View File

@@ -1,46 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
{
"name": "Ruff",
"image": "mcr.microsoft.com/devcontainers/rust:0-1-bullseye",
"mounts": [
{
"source": "devcontainer-cargo-cache-${devcontainerId}",
"target": "/usr/local/cargo",
"type": "volume"
}
],
"customizations": {
"codespaces": {
"openFiles": [
"CONTRIBUTING.md"
]
},
"vscode": {
"extensions": [
"ms-python.python",
"rust-lang.rust-analyzer",
"serayuzgur.crates",
"tamasfe.even-better-toml",
"Swellaby.vscode-rust-test-adapter",
"charliermarsh.ruff"
],
"settings": {
"rust-analyzer.updates.askBeforeDownload": false
}
}
},
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/python": {
"installTools": false
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
"postCreateCommand": ".devcontainer/post-create.sh"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env bash
rustup default < rust-toolchain
rustup component add clippy rustfmt
cargo install cargo-insta
cargo fetch
pip install maturin pre-commit

View File

@@ -14,7 +14,4 @@ indent_size = 2
indent_size = 4
[*.snap]
trim_trailing_whitespace = false
[*.md]
max_line_length = 100
trim_trailing_whitespace = false

9
.github/CODEOWNERS vendored
View File

@@ -1,9 +0,0 @@
# GitHub code owners file. For more info: https://help.github.com/articles/about-codeowners/
#
# - Comment lines begin with `#` character.
# - Each line is a file pattern followed by one or more owners.
# - The '*' pattern is global owners.
# - Order is important. The last matching pattern has the most precedence.
# Jupyter
/crates/ruff/src/jupyter/ @dhruvmanila

View File

@@ -1,15 +0,0 @@
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:
- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->
## Summary
<!-- What's the purpose of the change? What does it do, and why? -->
## Test Plan
<!-- How was it tested? -->

5
.github/release.yml vendored
View File

@@ -1,9 +1,5 @@
# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
changelog:
exclude:
labels:
- internal
- documentation
categories:
- title: Breaking Changes
labels:
@@ -15,7 +11,6 @@ changelog:
- title: Settings
labels:
- configuration
- cli
- title: Bug Fixes
labels:
- bug

View File

@@ -14,7 +14,7 @@ jobs:
name: "Run | ${{ matrix.os }}"
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
@@ -29,7 +29,10 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: "PR - Build benchmarks"
run: cargo bench -p ruff_benchmark --no-run
uses: actions-rs/cargo@v1
with:
command: bench
args: -p ruff_benchmark --no-run
- name: "PR - Run benchmarks"
run: cargo benchmark --save-baseline=pr
@@ -44,7 +47,10 @@ jobs:
run: rustup show
- name: "Main - Build benchmarks"
run: cargo bench -p ruff_benchmark --no-run
uses: actions-rs/cargo@v1
with:
command: bench
args: -p ruff_benchmark --no-run
- name: "Main - Run benchmarks"
run: cargo benchmark --save-baseline=main
@@ -72,10 +78,11 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: taiki-e/install-action@cargo-binstall
- name: "Install critcmp"
uses: taiki-e/install-action@v2
with:
tool: critcmp
run: cargo binstall critcmp -y
- name: "Linux | Download PR benchmark results"
uses: actions/download-artifact@v3

View File

@@ -2,7 +2,7 @@ name: CI
on:
push:
branches: [ main ]
branches: [main]
pull_request:
workflow_dispatch:
@@ -54,7 +54,7 @@ jobs:
cargo-test:
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
name: "cargo test | ${{ matrix.os }}"
steps:
@@ -76,9 +76,6 @@ jobs:
cargo insta test --all --all-features
git diff --exit-code
- run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
# Skipped as it's currently broken. The resource were moved from the
# ruff_cli to ruff crate, but this test was not updated.
if: false
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
@@ -90,22 +87,6 @@ jobs:
name: ruff
path: target/debug/ruff
cargo-fuzz:
runs-on: ubuntu-latest
name: "cargo fuzz"
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
with:
workspaces: "fuzz -> target"
- name: "Install cargo-fuzz"
uses: taiki-e/install-action@v2
with:
tool: cargo-fuzz@0.11
- run: cargo fuzz build -s none
cargo-test-wasm:
runs-on: ubuntu-latest
name: "cargo test (wasm)"
@@ -197,12 +178,12 @@ jobs:
- uses: actions/checkout@v3
- name: "Install nightly Rust toolchain"
# Only pinned to make caching work, update freely
run: rustup toolchain install nightly-2023-06-08
run: rustup toolchain install nightly-2023-03-30
- uses: Swatinem/rust-cache@v2
- name: "Install cargo-udeps"
uses: taiki-e/install-action@cargo-udeps
- name: "Run cargo-udeps"
run: cargo +nightly-2023-06-08 udeps
run: cargo +nightly-2023-03-30 udeps
python-package:
@@ -214,20 +195,18 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- uses: Swatinem/rust-cache@v2
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@v1
with:
manylinux: auto
args: --out dist
- name: "Test wheel"
run: |
pip install --force-reinstall --find-links dist ${{ env.PACKAGE_NAME }}
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
ruff --help
python -m ruff --help
- name: "Remove wheels from cache"
run: rm -rf target/wheels
pre-commit:
name: "pre-commit"
@@ -245,8 +224,8 @@ jobs:
- name: "Cache pre-commit"
uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
- name: "Run pre-commit"
run: |
echo '```console' > $GITHUB_STEP_SUMMARY

View File

@@ -1,9 +1,9 @@
name: mkdocs
on:
workflow_dispatch:
release:
types: [ published ]
types: [published]
workflow_dispatch:
jobs:
mkdocs:

View File

@@ -52,7 +52,7 @@ jobs:
- name: "Build wheels - universal2"
uses: PyO3/maturin-action@v1
with:
args: --release --target universal2-apple-darwin --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
args: --release --universal2 --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: "Install built wheel - universal2"
run: |
pip install dist/${{ env.CRATE_NAME }}-*universal2.whl --force-reinstall
@@ -66,7 +66,7 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
target: [ x64, x86 ]
target: [x64, x86]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
@@ -94,7 +94,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
target: [ x86_64, i686 ]
target: [x86_64, i686]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
@@ -121,7 +121,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
target: [ aarch64, armv7, s390x, ppc64le, ppc64 ]
target: [aarch64, armv7, s390x, ppc64le, ppc64]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4

View File

@@ -2,8 +2,8 @@ name: "[Playground] Release"
on:
workflow_dispatch:
release:
types: [ published ]
push:
branches: [main]
env:
CARGO_INCREMENTAL: 0

View File

@@ -2,8 +2,8 @@ name: PR Check Comment
on:
workflow_run:
workflows: [ CI, Benchmark ]
types: [ completed ]
workflows: [CI, Benchmark]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:

View File

@@ -2,17 +2,8 @@ name: "[ruff] Release"
on:
workflow_dispatch:
inputs:
tag:
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)."
type: string
sha:
description: "Optionally, the full sha of the commit to be released"
type: string
push:
paths:
# When we change pyproject.toml, we want to ensure that the maturin builds still work
- pyproject.toml
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -43,7 +34,6 @@ jobs:
args: --out dist
- name: "Test sdist"
run: |
rustup default $(cat rust-toolchain)
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
ruff --help
python -m ruff --help
@@ -104,7 +94,7 @@ jobs:
- name: "Build wheels - universal2"
uses: PyO3/maturin-action@v1
with:
args: --release --target universal2-apple-darwin --out dist
args: --release --universal2 --out dist
- name: "Test wheel - universal2"
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*universal2.whl --force-reinstall
@@ -230,7 +220,7 @@ jobs:
platform:
- target: aarch64-unknown-linux-gnu
arch: aarch64
# see https://github.com/astral-sh/ruff/issues/3791
# see https://github.com/charliermarsh/ruff/issues/3791
# and https://github.com/gnzlbg/jemallocator/issues/170#issuecomment-1503228963
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
- target: armv7-unknown-linux-gnueabihf
@@ -392,37 +382,6 @@ jobs:
*.tar.gz
*.sha256
validate-tag:
name: Validate tag
runs-on: ubuntu-latest
# If you don't set an input tag, it's a dry run (no uploads).
if: ${{ inputs.tag }}
steps:
- uses: actions/checkout@v3
- name: Check tag consistency
run: |
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
if [ "${{ inputs.tag }}" != "${version}" ]; then
echo "The input tag does not match the version from pyproject.toml:" >&2
echo "${{ inputs.tag }}" >&2
echo "${version}" >&2
exit 1
else
echo "Releasing ${version}"
fi
- name: Check SHA consistency
if: ${{ inputs.sha }}
run: |
git_sha=$(git rev-parse HEAD)
if [ "${{ inputs.sha }}" != "${git_sha}" ]; then
echo "The specified sha does not match the git checkout" >&2
echo "${{ inputs.sha }}" >&2
echo "${git_sha}" >&2
exit 1
else
echo "Releasing ${git_sha}"
fi
release:
name: Release
runs-on: ubuntu-latest
@@ -434,61 +393,27 @@ jobs:
- linux-cross
- musllinux
- musllinux-cross
- validate-tag
# If you don't set an input tag, it's a dry run (no uploads).
if: ${{ inputs.tag }}
environment:
name: release
permissions:
# For pypi trusted publishing
id-token: write
# For GitHub release publishing
contents: write
if: "startsWith(github.ref, 'refs/tags/')"
steps:
- uses: actions/download-artifact@v3
with:
name: wheels
path: wheels
- name: Publish to PyPi
uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true
packages-dir: wheels
verbose: true
- uses: actions/setup-python@v4
- name: "Publish to PyPi"
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.RUFF_TOKEN }}
run: |
pip install --upgrade twine
twine upload --skip-existing *
- name: "Update pre-commit mirror"
run: |
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.RUFF_PRE_COMMIT_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/charliermarsh/ruff-pre-commit/dispatches --data '{"event_type": "pypi_release"}'
- uses: actions/download-artifact@v3
with:
name: binaries
path: binaries
- name: git tag
run: |
git config user.email "hey@astral.sh"
git config user.name "Ruff Release CI"
git tag -m "v${{ inputs.tag }}" "v${{ inputs.tag }}"
# If there is duplicate tag, this will fail. The publish to pypi action will have been a noop (due to skip
# existing), so we make a non-destructive exit here
git push --tags
- name: "Publish to GitHub"
- name: Release
uses: softprops/action-gh-release@v1
with:
draft: true
files: binaries/*
tag_name: v${{ inputs.tag }}
# After the release has been published, we update downstream repositories
# This is separate because if this fails the release is still fine, we just need to do some manual workflow triggers
update-dependents:
name: Release
runs-on: ubuntu-latest
needs: release
steps:
- name: "Update pre-commit mirror"
uses: actions/github-script@v6
with:
github-token: ${{ secrets.RUFF_PRE_COMMIT_PAT }}
script: |
github.rest.actions.createWorkflowDispatch({
owner: 'astral-sh',
repo: 'ruff-pre-commit',
workflow_id: 'main.yml',
ref: 'main',
})

20
.gitignore vendored
View File

@@ -1,26 +1,10 @@
# Benchmarking cpython (CONTRIBUTING.md)
# Local cache
.ruff_cache
crates/ruff/resources/test/cpython
# generate_mkdocs.py
mkdocs.yml
.overrides
# check_ecosystem.py
ruff-old
github_search*.jsonl
# update_schemastore.py
schemastore
# `maturin develop` and ecosystem_all_check.sh
.venv*
# Formatter debugging (crates/ruff_python_formatter/README.md)
scratch.py
# Created by `perf` (CONTRIBUTING.md)
perf.data
perf.data.old
# Created by `flamegraph` (CONTRIBUTING.md)
flamegraph.svg
# Additional target directories that don't invalidate the main compile cache when changing linker settings,
# e.g. `CARGO_TARGET_DIR=target-maturin maturin build --release --strip` or
# `CARGO_TARGET_DIR=target-llvm-lines RUSTFLAGS="-Csymbol-mangling-version=v0" cargo llvm-lines -p ruff --lib`
/target*
###
# Rust.gitignore

View File

@@ -1,14 +0,0 @@
# default to true for all rules
default: true
# MD033/no-inline-html
MD033: false
# MD041/first-line-h1
MD041: false
# MD013/line-length
MD013:
line_length: 100
code_blocks: false
ignore_code_blocks: true

View File

@@ -1,14 +1,4 @@
fail_fast: true
exclude: |
(?x)^(
crates/ruff/resources/.*|
crates/ruff/src/rules/.*/snapshots/.*|
crates/ruff_cli/resources/.*|
crates/ruff_python_formatter/resources/.*|
crates/ruff_python_formatter/src/snapshots/.*
)$
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.12.1
@@ -27,9 +17,14 @@ repos:
rev: v0.33.0
hooks:
- id: markdownlint-fix
args:
- --disable
- MD013 # line-length
- MD033 # no-inline-html
- --
- repo: https://github.com/crate-ci/typos
rev: v1.14.12
rev: v1.14.8
hooks:
- id: typos
@@ -39,19 +34,29 @@ repos:
name: cargo fmt
entry: cargo fmt --
language: system
types: [ rust ]
pass_filenames: false # This makes it a lot faster
types: [rust]
- id: clippy
name: clippy
entry: cargo clippy --workspace --all-targets --all-features -- -D warnings
language: system
pass_filenames: false
- id: ruff
name: ruff
entry: cargo run --bin ruff -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
entry: cargo run -p ruff_cli -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
language: system
types_or: [ python, pyi ]
types_or: [python, pyi]
require_serial: true
exclude: |
(?x)^(
crates/ruff/resources/.*|
crates/ruff_python_formatter/resources/.*
)$
- id: dev-generate-all
name: dev-generate-all
entry: cargo dev generate-all
language: system
pass_filenames: false
exclude: target
# Black
- repo: https://github.com/psf/black
@@ -60,4 +65,4 @@ repos:
- id: black
ci:
skip: [ cargo-fmt, dev-generate-all ]
skip: [cargo-fmt, clippy, dev-generate-all]

View File

@@ -2,7 +2,7 @@
## 0.0.268
### The `keep-runtime-typing` setting has been removed ([#4427](https://github.com/astral-sh/ruff/pull/4427))
### The `keep-runtime-typing` setting has been removed ([#4427](https://github.com/charliermarsh/ruff/pull/4427))
Enabling the `keep-runtime-typing` option, located under the `pyupgrade` section, is equivalent
to ignoring the `UP006` and `UP007` rules via Ruff's standard `ignore` mechanism. As there's no
@@ -11,9 +11,9 @@ removed.
## 0.0.267
### `update-check` is no longer a valid configuration option ([#4313](https://github.com/astral-sh/ruff/pull/4313))
### `update-check` is no longer a valid configuration option ([#4313](https://github.com/charliermarsh/ruff/pull/4313))
The `update-check` functionality was deprecated in [#2530](https://github.com/astral-sh/ruff/pull/2530),
The `update-check` functionality was deprecated in [#2530](https://github.com/charliermarsh/ruff/pull/2530),
in that the behavior itself was removed, and Ruff was changed to warn when that option was enabled.
Now, Ruff will throw an error when `update-check` is provided via a configuration file (e.g.,
@@ -22,7 +22,7 @@ this option from their configuration.
## 0.0.265
### `--fix-only` now exits with a zero exit code, unless `--exit-non-zero-on-fix` is specified ([#4146](https://github.com/astral-sh/ruff/pull/4146))
### `--fix-only` now exits with a zero exit code, unless `--exit-non-zero-on-fix` is specified ([#4146](https://github.com/charliermarsh/ruff/pull/4146))
Previously, `--fix-only` would exit with a non-zero exit code if any fixes were applied. This
behavior was inconsistent with `--fix`, and further, meant that `--exit-non-zero-on-fix` was
@@ -33,7 +33,7 @@ in which case it will exit with a non-zero exit code if any fixes were applied.
## 0.0.260
### Fixes are now represented as a list of edits ([#3709](https://github.com/astral-sh/ruff/pull/3709))
### Fixes are now represented as a list of edits ([#3709](https://github.com/charliermarsh/ruff/pull/3709))
Previously, Ruff represented each fix as a single edit, which prohibited Ruff from automatically
fixing violations that required multiple edits across a file. As such, Ruff now represents each
@@ -68,14 +68,14 @@ The updated representation instead includes a list of edits:
## 0.0.246
### `multiple-statements-on-one-line-def` (`E704`) was removed ([#2773](https://github.com/astral-sh/ruff/pull/2773))
### `multiple-statements-on-one-line-def` (`E704`) was removed ([#2773](https://github.com/charliermarsh/ruff/pull/2773))
This rule was introduced in v0.0.245. However, it turns out that pycodestyle and Flake8 ignore this
rule by default, as it is not part of PEP 8. As such, we've removed it from Ruff.
## 0.0.245
### Ruff's public `check` method was removed ([#2709](https://github.com/astral-sh/ruff/pull/2709))
### Ruff's public `check` method was removed ([#2709](https://github.com/charliermarsh/ruff/pull/2709))
Previously, Ruff exposed a `check` method as a public Rust API. This method was used by few,
if any clients, and was not well documented or supported. As such, it has been removed, with
@@ -83,11 +83,10 @@ the intention of adding a stable public API in the future.
## 0.0.238
### `select`, `extend-select`, `ignore`, and `extend-ignore` have new semantics ([#2312](https://github.com/astral-sh/ruff/pull/2312))
### `select`, `extend-select`, `ignore`, and `extend-ignore` have new semantics ([#2312](https://github.com/charliermarsh/ruff/pull/2312))
Previously, the interplay between `select` and its related options could lead to unexpected
behavior. For example, `ruff --select E501 --ignore ALL` and `ruff --select E501 --extend-ignore ALL`
behaved differently. (See [#2312](https://github.com/astral-sh/ruff/pull/2312) for more
behavior. For example, `ruff --select E501 --ignore ALL` and `ruff --select E501 --extend-ignore ALL` behaved differently. (See [#2312](https://github.com/charliermarsh/ruff/pull/2312) for more
examples.)
When Ruff determines the enabled rule set, it has to reconcile `select` and `ignore` from a variety
@@ -113,14 +112,14 @@ ignore = ["F401"]
Running `ruff --select F` would previously have enabled all `F` rules, apart from `F401`. Now, it
will enable all `F` rules, including `F401`, as the command line's `--select` resets the resolution.
### `remove-six-compat` (`UP016`) has been removed ([#2332](https://github.com/astral-sh/ruff/pull/2332))
### `remove-six-compat` (`UP016`) has been removed ([#2332](https://github.com/charliermarsh/ruff/pull/2332))
The `remove-six-compat` rule has been removed. This rule was only useful for one-time Python 2-to-3
upgrades.
## 0.0.237
### `--explain`, `--clean`, and `--generate-shell-completion` are now subcommands ([#2190](https://github.com/astral-sh/ruff/pull/2190))
### `--explain`, `--clean`, and `--generate-shell-completion` are now subcommands ([#2190](https://github.com/charliermarsh/ruff/pull/2190))
`--explain`, `--clean`, and `--generate-shell-completion` are now implemented as subcommands:
@@ -163,14 +162,14 @@ no change in behavior. However, please note the following exceptions:
## 0.0.226
### `misplaced-comparison-constant` (`PLC2201`) was deprecated in favor of `SIM300` ([#1980](https://github.com/astral-sh/ruff/pull/1980))
### `misplaced-comparison-constant` (`PLC2201`) was deprecated in favor of `SIM300` ([#1980](https://github.com/charliermarsh/ruff/pull/1980))
These two rules contain (nearly) identical logic. To deduplicate the rule set, we've upgraded
`SIM300` to handle a few more cases, and deprecated `PLC2201` in favor of `SIM300`.
## 0.0.225
### `@functools.cache` rewrites have been moved to a standalone rule (`UP033`) ([#1938](https://github.com/astral-sh/ruff/pull/1938))
### `@functools.cache` rewrites have been moved to a standalone rule (`UP033`) ([#1938](https://github.com/charliermarsh/ruff/pull/1938))
Previously, `UP011` handled both `@functools.lru_cache()`-to-`@functools.lru_cache` conversions,
_and_ `@functools.lru_cache(maxsize=None)`-to-`@functools.cache` conversions. The latter has been
@@ -179,7 +178,7 @@ to reflect the change in rule code.
## 0.0.222
### `--max-complexity` has been removed from the CLI ([#1877](https://github.com/astral-sh/ruff/pull/1877))
### `--max-complexity` has been removed from the CLI ([#1877](https://github.com/charliermarsh/ruff/pull/1877))
The McCabe plugin's `--max-complexity` setting has been removed from the CLI, for consistency with
the treatment of other, similar settings.
@@ -194,7 +193,7 @@ max-complexity = 10
## 0.0.181
### Files excluded by `.gitignore` are now ignored ([#1234](https://github.com/astral-sh/ruff/pull/1234))
### Files excluded by `.gitignore` are now ignored ([#1234](https://github.com/charliermarsh/ruff/pull/1234))
Ruff will now avoid checking files that are excluded by `.ignore`, `.gitignore`,
`.git/info/exclude`, and global `gitignore` files. This behavior is powered by the [`ignore`](https://docs.rs/ignore/latest/ignore/struct.WalkBuilder.html#ignore-rules)
@@ -207,7 +206,7 @@ default.
## 0.0.178
### Configuration files are now resolved hierarchically ([#1190](https://github.com/astral-sh/ruff/pull/1190))
### Configuration files are now resolved hierarchically ([#1190](https://github.com/charliermarsh/ruff/pull/1190))
`pyproject.toml` files are now resolved hierarchically, such that for each Python file, we find
the first `pyproject.toml` file in its path, and use that to determine its lint settings.

View File

@@ -8,7 +8,6 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
- [Project Structure](#project-structure)
- [Example: Adding a new lint rule](#example-adding-a-new-lint-rule)
- [Rule naming convention](#rule-naming-convention)
- [Rule testing: fixtures and snapshots](#rule-testing-fixtures-and-snapshots)
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
- [MkDocs](#mkdocs)
- [Release Process](#release-process)
@@ -21,18 +20,18 @@ Ruff welcomes contributions in the form of Pull Requests.
For small changes (e.g., bug fixes), feel free to submit a PR.
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5) to discuss your idea with the
community.
creating an [**issue**](https://github.com/charliermarsh/ruff/issues) outlining your proposed
change. You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5) to discuss your idea with
the community.
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.
As a concrete example: consider taking on one of the rules from the [`flake8-pyi`](https://github.com/charliermarsh/ruff/issues/848)
plugin, and looking to the originating [Python source](https://github.com/PyCQA/flake8-pyi)
for guidance.
### Prerequisites
@@ -45,12 +44,6 @@ You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
cargo install cargo-insta
```
and pre-commit to run some validation checks:
```shell
pipx install pre-commit # or `pip install pre-commit` if you have a virtualenv
```
### Development
After cloning the repository, run Ruff locally with:
@@ -63,9 +56,9 @@ Prior to opening a pull request, ensure that your code has been auto-formatted,
and that it passes both the lint and test validation checks:
```shell
cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting
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.
cargo fmt # Auto-formatting...
cargo clippy --fix --workspace --all-targets --all-features # Linting...
cargo test # Testing...
```
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
@@ -78,6 +71,13 @@ after running `cargo test` like so:
cargo insta review
```
If you have `pre-commit` [installed](https://pre-commit.com/#installation) then you can use it to
assist with formatting and linting. The following command will run the `pre-commit` hooks:
```shell
pre-commit run --all-files
```
Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration
prior to merging.
@@ -92,123 +92,68 @@ The vast majority of the code, including all lint rules, lives in the `ruff` cra
At time of writing, the repository includes the following crates:
- `crates/ruff`: library crate containing all lint rules and the core logic for running them.
- `crates/ruff_benchmark`: binary crate for running micro-benchmarks.
- `crates/ruff_cache`: library crate for caching lint results.
- `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g.,
`cargo dev generate-all`).
- `crates/ruff_diagnostics`: library crate for the lint diagnostics APIs.
- `crates/ruff_formatter`: library crate for generic code formatting logic based on an intermediate
representation.
- `crates/ruff_index`: library crate inspired by `rustc_index`.
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g., `cargo dev generate-all`).
- `crates/ruff_macros`: library crate containing macros used by Ruff.
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
- `crates/ruff_python_formatter`: library crate containing Python-specific code formatting logic.
- `crates/ruff_python_semantic`: library crate containing Python-specific semantic analysis logic,
including Ruff's semantic model.
- `crates/ruff_python_stdlib`: library crate containing Python-specific standard library data.
- `crates/ruff_python_whitespace`: library crate containing Python-specific whitespace analysis
logic.
- `crates/ruff_rustpython`: library crate containing `RustPython`-specific utilities.
- `crates/ruff_testing_macros`: library crate containing macros used for testing Ruff.
- `crates/ruff_textwrap`: library crate to indent and dedent Python source code.
- `crates/ruff_wasm`: library crate for exposing Ruff as a WebAssembly module.
- `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of standard library modules by versionb).
- `crates/flake8_to_ruff`: binary crate for generating Ruff configuration from Flake8 configuration.
### Example: Adding a new lint rule
At a high level, the steps involved in adding a new lint rule are as follows:
1. Determine a name for the new rule as per our [rule naming convention](#rule-naming-convention)
(e.g., `AssertFalse`, as in, "allow `assert False`").
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/assert_false.rs`).
1. In that file, define a violation struct (e.g., `pub struct AssertFalse`). You can grep for
`#[violation]` to see examples.
1. In that file, define a function that adds the violation to the diagnostic list as appropriate
(e.g., `pub(crate) fn assert_false`) based on whatever inputs are required for the rule (e.g.,
an `ast::StmtAssert` node).
1. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast/mod.rs` (for
AST-based checks), `crates/ruff/src/checkers/tokens.rs` (for token-based checks),
`crates/ruff/src/checkers/lines.rs` (for text-based checks), or
`crates/ruff/src/checkers/filesystem.rs` (for filesystem-based checks).
1. Map the violation struct to a rule code in `crates/ruff/src/codes.rs` (e.g., `B011`).
1. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.
1. Determine a name for the new rule as per our [rule naming convention](#rule-naming-convention).
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
1. In that file, define a violation struct. You can grep for `#[violation]` to see examples.
1. Map the violation struct to a rule code in `crates/ruff/src/registry.rs` (e.g., `E402`).
1. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast/mod.rs` (for AST-based
checks), `crates/ruff/src/checkers/tokens.rs` (for token-based checks), `crates/ruff/src/checkers/lines.rs`
(for text-based checks), or `crates/ruff/src/checkers/filesystem.rs` (for filesystem-based
checks).
1. Add a test fixture.
1. Update the generated files (documentation and generated code).
To trigger the violation, you'll likely want to augment the logic in `crates/ruff/src/checkers/ast.rs`
to call your new function at the appropriate time and with the appropriate inputs. The `Checker`
defined therein is a Python AST visitor, which iterates over the AST, building up a semantic model,
and calling out to lint rule analyzer functions as it goes.
To define the violation, start by creating a dedicated file for your rule under the appropriate
rule linter (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`). That file should
contain a struct defined via `#[violation]`, along with a function that creates the violation
based on any required inputs.
To trigger the violation, you'll likely want to augment the logic in `crates/ruff/src/checkers/ast.rs`,
which defines the Python AST visitor, responsible for iterating over the abstract syntax tree and
collecting diagnostics as it goes.
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 `Check::new` invocations to understand how other, similar rules are implemented.
Once you're satisfied with your code, add tests for your rule. See [rule testing](#rule-testing-fixtures-and-snapshots)
for more details.
To add a test fixture, create a file under `crates/ruff/resources/test/fixtures/[linter]`, named to match
the code you defined earlier (e.g., `crates/ruff/resources/test/fixtures/pycodestyle/E402.py`). This file should
contain a variety of violations and non-violations designed to evaluate and demonstrate the behavior
of your lint rule.
Finally, regenerate the documentation and other generated assets (like our JSON Schema) with:
`cargo dev generate-all`.
Run `cargo dev generate-all` to generate the code for your new fixture. Then run Ruff
locally with (e.g.) `cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
`test_case` macro in the relevant `crates/ruff/src/rules/[linter]/mod.rs` file. Then, run `cargo test`.
Your test will fail, but you'll be prompted to follow-up with `cargo insta review`. Accept the
generated snapshot, then commit the snapshot file alongside the rest of your changes.
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
#### Rule naming convention
Like Clippy, Ruff's rule names should make grammatical and logical sense when read as "allow
${rule}" or "allow ${rule} items", as in the context of suppression comments.
The rule name should make sense when read as "allow _rule-name_" or "allow _rule-name_ items".
For example, `AssertFalse` fits this convention: it flags `assert False` statements, and so a
suppression comment would be framed as "allow `assert False`".
This implies that rule names:
As such, rule names should...
- should state the bad thing being checked for
- Highlight the pattern that is being linted against, rather than the preferred alternative.
For example, `AssertFalse` guards against `assert False` statements.
- should not contain instructions on what you should use instead
(these belong in the rule documentation and the `autofix_title` for rules that have autofix)
- _Not_ contain instructions on how to fix the violation, which instead belong in the rule
documentation and the `autofix_title`.
- _Not_ contain a redundant prefix, like `Disallow` or `Banned`, which are already implied by the
convention.
When re-implementing rules from other linters, we prioritize adhering to this convention over
When re-implementing rules from other linters, this convention is given more importance than
preserving the original rule name.
#### Rule testing: fixtures and snapshots
To test rules, Ruff uses snapshots of Ruff's output for a given file (fixture). Generally, there
will be one file per rule (e.g., `E402.py`), and each file will contain all necessary examples of
both violations and non-violations. `cargo insta review` will generate a snapshot file containing
Ruff's output for each fixture, which you can then commit alongside your changes.
Once you've completed the code for the rule itself, you can define tests with the following steps:
1. Add a Python file to `crates/ruff/resources/test/fixtures/[linter]` that contains the code you
want to test. The file name should match the rule name (e.g., `E402.py`), and it should include
examples of both violations and non-violations.
1. Run Ruff locally against your file and verify the output is as expected. Once you're satisfied
with the output (you see the violations you expect, and no others), proceed to the next step.
For example, if you're adding a new rule named `E402`, you would run:
```shell
cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache
```
1. Add the test to the relevant `crates/ruff/src/rules/[linter]/mod.rs` file. If you're contributing
a rule to a pre-existing set, you should be able to find a similar example to pattern-match
against. If you're adding a new linter, you'll need to create a new `mod.rs` file (see,
e.g., `crates/ruff/src/rules/flake8_bugbear/mod.rs`)
1. Run `cargo test`. Your test will fail, but you'll be prompted to follow-up
with `cargo insta review`. Run `cargo insta review`, review and accept the generated snapshot,
then commit the snapshot file alongside the rest of your changes.
1. Run `cargo test` again to ensure that your test passes.
### Example: Adding a new configuration option
Ruff's user-facing settings live in a few different places.
@@ -239,8 +184,6 @@ Finally, regenerate the documentation and generated code with `cargo dev generat
To preview any changes to the documentation locally:
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
1. Install MkDocs and Material for MkDocs with:
```shell
@@ -271,28 +214,6 @@ them to [PyPI](https://pypi.org/project/ruff/).
Ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).
### Creating a new release
1. Update the version with `rg 0.0.269 --files-with-matches | xargs sed -i 's/0.0.269/0.0.270/g'`
1. Update `BREAKING_CHANGES.md`
1. Create a PR with the version and `BREAKING_CHANGES.md` updated
1. Merge the PR
1. Run the release workflow with the version number (without starting `v`) as input. Make sure
main has your merged PR as last commit
1. The release workflow will do the following:
1. Build all the assets. If this fails (even though we tested in step 4), we havent tagged or
uploaded anything, you can restart after pushing a fix
1. Upload to pypi
1. Create and push the git tag (from pyproject.toml). We create the git tag only here
because we can't change it ([#4468](https://github.com/charliermarsh/ruff/issues/4468)), so
we want to make sure everything up to and including publishing to pypi worked.
1. Attach artifacts to draft GitHub release
1. Trigger downstream repositories. This can fail without causing fallout, it is possible (if
inconvenient) to trigger the downstream jobs manually
1. Create release notes in GitHub UI and promote from draft to proper release(<https://github.com/charliermarsh/ruff/releases/new>)
1. If needed, [update the schemastore](https://github.com/charliermarsh/ruff/blob/main/scripts/update_schemastore.py)
1. If needed, update ruff-lsp and ruff-vscode
## Ecosystem CI
GitHub Actions will run your changes against a number of real-world projects from GitHub and
@@ -304,7 +225,7 @@ python scripts/check_ecosystem.py path/to/your/ruff path/to/older/ruff
You can also run the Ecosystem CI check in a Docker container across a larger set of projects by
downloading the [`known-github-tomls.json`](https://github.com/akx/ruff-usage-aggregate/blob/master/data/known-github-tomls.jsonl)
as `github_search.jsonl` and following the instructions in [scripts/Dockerfile.ecosystem](https://github.com/astral-sh/ruff/blob/main/scripts/Dockerfile.ecosystem).
as `github_search.jsonl` and following the instructions in [scripts/Dockerfile.ecosystem](https://github.com/charliermarsh/ruff/blob/main/scripts/Dockerfile.ecosystem).
Note that this check will take a while to run.
## Benchmarks

533
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,13 @@
[workspace]
members = ["crates/*"]
resolver = "2"
[workspace.package]
edition = "2021"
rust-version = "1.70"
homepage = "https://beta.ruff.rs/docs"
documentation = "https://beta.ruff.rs/docs"
repository = "https://github.com/astral-sh/ruff"
rust-version = "1.69"
homepage = "https://beta.ruff.rs/docs/"
documentation = "https://beta.ruff.rs/docs/"
repository = "https://github.com/charliermarsh/ruff"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
license = "MIT"
[workspace.dependencies]
anyhow = { version = "1.0.69" }
@@ -24,45 +22,34 @@ ignore = { version = "0.4.20" }
insta = { version = "1.28.0" }
is-macro = { version = "0.2.2" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
log = { version = "0.4.17" }
memchr = "2.5.0"
nohash-hasher = { version = "0.2.0" }
num-bigint = { version = "0.4.3" }
num-traits = { version = "0.2.15" }
once_cell = { version = "1.17.1" }
path-absolutize = { version = "3.0.14" }
proc-macro2 = { version = "1.0.51" }
quote = { version = "1.0.23" }
regex = { version = "1.7.1" }
rustc-hash = { version = "1.1.0" }
ruff_text_size = { git = "https://github.com/RustPython/Parser.git", rev = "3654cf0bdfc270df6b2b83e2df086843574ad082" }
rustpython-format = { git = "https://github.com/RustPython/Parser.git", rev = "3654cf0bdfc270df6b2b83e2df086843574ad082" }
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", rev = "3654cf0bdfc270df6b2b83e2df086843574ad082" }
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", rev = "3654cf0bdfc270df6b2b83e2df086843574ad082", default-features = false, features = ["full-lexer", "all-nodes-with-ranges"] }
schemars = { version = "0.8.12" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.93" }
serde_json = { version = "1.0.93", features = ["preserve_order"] }
shellexpand = { version = "3.0.0" }
similar = { version = "2.2.1", features = ["inline"] }
similar = { version = "2.2.1" }
smallvec = { version = "1.10.0" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
syn = { version = "2.0.15" }
test-case = { version = "3.0.0" }
textwrap = { version = "0.16.0" }
toml = { version = "0.7.2" }
# v0.0.1
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
# v0.0.3
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" }
# v0.0.3
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" , default-features = false, features = ["all-nodes-with-ranges", "num-bigint"]}
# v0.0.3
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca", default-features = false, features = ["num-bigint"] }
# v0.0.3
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca", default-features = false }
# v0.0.3
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" , default-features = false, features = ["full-lexer", "all-nodes-with-ranges", "num-bigint"] }
[profile.release]
lto = "fat"
codegen-units = 1
[profile.dev.package.insta]
opt-level = 3

48
LICENSE
View File

@@ -354,29 +354,6 @@ are:
SOFTWARE.
"""
- flake8-slots, licensed as follows:
"""
Copyright (c) 2021 Dominic Davis-Foster
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
"""
- flake8-todos, licensed as follows:
"""
Copyright (c) 2019 EclecticIQ. All rights reserved.
@@ -1199,31 +1176,6 @@ are:
- flake8-django, licensed under the GPL license.
- perflint, licensed as follows:
"""
MIT License
Copyright (c) 2022 Anthony Shaw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- rust-analyzer/text-size, licensed under the MIT license:
"""
Permission is hereby granted, free of charge, to any

View File

@@ -2,11 +2,11 @@
# Ruff
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/charliermarsh/ruff)
[![image](https://img.shields.io/pypi/v/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/l/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/pyversions/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![Actions status](https://github.com/astral-sh/ruff/workflows/CI/badge.svg)](https://github.com/astral-sh/ruff/actions)
[![Actions status](https://github.com/charliermarsh/ruff/workflows/CI/badge.svg)](https://github.com/charliermarsh/ruff/actions)
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://beta.ruff.rs/docs/) | [**Playground**](https://play.ruff.rs/)
@@ -24,18 +24,17 @@ An extremely fast Python linter, written in Rust.
<i>Linting the CPython codebase from scratch.</i>
</p>
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🛠️ `pyproject.toml` support
- 🤝 Python 3.11 compatibility
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
built-in Flake8 rule set
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
- ⌨️ First-party editor integrations for [VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🛠️ `pyproject.toml` support
- 🤝 Python 3.11 compatibility
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
- ⌨️ First-party editor integrations for [VS Code](https://github.com/charliermarsh/ruff-vscode) and [more](https://github.com/charliermarsh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface.
@@ -85,10 +84,9 @@ of [Conda](https://docs.conda.io/en/latest/):
[**Timothy Crosley**](https://twitter.com/timothycrosley/status/1606420868514877440),
creator of [isort](https://github.com/PyCQA/isort):
> Just switched my first project to Ruff. Only one downside so far: it's so fast I couldn't believe
> it was working till I intentionally introduced some errors.
> Just switched my first project to Ruff. Only one downside so far: it's so fast I couldn't believe it was working till I intentionally introduced some errors.
[**Tim Abbott**](https://github.com/astral-sh/ruff/issues/465#issuecomment-1317400028), lead
[**Tim Abbott**](https://github.com/charliermarsh/ruff/issues/465#issuecomment-1317400028), lead
developer of [Zulip](https://github.com/zulip/zulip):
> This is just ridiculously fast... `ruff` is amazing.
@@ -137,15 +135,15 @@ ruff check path/to/code/to/file.py # Lint `file.py`
Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: v0.0.274
rev: 'v0.0.269'
hooks:
- id: ruff
```
Ruff can also be used as a [VS Code extension](https://github.com/astral-sh/ruff-vscode) or
alongside any other editor through the [Ruff LSP](https://github.com/astral-sh/ruff-lsp).
Ruff can also be used as a [VS Code extension](https://github.com/charliermarsh/ruff-vscode) or
alongside any other editor through the [Ruff LSP](https://github.com/charliermarsh/ruff-lsp).
Ruff can also be used as a [GitHub Action](https://github.com/features/actions) via
[`ruff-action`](https://github.com/chartboost/ruff-action):
@@ -244,8 +242,6 @@ stylistic rules made obsolete by the use of an autoformatter, like
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
catches a wide variety of common errors (like unused imports) with zero configuration.
<!-- End section: Rules -->
Beyond the defaults, Ruff re-implements some of the most popular Flake8 plugins and related code
quality tools, including:
@@ -254,14 +250,13 @@ quality tools, including:
- [flake8-2020](https://pypi.org/project/flake8-2020/)
- [flake8-annotations](https://pypi.org/project/flake8-annotations/)
- [flake8-async](https://pypi.org/project/flake8-async)
- [flake8-bandit](https://pypi.org/project/flake8-bandit/) ([#1646](https://github.com/astral-sh/ruff/issues/1646))
- [flake8-bandit](https://pypi.org/project/flake8-bandit/) ([#1646](https://github.com/charliermarsh/ruff/issues/1646))
- [flake8-blind-except](https://pypi.org/project/flake8-blind-except/)
- [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/)
- [flake8-bugbear](https://pypi.org/project/flake8-bugbear/)
- [flake8-builtins](https://pypi.org/project/flake8-builtins/)
- [flake8-commas](https://pypi.org/project/flake8-commas/)
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
- [flake8-copyright](https://pypi.org/project/flake8-copyright/)
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
- [flake8-django](https://pypi.org/project/flake8-django/)
@@ -284,24 +279,24 @@ quality tools, including:
- [flake8-return](https://pypi.org/project/flake8-return/)
- [flake8-self](https://pypi.org/project/flake8-self/)
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
- [flake8-slots](https://pypi.org/project/flake8-slots/)
- [flake8-super](https://pypi.org/project/flake8-super/)
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
- [flake8-todos](https://pypi.org/project/flake8-todos/)
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102))
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/charliermarsh/ruff/issues/2102))
- [isort](https://pypi.org/project/isort/)
- [mccabe](https://pypi.org/project/mccabe/)
- [pandas-vet](https://pypi.org/project/pandas-vet/)
- [pep8-naming](https://pypi.org/project/pep8-naming/)
- [pydocstyle](https://pypi.org/project/pydocstyle/)
- [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks)
- [pylint-airflow](https://pypi.org/project/pylint-airflow/)
- [pyupgrade](https://pypi.org/project/pyupgrade/)
- [tryceratops](https://pypi.org/project/tryceratops/)
- [yesqa](https://pypi.org/project/yesqa/)
<!-- End section: Rules -->
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
## Contributing
@@ -313,8 +308,8 @@ You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
## Support
Having trouble? Check out the existing issues on [**GitHub**](https://github.com/astral-sh/ruff/issues),
or feel free to [**open a new one**](https://github.com/astral-sh/ruff/issues/new).
Having trouble? Check out the existing issues on [**GitHub**](https://github.com/charliermarsh/ruff/issues),
or feel free to [**open a new one**](https://github.com/charliermarsh/ruff/issues/new).
You can also ask for help on [**Discord**](https://discord.gg/c9MhzV8aU5).
@@ -336,7 +331,7 @@ and again draws on both the APIs and implementation details of [Rome](https://gi
Ruff is also influenced by a number of tools outside the Python ecosystem, like
[Clippy](https://github.com/rust-lang/rust-clippy) and [ESLint](https://github.com/eslint/eslint).
Ruff is the beneficiary of a large number of [contributors](https://github.com/astral-sh/ruff/graphs/contributors).
Ruff is the beneficiary of a large number of [contributors](https://github.com/charliermarsh/ruff/graphs/contributors).
Ruff is released under the MIT license.
@@ -357,9 +352,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- [FastAPI](https://github.com/tiangolo/fastapi)
- [Gradio](https://github.com/gradio-app/gradio)
- [Great Expectations](https://github.com/great-expectations/great_expectations)
- Hugging Face ([Transformers](https://github.com/huggingface/transformers),
[Datasets](https://github.com/huggingface/datasets),
[Diffusers](https://github.com/huggingface/diffusers))
- Hugging Face ([Transformers](https://github.com/huggingface/transformers), [Datasets](https://github.com/huggingface/datasets), [Diffusers](https://github.com/huggingface/diffusers))
- [Hatch](https://github.com/pypa/hatch)
- [Home Assistant](https://github.com/home-assistant/core)
- [Ibis](https://github.com/ibis-project/ibis)
@@ -371,9 +364,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- Modern Treasury ([Python SDK](https://github.com/Modern-Treasury/modern-treasury-python-sdk))
- Mozilla ([Firefox](https://github.com/mozilla/gecko-dev))
- [MegaLinter](https://github.com/oxsecurity/megalinter)
- Microsoft ([Semantic Kernel](https://github.com/microsoft/semantic-kernel),
[ONNX Runtime](https://github.com/microsoft/onnxruntime),
[LightGBM](https://github.com/microsoft/LightGBM))
- Microsoft ([Semantic Kernel](https://github.com/microsoft/semantic-kernel), [ONNX Runtime](https://github.com/microsoft/onnxruntime), [LightGBM](https://github.com/microsoft/LightGBM))
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
- [Neon](https://github.com/neondatabase/neon)
- [ONNX](https://github.com/onnx/onnx)
@@ -415,21 +406,21 @@ Ruff is used by a number of major open-source projects and companies, including:
If you're using Ruff, consider adding the Ruff badge to project's `README.md`:
```md
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/charliermarsh/ruff)
```
...or `README.rst`:
```rst
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
:target: https://github.com/astral-sh/ruff
:target: https://github.com/charliermarsh/ruff
:alt: Ruff
```
...or, as HTML:
```html
<a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
<a href="https://github.com/charliermarsh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
```
## License

View File

@@ -1,5 +1,5 @@
[files]
extend-exclude = ["resources", "snapshots"]
extend-exclude = ["snapshots", "black"]
[default.extend-words]
trivias = "trivias"
@@ -8,4 +8,3 @@ whos = "whos"
spawnve = "spawnve"
ned = "ned"
poit = "poit"
BA = "BA" # acronym for "Bad Allowed", used in testing.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,16 +1,8 @@
[package]
name = "flake8-to-ruff"
version = "0.0.274"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""
authors = { workspace = true }
version = "0.0.269"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
[dependencies]
ruff = { path = "../ruff", default-features = false }

View File

@@ -1,7 +1,7 @@
# flake8-to-ruff
Convert existing Flake8 configuration files (`setup.cfg`, `tox.ini`, or `.flake8`) for use with
[Ruff](https://github.com/astral-sh/ruff).
[Ruff](https://github.com/charliermarsh/ruff).
Generates a Ruff-compatible `pyproject.toml` section.
@@ -96,4 +96,4 @@ MIT
## Contributing
Contributions are welcome and hugely appreciated. To get started, check out the
[contributing guidelines](https://github.com/astral-sh/ruff/blob/main/CONTRIBUTING.md).
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).

View File

@@ -23,10 +23,10 @@ description = "Convert existing Flake8 configuration to Ruff."
requires-python = ">=3.7"
[project.urls]
repository = "https://github.com/astral-sh/ruff#subdirectory=crates/flake8_to_ruff"
repository = "https://github.com/charliermarsh/ruff#subdirectory=crates/flake8_to_ruff"
[build-system]
requires = ["maturin>=1.0,<2.0"]
requires = ["maturin>=0.15.2,<0.16"]
build-backend = "maturin"
[tool.maturin]

View File

@@ -1,15 +1,14 @@
[package]
name = "ruff"
version = "0.0.274"
publish = false
authors = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
version = "0.0.269"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
readme = "README.md"
license = "MIT"
[lib]
name = "ruff"
@@ -18,13 +17,11 @@ name = "ruff"
ruff_cache = { path = "../ruff_cache" }
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
ruff_macros = { path = "../ruff_macros" }
ruff_python_whitespace = { path = "../ruff_python_whitespace" }
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
ruff_python_semantic = { path = "../ruff_python_semantic" }
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
ruff_rustpython = { path = "../ruff_rustpython" }
ruff_text_size = { workspace = true }
ruff_textwrap = { path = "../ruff_textwrap" }
annotate-snippets = { version = "0.9.1", features = ["color"] }
anyhow = { workspace = true }
@@ -44,8 +41,8 @@ libcst = { workspace = true }
log = { workspace = true }
natord = { version = "1.0.9" }
nohash-hasher = { workspace = true }
num-bigint = { workspace = true }
num-traits = { workspace = true }
num-bigint = { version = "0.4.3" }
num-traits = { version = "0.2.15" }
once_cell = { workspace = true }
path-absolutize = { workspace = true, features = [
"once_cell_cache",
@@ -53,8 +50,6 @@ path-absolutize = { workspace = true, features = [
] }
pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.3.1", features = ["serde"] }
phf = { version = "0.11", features = ["macros"] }
pyproject-toml = { version = "0.6.0" }
quick-junit = { version = "0.3.2" }
regex = { workspace = true }
result-like = { version = "0.4.6" }
@@ -65,17 +60,16 @@ schemars = { workspace = true, optional = true }
semver = { version = "1.0.16" }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { version = "3.0.0" }
similar = { workspace = true }
similar = { workspace = true, features = ["inline"] }
shellexpand = { workspace = true }
smallvec = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
textwrap = { workspace = true }
thiserror = { version = "1.0.38" }
toml = { workspace = true }
typed-arena = { version = "2.0.2" }
unicode-width = { version = "0.1.10" }
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
[dev-dependencies]
insta = { workspace = true }
@@ -88,3 +82,4 @@ colored = { workspace = true, features = ["no-color"] }
default = []
schemars = ["dep:schemars"]
jupyter_notebook = []
ecosystem_ci = []

View File

@@ -1,16 +0,0 @@
from airflow.operators import PythonOperator
def my_callable():
pass
my_task = PythonOperator(task_id="my_task", callable=my_callable)
my_task_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
incorrect_name = PythonOperator(task_id="my_task")
incorrect_name_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
from my_module import MyClass
incorrect_name = MyClass(task_id="my_task")

View File

@@ -1,8 +0,0 @@
import os
import subprocess
os.popen("chmod +w foo*")
subprocess.Popen("/bin/chown root: *", shell=True)
subprocess.Popen(["/usr/local/bin/rsync", "*", "some_where:"], shell=True)
subprocess.Popen("/usr/local/bin/rsync * no_injection_here:")
os.system("tar cf foo.tar bar/*")

View File

@@ -57,16 +57,12 @@ dict.fromkeys(("world",), True)
{}.deploy(True, False)
getattr(someobj, attrname, False)
mylist.index(True)
bool(False)
int(True)
str(int(False))
cfg.get("hello", True)
cfg.getint("hello", True)
cfg.getfloat("hello", True)
cfg.getboolean("hello", True)
os.set_blocking(0, False)
g_action.set_enabled(True)
settings.set_enable_developer_extras(True)
class Registry:
@@ -84,6 +80,3 @@ class Registry:
# FBT001: Boolean positional arg in function definition
def foo(self, value: bool) -> None:
pass
def foo(self) -> None:
object.__setattr__(self, "flag", True)

View File

@@ -1,7 +1,6 @@
import collections
import datetime as dt
from decimal import Decimal
from fractions import Fraction
import logging
import operator
from pathlib import Path
@@ -159,37 +158,12 @@ def float_infinity_literal(value=float("1e999")):
pass
# Allow standard floats
def float_int_okay(value=float(3)):
# But don't allow standard floats
def float_int_is_wrong(value=float(3)):
pass
def float_str_not_inf_or_nan_okay(value=float("3.14")):
pass
# Allow immutable str() value
def str_okay(value=str("foo")):
pass
# Allow immutable bool() value
def bool_okay(value=bool("bar")):
pass
# Allow immutable int() value
def int_okay(value=int("12")):
pass
# Allow immutable complex() value
def complex_okay(value=complex(1,2)):
pass
# Allow immutable Fraction() value
def fraction_okay(value=Fraction(1,2)):
def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
pass

View File

@@ -73,18 +73,7 @@ def f():
def f():
# Unfixable.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
else:
bar = 1
print(bar)
def f():
# Unfixable (false negative) due to usage of `bar` outside of loop.
# Fixable.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
@@ -96,4 +85,4 @@ def f():
# Unfixable due to trailing underscore (`_line_` wouldn't be considered an ignorable
# variable name).
for line_ in range(self.header_lines):
fp.readline()
fp.readline()

View File

@@ -120,11 +120,3 @@ class AbstractClass(ABC):
@abstractmethod
def empty_1(self, foo: Union[str, int, list, float]):
...
from dataclasses import dataclass
@dataclass
class Foo(ABC): # noqa: B024
...

View File

@@ -149,7 +149,7 @@ for group in groupby(items, key=lambda p: p[1]):
collect_shop_items("Joe", group[1])
# https://github.com/astral-sh/ruff/issues/4050
# https://github.com/charliermarsh/ruff/issues/4050
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
if _section == "greens":
for item in section_items:

View File

@@ -1,6 +1,3 @@
from itertools import count, cycle, repeat
# Errors
zip()
zip(range(3))
zip("a", "b")
@@ -8,18 +5,6 @@ zip("a", "b", *zip("c"))
zip(zip("a"), strict=False)
zip(zip("a", strict=True))
# OK
zip(range(3), strict=True)
zip("a", "b", strict=False)
zip("a", "b", "c", strict=True)
# OK (infinite iterators).
zip([1, 2, 3], cycle("ABCDEF"))
zip([1, 2, 3], count())
zip([1, 2, 3], repeat(1))
zip([1, 2, 3], repeat(1, None))
zip([1, 2, 3], repeat(1, times=None))
# Errors (limited iterators).
zip([1, 2, 3], repeat(1, 1))
zip([1, 2, 3], repeat(1, times=4))

View File

@@ -1,6 +1,6 @@
class MyClass:
ImportError = 4
id: int
id = 5
dir = "/"
def __init__(self):
@@ -10,10 +10,3 @@ class MyClass:
def str(self):
pass
from typing import TypedDict
class MyClass(TypedDict):
id: int

View File

@@ -1,20 +1,13 @@
x = set(x for x in range(3))
x = set(x for x in range(3))
y = f"{set(a if a < 6 else 0 for a 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")
x = set(
x for x in range(3)
)
y = f'{set(a if a < 6 else 0 for a 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')
def set(*args, **kwargs):
return None
def f(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"{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
# around the set comprehension.
print(f"{ {set(a for a in 'abc')} }")
set(x for x in range(3))

View File

@@ -5,14 +5,3 @@ dict(
dict(((x, x) for x in range(3)), z=3)
y = f'{dict((x, x) for x in range(3))}'
print(f'Hello {dict((x, x) for x in range(3))} World')
print(f"Hello {dict((x, x) for x in 'abc')} World")
print(f'Hello {dict((x, x) for x in "abc")} World')
print(f'Hello {dict((x,x) for x in "abc")} World')
f'{dict((x, x) for x in range(3)) | dict((x, x) for x in range(3))}'
f'{ dict((x, x) for x in range(3)) | dict((x, x) for x in range(3)) }'
def f(x):
return x
print(f'Hello {dict((x,f(x)) for x in "abc")} World')

View File

@@ -2,14 +2,3 @@ s = set([x for x in range(3)])
s = set(
[x for x in range(3)]
)
s = f"{set([x for x in 'ab'])}"
s = f'{set([x for x in "ab"])}'
def f(x):
return x
s = f"{set([f(x) for x in 'ab'])}"
s = f"{ set([x for x in 'ab']) | set([x for x in 'ab']) }"
s = f"{set([x for x in 'ab']) | set([x for x in 'ab'])}"

View File

@@ -1,13 +1,2 @@
dict([(i, i) for i in range(3)])
dict([(i, i) for i in range(3)], z=4)
def f(x):
return x
f'{dict([(s,s) for s in "ab"])}'
f"{dict([(s,s) for s in 'ab'])}"
f"{dict([(s, s) for s in 'ab'])}"
f"{dict([(s,f(s)) for s in 'ab'])}"
f'{dict([(s,s) for s in "ab"]) | dict([(s,s) for s in "ab"])}'
f'{ dict([(s,s) for s in "ab"]) | dict([(s,s) for s in "ab"]) }'

View File

@@ -16,11 +16,3 @@ set(
set(
[1,]
)
f"{set([1,2,3])}"
f"{set(['a', 'b'])}"
f'{set(["a", "b"])}'
f"{set(['a', 'b']) - set(['a'])}"
f"{ set(['a', 'b']) - set(['a']) }"
f"a {set(['a', 'b']) - set(['a'])} b"
f"a { set(['a', 'b']) - set(['a']) } b"

View File

@@ -10,13 +10,3 @@ def list():
a = list()
f"{dict(x='y')}"
f'{dict(x="y")}'
f"{dict()}"
f"a {dict()} b"
f"{dict(x='y') | dict(y='z')}"
f"{ dict(x='y') | dict(y='z') }"
f"a {dict(x='y') | dict(y='z')} b"
f"a { dict(x='y') | dict(y='z') } b"

View File

@@ -9,10 +9,6 @@ def f_a_short():
raise RuntimeError("Error")
def f_a_empty():
raise RuntimeError("")
def f_b():
example = "example"
raise RuntimeError(f"This is an {example} exception")

View File

@@ -1,8 +0,0 @@
# TODO: todo
# todo: todo
# XXX: xxx
# xxx: xxx
# HACK: hack
# hack: hack
# FIXME: fixme
# fixme: fixme

View File

@@ -34,19 +34,3 @@ _ = (
b"abc"
b"def"
)
_ = """a""" """b"""
_ = """a
b""" """c
d"""
_ = f"""a""" f"""b"""
_ = f"a" "b"
_ = """a""" "b"
_ = 'a' "b"
_ = rf"a" rf"b"

View File

@@ -1,9 +0,0 @@
import collections
person: collections.namedtuple # OK
from collections import namedtuple
person: namedtuple # OK
person = namedtuple("Person", ["name", "age"]) # OK

View File

@@ -1,11 +0,0 @@
import collections
person: collections.namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
from collections import namedtuple
person: namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
person = namedtuple(
"Person", ["name", "age"]
) # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"

View File

@@ -1,19 +0,0 @@
def f():
from collections.abc import Set as AbstractSet # Ok
def f():
from collections.abc import Container, Sized, Set as AbstractSet, ValuesView # Ok
def f():
from collections.abc import Set # PYI025
def f():
from collections.abc import Container, Sized, Set, ValuesView # PYI025
GLOBAL: Set[int] = set()
class Class:
member: Set[int]

View File

@@ -1,50 +0,0 @@
def f():
from collections.abc import Set as AbstractSet # Ok
def f():
from collections.abc import Container, Sized, Set as AbstractSet, ValuesView # Ok
def f():
from collections.abc import Set # PYI025
def f():
from collections.abc import Container, Sized, Set, ValuesView # PYI025
def f():
"""Test: local symbol renaming."""
if True:
from collections.abc import Set
else:
Set = 1
x: Set = set()
x: Set
del Set
def f():
print(Set)
def Set():
pass
print(Set)
from collections.abc import Set
def f():
"""Test: global symbol renaming."""
global Set
Set = 1
print(Set)
def f():
"""Test: nonlocal symbol renaming."""
from collections.abc import Set
def g():
nonlocal Set
Set = 1
print(Set)

View File

@@ -1,57 +0,0 @@
import builtins
from abc import abstractmethod
def __repr__(self) -> str:
...
def __str__(self) -> builtins.str:
...
def __repr__(self, /, foo) -> str:
...
def __repr__(self, *, foo) -> str:
...
class ShouldRemoveSingle:
def __str__(self) -> builtins.str:
...
class ShouldRemove:
def __repr__(self) -> str:
...
def __str__(self) -> builtins.str:
...
class NoReturnSpecified:
def __str__(self):
...
def __repr__(self):
...
class NonMatchingArgs:
def __str__(self, *, extra) -> builtins.str:
...
def __repr__(self, /, extra) -> str:
...
class MatchingArgsButAbstract:
@abstractmethod
def __str__(self) -> builtins.str:
...
@abstractmethod
def __repr__(self) -> str:
...

View File

@@ -1,28 +0,0 @@
import builtins
from abc import abstractmethod
def __repr__(self) -> str: ...
def __str__(self) -> builtins.str: ...
def __repr__(self, /, foo) -> str: ...
def __repr__(self, *, foo) -> str: ...
class ShouldRemoveSingle:
def __str__(self) -> builtins.str: ... # Error: PYI029
class ShouldRemove:
def __repr__(self) -> str: ... # Error: PYI029
def __str__(self) -> builtins.str: ... # Error: PYI029
class NoReturnSpecified:
def __str__(self): ...
def __repr__(self): ...
class NonMatchingArgs:
def __str__(self, *, extra) -> builtins.str: ...
def __repr__(self, /, extra) -> str: ...
class MatchingArgsButAbstract:
@abstractmethod
def __str__(self) -> builtins.str: ...
@abstractmethod
def __repr__(self) -> str: ...

View File

@@ -1,24 +0,0 @@
from typing import Any
import typing
class Bad:
def __eq__(self, other: Any) -> bool: ... # Fine because not a stub file
def __ne__(self, other: typing.Any) -> typing.Any: ... # Fine because not a stub file
class Good:
def __eq__(self, other: object) -> bool: ...
def __ne__(self, obj: object) -> int: ...
class WeirdButFine:
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any: ...
def __ne__(self, *, kw_only_other: Any) -> bool: ...
class Unannotated:
def __eq__(self) -> Any: ...
def __ne__(self) -> bool: ...

View File

@@ -1,24 +0,0 @@
from typing import Any
import typing
class Bad:
def __eq__(self, other: Any) -> bool: ... # Y032
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032
class Good:
def __eq__(self, other: object) -> bool: ...
def __ne__(self, obj: object) -> int: ...
class WeirdButFine:
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any: ...
def __ne__(self, *, kw_only_other: Any) -> bool: ...
class Unannotated:
def __eq__(self) -> Any: ...
def __ne__(self) -> bool: ...

View File

@@ -1,280 +0,0 @@
# flags: --extend-ignore=Y023
import abc
import builtins
import collections.abc
import typing
from abc import abstractmethod
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
from typing import Any, overload
import typing_extensions
from _typeshed import Self
from typing_extensions import final
class Bad(
object
): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
def __new__(cls, *args: Any, **kwargs: Any) -> Bad:
... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
def __repr__(self) -> str:
... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
def __str__(self) -> builtins.str:
... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
def __eq__(self, other: Any) -> bool:
... # Y032 Prefer "object" to "Any" for the second parameter in "__eq__" methods
def __ne__(self, other: typing.Any) -> typing.Any:
... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
def __enter__(self) -> Bad:
... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
async def __aenter__(self) -> Bad:
... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
def __iadd__(self, other: Bad) -> Bad:
... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
class AlsoBad(int, builtins.object):
... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
class Good:
def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self:
...
@abstractmethod
def __str__(self) -> str:
...
@abc.abstractmethod
def __repr__(self) -> str:
...
def __eq__(self, other: object) -> bool:
...
def __ne__(self, obj: object) -> int:
...
def __enter__(self: Self) -> Self:
...
async def __aenter__(self: Self) -> Self:
...
def __ior__(self: Self, other: Self) -> Self:
...
class Fine:
@overload
def __new__(cls, foo: int) -> FineSubclass:
...
@overload
def __new__(cls, *args: Any, **kwargs: Any) -> Fine:
...
@abc.abstractmethod
def __str__(self) -> str:
...
@abc.abstractmethod
def __repr__(self) -> str:
...
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any:
...
def __ne__(self, *, kw_only_other: Any) -> bool:
...
def __enter__(self) -> None:
...
async def __aenter__(self) -> bool:
...
class FineSubclass(Fine):
...
class StrangeButAcceptable(str):
@typing_extensions.overload
def __new__(cls, foo: int) -> StrangeButAcceptableSubclass:
...
@typing_extensions.overload
def __new__(cls, *args: Any, **kwargs: Any) -> StrangeButAcceptable:
...
def __str__(self) -> StrangeButAcceptable:
...
def __repr__(self) -> StrangeButAcceptable:
...
class StrangeButAcceptableSubclass(StrangeButAcceptable):
...
class FineAndDandy:
def __str__(self, weird_extra_arg) -> str:
...
def __repr__(self, weird_extra_arg_with_default=...) -> str:
...
@final
class WillNotBeSubclassed:
def __new__(cls, *args: Any, **kwargs: Any) -> WillNotBeSubclassed:
...
def __enter__(self) -> WillNotBeSubclassed:
...
async def __aenter__(self) -> WillNotBeSubclassed:
...
# we don't emit an error for these; out of scope for a linter
class InvalidButPluginDoesNotCrash:
def __new__() -> InvalidButPluginDoesNotCrash:
...
def __enter__() -> InvalidButPluginDoesNotCrash:
...
async def __aenter__() -> InvalidButPluginDoesNotCrash:
...
class BadIterator1(Iterator[int]):
def __iter__(self) -> Iterator[int]:
... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator2(
typing.Iterator[int]
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
def __iter__(self) -> Iterator[int]:
... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator3(
typing.Iterator[int]
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
def __iter__(self) -> collections.abc.Iterator[int]:
... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator4(Iterator[int]):
# Note: *Iterable*, not *Iterator*, returned!
def __iter__(self) -> Iterable[int]:
... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
class IteratorReturningIterable:
def __iter__(self) -> Iterable[str]:
... # Y045 "__iter__" methods should return an Iterator, not an Iterable
class BadAsyncIterator(collections.abc.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)
class AsyncIteratorReturningAsyncIterable:
def __aiter__(self) -> AsyncIterable[str]:
... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable
class Abstract(Iterator[str]):
@abstractmethod
def __iter__(self) -> Iterator[str]:
...
@abstractmethod
def __enter__(self) -> Abstract:
...
@abstractmethod
async def __aenter__(self) -> Abstract:
...
class GoodIterator(Iterator[str]):
def __iter__(self: Self) -> Self:
...
class GoodAsyncIterator(AsyncIterator[int]):
def __aiter__(self: Self) -> Self:
...
class DoesNotInheritFromIterator:
def __iter__(self) -> DoesNotInheritFromIterator:
...
class Unannotated:
def __new__(cls, *args, **kwargs):
...
def __iter__(self):
...
def __aiter__(self):
...
async def __aenter__(self):
...
def __repr__(self):
...
def __str__(self):
...
def __eq__(self):
...
def __ne__(self):
...
def __iadd__(self):
...
def __ior__(self):
...
def __repr__(self) -> str:
...
def __str__(self) -> str:
...
def __eq__(self, other: Any) -> bool:
...
def __ne__(self, other: Any) -> bool:
...
def __imul__(self, other: Any) -> list[str]:
...

View File

@@ -1,188 +0,0 @@
# flags: --extend-ignore=Y023
import abc
import builtins
import collections.abc
import typing
from abc import abstractmethod
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
from typing import Any, overload
import typing_extensions
from _typeshed import Self
from typing_extensions import final
class Bad(
object
): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
def __new__(
cls, *args: Any, **kwargs: Any
) -> Bad: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
def __repr__(
self,
) -> str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
def __str__(
self,
) -> builtins.str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
def __eq__(
self, other: Any
) -> bool: ... # Y032 Prefer "object" to "Any" for the second parameter in "__eq__" methods
def __ne__(
self, other: typing.Any
) -> typing.Any: ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
def __enter__(
self,
) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
async def __aenter__(
self,
) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
def __iadd__(
self, other: Bad
) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
class AlsoBad(
int, builtins.object
): ... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
class Good:
def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self: ...
@abstractmethod
def __str__(self) -> str: ...
@abc.abstractmethod
def __repr__(self) -> str: ...
def __eq__(self, other: object) -> bool: ...
def __ne__(self, obj: object) -> int: ...
def __enter__(self: Self) -> Self: ...
async def __aenter__(self: Self) -> Self: ...
def __ior__(self: Self, other: Self) -> Self: ...
class Fine:
@overload
def __new__(cls, foo: int) -> FineSubclass: ...
@overload
def __new__(cls, *args: Any, **kwargs: Any) -> Fine: ...
@abc.abstractmethod
def __str__(self) -> str: ...
@abc.abstractmethod
def __repr__(self) -> str: ...
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any: ...
def __ne__(self, *, kw_only_other: Any) -> bool: ...
def __enter__(self) -> None: ...
async def __aenter__(self) -> bool: ...
class FineSubclass(Fine): ...
class StrangeButAcceptable(str):
@typing_extensions.overload
def __new__(cls, foo: int) -> StrangeButAcceptableSubclass: ...
@typing_extensions.overload
def __new__(cls, *args: Any, **kwargs: Any) -> StrangeButAcceptable: ...
def __str__(self) -> StrangeButAcceptable: ...
def __repr__(self) -> StrangeButAcceptable: ...
class StrangeButAcceptableSubclass(StrangeButAcceptable): ...
class FineAndDandy:
def __str__(self, weird_extra_arg) -> str: ...
def __repr__(self, weird_extra_arg_with_default=...) -> str: ...
@final
class WillNotBeSubclassed:
def __new__(cls, *args: Any, **kwargs: Any) -> WillNotBeSubclassed: ...
def __enter__(self) -> WillNotBeSubclassed: ...
async def __aenter__(self) -> WillNotBeSubclassed: ...
# we don't emit an error for these; out of scope for a linter
class InvalidButPluginDoesNotCrash:
def __new__() -> InvalidButPluginDoesNotCrash: ...
def __enter__() -> InvalidButPluginDoesNotCrash: ...
async def __aenter__() -> InvalidButPluginDoesNotCrash: ...
class BadIterator1(Iterator[int]):
def __iter__(
self,
) -> Iterator[
int
]: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator2(
typing.Iterator[int]
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
def __iter__(
self,
) -> Iterator[
int
]: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator3(
typing.Iterator[int]
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
def __iter__(
self,
) -> collections.abc.Iterator[
int
]: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator4(Iterator[int]):
# Note: *Iterable*, not *Iterator*, returned!
def __iter__(
self,
) -> Iterable[
int
]: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
class IteratorReturningIterable:
def __iter__(
self,
) -> Iterable[
str
]: ... # Y045 "__iter__" methods should return an Iterator, not an Iterable
class BadAsyncIterator(collections.abc.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)
class AsyncIteratorReturningAsyncIterable:
def __aiter__(
self,
) -> AsyncIterable[
str
]: ... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable
class Abstract(Iterator[str]):
@abstractmethod
def __iter__(self) -> Iterator[str]: ...
@abstractmethod
def __enter__(self) -> Abstract: ...
@abstractmethod
async def __aenter__(self) -> Abstract: ...
class GoodIterator(Iterator[str]):
def __iter__(self: Self) -> Self: ...
class GoodAsyncIterator(AsyncIterator[int]):
def __aiter__(self: Self) -> Self: ...
class DoesNotInheritFromIterator:
def __iter__(self) -> DoesNotInheritFromIterator: ...
class Unannotated:
def __new__(cls, *args, **kwargs): ...
def __iter__(self): ...
def __aiter__(self): ...
async def __aenter__(self): ...
def __repr__(self): ...
def __str__(self): ...
def __eq__(self): ...
def __ne__(self): ...
def __iadd__(self): ...
def __ior__(self): ...
def __repr__(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: Any) -> bool: ...
def __ne__(self, other: Any) -> bool: ...
def __imul__(self, other: Any) -> list[str]: ...

View File

@@ -1,15 +0,0 @@
__all__: list[str]
__all__: list[str] = ["foo"]
class Foo:
__all__: list[str]
__match_args__: tuple[str, ...]
__slots__: tuple[str, ...]
class Bar:
__all__: list[str] = ["foo"]
__match_args__: tuple[str, ...] = (1,)
__slots__: tuple[str, ...] = "foo"

View File

@@ -1,13 +0,0 @@
__all__: list[str] # Error: PYI035
__all__: list[str] = ["foo"]
class Foo:
__all__: list[str]
__match_args__: tuple[str, ...] # Error: PYI035
__slots__: tuple[str, ...] # Error: PYI035
class Bar:
__all__: list[str] = ["foo"]
__match_args__: tuple[str, ...] = (1,)
__slots__: tuple[str, ...] = "foo"

View File

@@ -1,7 +0,0 @@
# Bad import.
from __future__ import annotations # Not PYI044 (not a stubfile).
# Good imports.
from __future__ import Something
import sys
from socket import AF_INET

View File

@@ -1,7 +0,0 @@
# Bad import.
from __future__ import annotations # PYI044.
# Good imports.
from __future__ import Something
import sys
from socket import AF_INET

View File

@@ -1,85 +0,0 @@
import collections.abc
import typing
from collections.abc import Iterator, Iterable
class NoReturn:
def __iter__(self):
...
class TypingIterableTReturn:
def __iter__(self) -> typing.Iterable[int]:
...
def not_iter(self) -> typing.Iterable[int]:
...
class TypingIterableReturn:
def __iter__(self) -> typing.Iterable:
...
def not_iter(self) -> typing.Iterable:
...
class CollectionsIterableTReturn:
def __iter__(self) -> collections.abc.Iterable[int]:
...
def not_iter(self) -> collections.abc.Iterable[int]:
...
class CollectionsIterableReturn:
def __iter__(self) -> collections.abc.Iterable:
...
def not_iter(self) -> collections.abc.Iterable:
...
class IterableReturn:
def __iter__(self) -> Iterable:
...
class IteratorReturn:
def __iter__(self) -> Iterator:
...
class IteratorTReturn:
def __iter__(self) -> Iterator[int]:
...
class TypingIteratorReturn:
def __iter__(self) -> typing.Iterator:
...
class TypingIteratorTReturn:
def __iter__(self) -> typing.Iterator[int]:
...
class CollectionsIteratorReturn:
def __iter__(self) -> collections.abc.Iterator:
...
class CollectionsIteratorTReturn:
def __iter__(self) -> collections.abc.Iterator[int]:
...
class TypingAsyncIterableTReturn:
def __aiter__(self) -> typing.AsyncIterable[int]:
...
class TypingAsyncIterableReturn:
def __aiter__(self) -> typing.AsyncIterable:
...

View File

@@ -1,49 +0,0 @@
import collections.abc
import typing
from collections.abc import Iterator, Iterable
class NoReturn:
def __iter__(self): ...
class TypingIterableTReturn:
def __iter__(self) -> typing.Iterable[int]: ... # Error: PYI045
def not_iter(self) -> typing.Iterable[int]: ...
class TypingIterableReturn:
def __iter__(self) -> typing.Iterable: ... # Error: PYI045
def not_iter(self) -> typing.Iterable: ...
class CollectionsIterableTReturn:
def __iter__(self) -> collections.abc.Iterable[int]: ... # Error: PYI045
def not_iter(self) -> collections.abc.Iterable[int]: ...
class CollectionsIterableReturn:
def __iter__(self) -> collections.abc.Iterable: ... # Error: PYI045
def not_iter(self) -> collections.abc.Iterable: ...
class IterableReturn:
def __iter__(self) -> Iterable: ... # Error: PYI045
class IteratorReturn:
def __iter__(self) -> Iterator: ...
class IteratorTReturn:
def __iter__(self) -> Iterator[int]: ...
class TypingIteratorReturn:
def __iter__(self) -> typing.Iterator: ...
class TypingIteratorTReturn:
def __iter__(self) -> typing.Iterator[int]: ...
class CollectionsIteratorReturn:
def __iter__(self) -> collections.abc.Iterator: ...
class CollectionsIteratorTReturn:
def __iter__(self) -> collections.abc.Iterator[int]: ...
class TypingAsyncIterableTReturn:
def __aiter__(self) -> typing.AsyncIterable[int]: ... # Error: PYI045
class TypingAsyncIterableReturn:
def __aiter__(self) -> typing.AsyncIterable: ... # Error: PYI045

View File

@@ -1,19 +0,0 @@
def bar(): # OK
...
def oof(): # OK, docstrings are handled by another rule
"""oof"""
print("foo")
def foo(): # Ok not in Stub file
"""foo"""
print("foo")
print("foo")
def buzz(): # Ok not in Stub file
print("fizz")
print("buzz")
print("test")

View File

@@ -1,20 +0,0 @@
def bar():
... # OK
def oof(): # OK, docstrings are handled by another rule
"""oof"""
print("foo")
def foo(): # ERROR PYI048
"""foo"""
print("foo")
print("foo")
def buzz(): # ERROR PYI048
print("fizz")
print("buzz")
print("test")

View File

@@ -1,32 +0,0 @@
from typing import NoReturn, Never
import typing_extensions
def foo(arg):
...
def foo_int(arg: int):
...
def foo_no_return(arg: NoReturn):
...
def foo_no_return_typing_extensions(
arg: typing_extensions.NoReturn,
):
...
def foo_no_return_kwarg(arg: int, *, arg2: NoReturn):
...
def foo_no_return_pos_only(arg: int, /, arg2: NoReturn):
...
def foo_never(arg: Never):
...

View File

@@ -1,12 +0,0 @@
from typing import NoReturn, Never
import typing_extensions
def foo(arg): ...
def foo_int(arg: int): ...
def foo_no_return(arg: NoReturn): ... # Error: PYI050
def foo_no_return_typing_extensions(
arg: typing_extensions.NoReturn,
): ... # Error: PYI050
def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050
def foo_no_return_pos_only(arg: int, /, arg2: NoReturn): ... # Error: PYI050
def foo_never(arg: Never): ...

View File

@@ -1,38 +0,0 @@
def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None:
...
def f2(x: str = "51 character stringgggggggggggggggggggggggggggggggg") -> None:
...
def f3(x: str = "50 character stringggggggggggggggggggggggggggggg\U0001f600") -> None:
...
def f4(x: str = "51 character stringgggggggggggggggggggggggggggggg\U0001f600") -> None:
...
def f5(x: bytes = b"50 character byte stringgggggggggggggggggggggggggg") -> None:
...
def f6(x: bytes = b"51 character byte stringgggggggggggggggggggggggggg") -> None:
...
def f7(x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff") -> None:
...
def f8(x: bytes = b"50 character byte stringgggggggggggggggggggggggggg\xff") -> None:
...
foo: str = "50 character stringggggggggggggggggggggggggggggggg"
bar: str = "51 character stringgggggggggggggggggggggggggggggggg"
baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg"
qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff"

View File

@@ -1,30 +0,0 @@
def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
def f2(
x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
) -> None: ...
def f3(
x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
) -> None: ...
def f4(
x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
) -> None: ...
def f5(
x: bytes = b"50 character byte stringgggggggggggggggggggggggggg", # OK
) -> None: ...
def f6(
x: bytes = b"51 character byte stringgggggggggggggggggggggggggg", # Error: PYI053
) -> None: ...
def f7(
x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff", # OK
) -> None: ...
def f8(
x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
) -> None: ...
foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053

View File

@@ -1,20 +0,0 @@
field01: int = 0xFFFFFFFF
field02: int = 0xFFFFFFFFF
field03: int = -0xFFFFFFFF
field04: int = -0xFFFFFFFFF
field05: int = 1234567890
field06: int = 12_456_890
field07: int = 12345678901
field08: int = -1234567801
field09: int = -234_567_890
field10: float = 123.456789
field11: float = 123.4567890
field12: float = -123.456789
field13: float = -123.567_890
field14: complex = 1e1234567j
field15: complex = 1e12345678j
field16: complex = -1e1234567j
field17: complex = 1e123456789j

View File

@@ -1,20 +0,0 @@
field01: int = 0xFFFFFFFF
field02: int = 0xFFFFFFFFF # Error: PYI054
field03: int = -0xFFFFFFFF
field04: int = -0xFFFFFFFFF # Error: PYI054
field05: int = 1234567890
field06: int = 12_456_890
field07: int = 12345678901 # Error: PYI054
field08: int = -1234567801
field09: int = -234_567_890 # Error: PYI054
field10: float = 123.456789
field11: float = 123.4567890 # Error: PYI054
field12: float = -123.456789
field13: float = -123.567_890 # Error: PYI054
field14: complex = 1e1234567j
field15: complex = 1e12345678j # Error: PYI054
field16: complex = -1e1234567j
field17: complex = 1e123456789j # Error: PYI054

View File

@@ -1,25 +1,17 @@
import pytest
# OK
def f():
pytest.fail("this is a failure")
def test_xxx():
pytest.fail("this is a failure") # Test OK arg
def f():
pytest.fail(msg="this is a failure")
def test_xxx():
pytest.fail(msg="this is a failure") # Test OK kwarg
def f():
pytest.fail(reason="this is a failure")
# Errors
def f():
def test_xxx(): # Error
pytest.fail()
pytest.fail("")
pytest.fail(f"")
pytest.fail(msg="")
pytest.fail(msg=f"")
pytest.fail(reason="")
pytest.fail(reason=f"")

View File

@@ -21,13 +21,6 @@ def test_error():
assert something and something_else == """error
message
"""
assert (
something
and something_else
== """error
message
"""
)
# recursive case
assert not (a or not (b or c))
@@ -38,6 +31,14 @@ message
assert not (something or something_else and something_third), "with message"
# detected, but no autofix for mixed conditions (e.g. `a or b and c`)
assert not (something or something_else and something_third)
# detected, but no autofix for parenthesized conditions
assert (
something
and something_else
== """error
message
"""
)
assert something # OK

View File

@@ -79,7 +79,7 @@ def x():
return a
# Ignore unpacking
# ignore unpacking
def x():
b, a = [1, 2]
return a
@@ -109,8 +109,7 @@ def x():
# Considered OK, since functions can have side effects.
def x():
a = 1
b = 2
b, a = 1, 2
print(b)
return a
@@ -273,87 +272,3 @@ def str_to_bool(val):
if isinstance(val, bool):
return some_obj
return val
# Mixed assignments
def function_assignment(x):
def f():
...
return f
def class_assignment(x):
class Foo:
...
return Foo
def mixed_function_assignment(x):
if x:
def f():
...
else:
f = 42
return f
def mixed_class_assignment(x):
if x:
class Foo:
...
else:
Foo = 42
return Foo
# `with` statements
def foo():
with open("foo.txt", "r") as f:
x = f.read()
return x # RET504
def foo():
with open("foo.txt", "r") as f:
x = f.read()
print(x)
return x
def foo():
with open("foo.txt", "r") as f:
x = f.read()
print(x)
return x
# Autofix cases
def foo():
a = 1
b=a
return b # RET504
def foo():
a = 1
b =a
return b # RET504
def foo():
a = 1
b= a
return b # RET504
def foo():
a = 1 # Comment
return a

View File

@@ -53,9 +53,6 @@ class Foo(metaclass=BazMeta):
def __really_private_func(self, arg):
super().__really_private_func(arg)
def __eq__(self, other):
return self._private_thing == other._private_thing
foo = Foo()

View File

@@ -86,16 +86,9 @@ while x > 0:
):
print("Bad module!")
# SIM102 (auto-fixable)
if node.module012345678:
if node.module == "multiprocß9💣2" or node.module.startswith(
"multiprocessing."
):
print("Bad module!")
# SIM102 (not auto-fixable)
if node.module0123456789:
if node.module == "multiprocß9💣2" or node.module.startswith(
# SIM102
if node.module:
if node.module == "multiprocessing" or node.module.startswith(
"multiprocessing."
):
print("Bad module!")

View File

@@ -80,25 +80,17 @@ else:
# SIM108
if a:
b = "cccccccccccccccccccccccccccccccccß"
b = cccccccccccccccccccccccccccccccccccc
else:
b = "ddddddddddddddddddddddddddddddddd💣"
b = ddddddddddddddddddddddddddddddddddddd
# OK (too long)
if True:
if a:
b = ccccccccccccccccccccccccccccccccccc
b = cccccccccccccccccccccccccccccccccccc
else:
b = ddddddddddddddddddddddddddddddddddd
# OK (too long with tabs)
if True:
if a:
b = ccccccccccccccccccccccccccccccccccc
else:
b = ddddddddddddddddddddddddddddddddddd
b = ddddddddddddddddddddddddddddddddddddd
# SIM108 (without fix due to trailing comment)

View File

@@ -155,33 +155,3 @@ def f():
if check(x):
return False
return True
def f():
# SIM110
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2":
if x.isdigit():
return True
return False
def f():
# OK (too long)
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29":
if x.isdigit():
return True
return False
async def f():
# OK
for x in iterable:
if await check(x):
return True
return False
async def f():
# SIM110
for x in iterable:
if check(x):
return True
return False

View File

@@ -171,19 +171,3 @@ def f():
if x > y:
return False
return True
def f():
# SIM111
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9":
if x.isdigit():
return False
return True
def f():
# OK (too long)
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß90":
if x.isdigit():
return False
return True

View File

@@ -33,17 +33,17 @@ with A() as a:
print("hello")
a()
# OK, can't merge async with and with.
# OK
async with A() as a:
with B() as b:
print("hello")
# OK, can't merge async with and with.
# OK
with A() as a:
async with B() as b:
print("hello")
# SIM117
# OK
async with A() as a:
async with B() as b:
print("hello")
@@ -90,34 +90,3 @@ with (
D() as d,
):
print("hello")
# SIM117 (auto-fixable)
with A("01ß9💣28901ß9💣28901ß9💣289") as a:
with B("01ß9💣28901ß9💣28901ß9💣289") as b:
print("hello")
# SIM117 (not auto-fixable too long)
with A("01ß9💣28901ß9💣28901ß9💣2890") as a:
with B("01ß9💣28901ß9💣28901ß9💣289") as b:
print("hello")
# From issue #3025.
async def main():
async with A() as a: # SIM117.
async with B() as b:
print("async-inside!")
return 0
# OK. Can't merge across different kinds of with statements.
with a as a2:
async with b as b2:
with c as c2:
async with d as d2:
f(a2, b2, c2, d2)
# OK. Can't merge across different kinds of with statements.
async with b as b2:
with c as c2:
async with d as d2:
f(b2, c2, d2)

View File

@@ -36,18 +36,12 @@ else:
if key in a_dict:
vars[idx] = a_dict[key]
else:
vars[idx] = "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789"
vars[idx] = "default"
###
# Negative cases
###
# OK (too long)
if key in a_dict:
vars[idx] = a_dict[key]
else:
vars[idx] = "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789ß"
# OK (false negative)
if not key in a_dict:
var = "default"

View File

@@ -1,6 +0,0 @@
class Bad(str): # SLOT000
pass
class Good(str): # Ok
__slots__ = ["foo"]

View File

@@ -1,21 +0,0 @@
class Bad(tuple): # SLOT001
pass
class Good(tuple): # Ok
__slots__ = ("foo",)
from typing import Tuple
class Bad(Tuple): # SLOT001
pass
class Bad(Tuple[str, int, float]): # SLOT001
pass
class Good(Tuple[str, int, float]): # OK
__slots__ = ("foo",)

View File

@@ -1,14 +0,0 @@
from collections import namedtuple
from typing import NamedTuple
class Bad(namedtuple("foo", ["str", "int"])): # SLOT002
pass
class Good(namedtuple("foo", ["str", "int"])): # OK
__slots__ = ("foo",)
class Good(NamedTuple): # Ok
pass

View File

@@ -6,4 +6,3 @@
# T001 - errors
# XXX (evanrittenhouse): this is not fine
# FIXME (evanrittenhouse): this is not fine
# foo # XXX: this isn't fine either

View File

@@ -5,4 +5,3 @@
# TODO: this has no author
# FIXME: neither does this
# TODO : and neither does this
# foo # TODO: this doesn't either

View File

@@ -1,6 +1,6 @@
# TDO003 - accepted
# TODO: this comment has a link
# https://github.com/astral-sh/ruff/issues/3870
# https://github.com/charliermarsh/ruff/issues/3870
# TODO: this comment has an issue
# TDO-3870
@@ -26,6 +26,4 @@ def foo(x):
# TODO: followed by a new TODO with an issue link
# TDO-3870
# foo # TODO: no link!
# TODO: here's a TODO on the last line with no link

View File

@@ -4,5 +4,3 @@
# TODO this has no colon
# TODO(evanrittenhouse 😀) this has no colon
# FIXME add a colon
# foo # TODO add a colon
# TODO this has a colon but it doesn't terminate the tag, so this should throw. https://www.google.com

View File

@@ -4,4 +4,3 @@
# TODO(evanrittenhouse):
# TODO(evanrittenhouse)
# FIXME
# foo # TODO

View File

@@ -3,4 +3,3 @@
# TDO006 - error
# ToDo (evanrittenhouse): invalid capitalization
# todo (evanrittenhouse): another invalid capitalization
# foo # todo: invalid capitalization

View File

@@ -6,5 +6,3 @@
# TODO (evanrittenhouse):this doesn't either
# TODO:neither does this
# FIXME:and lastly neither does this
# foo # TODO:this is really the last one
# TODO this colon doesn't terminate the tag, so don't check it. https://www.google.com

View File

@@ -146,29 +146,3 @@ def f():
import pandas as pd
x = dict[pd.DataFrame, pd.DataFrame]
def f():
import pandas as pd
def f():
from pandas import DataFrame # noqa: TCH002
x: DataFrame = 2
def f():
from pandas import ( # noqa: TCH002
DataFrame,
)
x: DataFrame = 2
def f():
global Member
from module import Member
x: Member = 1

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
def f():
# Even in strict mode, this shouldn't raise an error, since `pkg` is used at runtime,
# Even in strict mode, this shouldn't rase an error, since `pkg` is used at runtime,
# and implicitly imports `pkg.bar`.
import pkg
import pkg.bar
@@ -12,7 +12,7 @@ def f():
def f():
# Even in strict mode, this shouldn't raise an error, since `pkg.bar` is used at
# Even in strict mode, this shouldn't rase an error, since `pkg.bar` is used at
# runtime, and implicitly imports `pkg`.
import pkg
import pkg.bar
@@ -22,7 +22,7 @@ def f():
def f():
# In un-strict mode, this shouldn't raise an error, since `pkg` is used at runtime.
# In un-strict mode, this shouldn't rase an error, since `pkg` is used at runtime.
import pkg
from pkg import A
@@ -31,7 +31,7 @@ def f():
def f():
# In un-strict mode, this shouldn't raise an error, since `pkg` is used at runtime.
# In un-strict mode, this shouldn't rase an error, since `pkg` is used at runtime.
from pkg import A, B
def test(value: A):
@@ -39,7 +39,7 @@ def f():
def f():
# Even in strict mode, this shouldn't raise an error, since `pkg.baz` is used at
# Even in strict mode, this shouldn't rase an error, since `pkg.baz` is used at
# runtime, and implicitly imports `pkg.bar`.
import pkg.bar
import pkg.baz
@@ -49,56 +49,9 @@ def f():
def f():
# In un-strict mode, this _should_ raise an error, since `pkg.bar` isn't used at runtime
# In un-strict mode, this _should_ rase an error, since `pkg` is used at runtime.
import pkg
from pkg.bar import A
def test(value: A):
return pkg.B()
def f():
# In un-strict mode, this shouldn't raise an error, since `pkg.bar` is used at runtime.
import pkg
import pkg.bar as B
def test(value: pkg.A):
return B()
def f():
# In un-strict mode, this shouldn't raise an error, since `pkg.foo.bar` is used at runtime.
import pkg.foo as F
import pkg.foo.bar as B
def test(value: F.Foo):
return B()
def f():
# In un-strict mode, this shouldn't raise an error, since `pkg.foo.bar` is used at runtime.
import pkg
import pkg.foo.bar as B
def test(value: pkg.A):
return B()
def f():
# In un-strict mode, this _should_ raise an error, since `pkg` isn't used at runtime.
# Note that `pkg` is a prefix of `pkgfoo` which are both different modules. This is
# testing the implementation.
import pkg
import pkgfoo.bar as B
def test(value: pkg.A):
return B()
def f():
# In un-strict mode, this shouldn't raise an error, since `pkg` is used at runtime.
import pkg.bar as B
import pkg.foo as F
def test(value: F.Foo):
return B.Bar()

View File

@@ -2,7 +2,3 @@ import a
# Don't take this comment into account when determining whether the next import can fit on one line.
from b import c
from d import e # Do take this comment into account when determining whether the next import can fit on one line.
# The next import fits on one line.
from f import g # 012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2
# The next import doesn't fit on one line.
from h import i # 012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29

View File

@@ -1,4 +0,0 @@
# isort: off
x = 1
# isort: on

View File

@@ -6,16 +6,7 @@ import f
import c
import d
# isort: split
# isort: split
import a
import b
if True:
import C
import A
# isort: split
import D
import B

View File

@@ -1,37 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "1",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"\n",
"math.pi"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python (ruff)",
"language": "python",
"name": "ruff"
},
"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.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -1,38 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "1",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"import os\n",
"\n",
"math.pi"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python (ruff)",
"language": "python",
"name": "ruff"
},
"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.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -1,8 +0,0 @@
{
"execution_count": null,
"cell_type": "code",
"id": "1",
"metadata": {},
"outputs": [],
"source": ["def foo():\n", " pass\n", "\n", "%timeit foo()"]
}

View File

@@ -1,6 +0,0 @@
{
"cell_type": "markdown",
"id": "1",
"metadata": {},
"source": ["This is a markdown cell\n", "Some more content"]
}

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