Compare commits
47 Commits
dhruv/redi
...
0.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
244b923f61 | ||
|
|
a8b48fce7e | ||
|
|
04c8597b8a | ||
|
|
4029a25ebd | ||
|
|
0917ce16f4 | ||
|
|
22cebdf29b | ||
|
|
72b6c26101 | ||
|
|
73851e73ab | ||
|
|
e7b49694a7 | ||
|
|
c98d8a040f | ||
|
|
6f2e024cc6 | ||
|
|
fb1d7610ac | ||
|
|
bd845812c7 | ||
|
|
c7b2f2b788 | ||
|
|
8cc96d7868 | ||
|
|
4b3278fe0b | ||
|
|
41203ea208 | ||
|
|
c0d2f439b7 | ||
|
|
b0b68a5601 | ||
|
|
c9a283a5ad | ||
|
|
c54bf0c734 | ||
|
|
1968332d93 | ||
|
|
0a24d70bfd | ||
|
|
a4d711f25f | ||
|
|
c46ae3a3cf | ||
|
|
9e8a45f343 | ||
|
|
36a9efdb48 | ||
|
|
d6a2cad9c2 | ||
|
|
117203f713 | ||
|
|
12effb897c | ||
|
|
bfe36b9584 | ||
|
|
b24e4473c5 | ||
|
|
a4688aebe9 | ||
|
|
e137c824c3 | ||
|
|
55f4812051 | ||
|
|
47c9ed07f2 | ||
|
|
7cb2619ef5 | ||
|
|
83fe44728b | ||
|
|
00e456ead4 | ||
|
|
2853751344 | ||
|
|
7109214b57 | ||
|
|
d930e97212 | ||
|
|
9c1b6ec411 | ||
|
|
692309ebd7 | ||
|
|
68a8978454 | ||
|
|
cd2af3be73 | ||
|
|
e2e98d005c |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -8,6 +8,7 @@ crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py text eol=crlf
|
||||
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
|
||||
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf
|
||||
|
||||
crates/ruff_python_parser/resources/invalid/re_lexing/line_continuation_windows_eol.py text eol=crlf
|
||||
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_windows_eol.py text eol=crlf
|
||||
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_mac_eol.py text eol=cr
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
name: "[ruff] Release"
|
||||
# Build ruff on all platforms.
|
||||
#
|
||||
# Generates both wheels (for PyPI) and archived binaries (for GitHub releases).
|
||||
#
|
||||
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local
|
||||
# artifacts job within `cargo-dist`.
|
||||
name: "Build binaries"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag:
|
||||
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)."
|
||||
type: string
|
||||
sha:
|
||||
description: "The full sha of the commit to be released. If omitted, the latest commit on the default branch will be used."
|
||||
default: ""
|
||||
plan:
|
||||
required: true
|
||||
type: string
|
||||
pull_request:
|
||||
paths:
|
||||
# When we change pyproject.toml, we want to ensure that the maturin builds still work
|
||||
# When we change pyproject.toml, we want to ensure that the maturin builds still work.
|
||||
- pyproject.toml
|
||||
# And when we change this workflow itself...
|
||||
- .github/workflows/release.yaml
|
||||
- .github/workflows/build-binaries.yml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -23,6 +25,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
PACKAGE_NAME: ruff
|
||||
MODULE_NAME: ruff
|
||||
PYTHON_VERSION: "3.11"
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
@@ -31,11 +34,12 @@ env:
|
||||
|
||||
jobs:
|
||||
sdist:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -49,8 +53,8 @@ jobs:
|
||||
- name: "Test sdist"
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
${{ env.MODULE_NAME }} --help
|
||||
python -m ${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload sdist"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -58,11 +62,12 @@ jobs:
|
||||
path: dist
|
||||
|
||||
macos-x86_64:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -74,11 +79,6 @@ jobs:
|
||||
with:
|
||||
target: x86_64
|
||||
args: --release --locked --out dist
|
||||
- name: "Test wheel - x86_64"
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -86,23 +86,29 @@ jobs:
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
ARCHIVE_FILE=ruff-${{ inputs.tag }}-x86_64-apple-darwin.tar.gz
|
||||
tar czvf $ARCHIVE_FILE -C target/x86_64-apple-darwin/release ruff
|
||||
TARGET=x86_64-apple-darwin
|
||||
ARCHIVE_NAME=ruff-$TARGET
|
||||
ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz
|
||||
|
||||
mkdir -p $ARCHIVE_NAME
|
||||
cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries-macos-x86_64
|
||||
name: artifacts-macos-x86_64
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
|
||||
macos-aarch64:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -126,18 +132,24 @@ jobs:
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
ARCHIVE_FILE=ruff-${{ inputs.tag }}-aarch64-apple-darwin.tar.gz
|
||||
tar czvf $ARCHIVE_FILE -C target/aarch64-apple-darwin/release ruff
|
||||
TARGET=aarch64-apple-darwin
|
||||
ARCHIVE_NAME=ruff-$TARGET
|
||||
ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz
|
||||
|
||||
mkdir -p $ARCHIVE_NAME
|
||||
cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries-aarch64-apple-darwin
|
||||
name: artifacts-aarch64-apple-darwin
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
|
||||
windows:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -151,7 +163,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -171,8 +183,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
${{ env.MODULE_NAME }} --help
|
||||
python -m ${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -181,18 +193,19 @@ jobs:
|
||||
- name: "Archive binary"
|
||||
shell: bash
|
||||
run: |
|
||||
ARCHIVE_FILE=ruff-${{ inputs.tag }}-${{ matrix.platform.target }}.zip
|
||||
ARCHIVE_FILE=ruff-${{ matrix.platform.target }}.zip
|
||||
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
|
||||
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries-${{ matrix.platform.target }}
|
||||
name: artifacts-${{ matrix.platform.target }}
|
||||
path: |
|
||||
*.zip
|
||||
*.sha256
|
||||
|
||||
linux:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -202,7 +215,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -219,27 +232,36 @@ jobs:
|
||||
if: ${{ startsWith(matrix.target, 'x86_64') }}
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
${{ env.MODULE_NAME }} --help
|
||||
python -m ${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
shell: bash
|
||||
run: |
|
||||
ARCHIVE_FILE=ruff-${{ inputs.tag }}-${{ matrix.target }}.tar.gz
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
|
||||
set -euo pipefail
|
||||
|
||||
TARGET=${{ matrix.target }}
|
||||
ARCHIVE_NAME=ruff-$TARGET
|
||||
ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz
|
||||
|
||||
mkdir -p $ARCHIVE_NAME
|
||||
cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries-${{ matrix.target }}
|
||||
name: artifacts-${{ matrix.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
|
||||
linux-cross:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -261,11 +283,13 @@ jobs:
|
||||
arch: ppc64
|
||||
# see https://github.com/astral-sh/ruff/issues/10073
|
||||
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
|
||||
- target: arm-unknown-linux-musleabihf
|
||||
arch: arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -282,8 +306,8 @@ jobs:
|
||||
if: matrix.platform.arch != 'ppc64'
|
||||
name: Test wheel
|
||||
with:
|
||||
arch: ${{ matrix.platform.arch }}
|
||||
distro: ubuntu20.04
|
||||
arch: ${{ matrix.platform.arch == 'arm' && 'armv6' || matrix.platform.arch }}
|
||||
distro: ${{ matrix.platform.arch == 'arm' && 'bullseye' || 'ubuntu20.04' }}
|
||||
githubToken: ${{ github.token }}
|
||||
install: |
|
||||
apt-get update
|
||||
@@ -298,19 +322,28 @@ jobs:
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
shell: bash
|
||||
run: |
|
||||
ARCHIVE_FILE=ruff-${{ inputs.tag }}-${{ matrix.platform.target }}.tar.gz
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
|
||||
set -euo pipefail
|
||||
|
||||
TARGET=${{ matrix.platform.target }}
|
||||
ARCHIVE_NAME=ruff-$TARGET
|
||||
ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz
|
||||
|
||||
mkdir -p $ARCHIVE_NAME
|
||||
cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries-${{ matrix.platform.target }}
|
||||
name: artifacts-${{ matrix.platform.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
|
||||
musllinux:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -320,7 +353,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -343,26 +376,35 @@ jobs:
|
||||
apk add python3
|
||||
python -m venv .venv
|
||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
.venv/bin/ruff check --help
|
||||
.venv/bin/${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
shell: bash
|
||||
run: |
|
||||
ARCHIVE_FILE=ruff-${{ inputs.tag }}-${{ matrix.target }}.tar.gz
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
|
||||
set -euo pipefail
|
||||
|
||||
TARGET=${{ matrix.target }}
|
||||
ARCHIVE_NAME=ruff-$TARGET
|
||||
ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz
|
||||
|
||||
mkdir -p $ARCHIVE_NAME
|
||||
cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries-${{ matrix.target }}
|
||||
name: artifacts-${{ matrix.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
|
||||
musllinux-cross:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -376,7 +418,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -400,204 +442,29 @@ jobs:
|
||||
run: |
|
||||
python -m venv .venv
|
||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
.venv/bin/ruff check --help
|
||||
.venv/bin/${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
shell: bash
|
||||
run: |
|
||||
ARCHIVE_FILE=ruff-${{ inputs.tag }}-${{ matrix.platform.target }}.tar.gz
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
|
||||
set -euo pipefail
|
||||
|
||||
TARGET=${{ matrix.platform.target }}
|
||||
ARCHIVE_NAME=ruff-$TARGET
|
||||
ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz
|
||||
|
||||
mkdir -p $ARCHIVE_NAME
|
||||
cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries-${{ matrix.platform.target }}
|
||||
name: artifacts-${{ matrix.platform.target }}
|
||||
path: |
|
||||
*.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@v4
|
||||
with:
|
||||
ref: main # We checkout the main branch to check for the commit
|
||||
- name: Check main branch
|
||||
if: ${{ inputs.sha }}
|
||||
run: |
|
||||
# Fetch the main branch since a shallow checkout is used by default
|
||||
git fetch origin main --unshallow
|
||||
if ! git branch --contains ${{ inputs.sha }} | grep -E '(^|\s)main$'; then
|
||||
echo "The specified sha is not on the main branch" >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Check tag consistency
|
||||
run: |
|
||||
# Switch to the commit we want to release
|
||||
git checkout ${{ inputs.sha }}
|
||||
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
|
||||
|
||||
upload-release:
|
||||
name: Upload to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- macos-aarch64
|
||||
- macos-x86_64
|
||||
- windows
|
||||
- linux
|
||||
- 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
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: wheels-*
|
||||
path: wheels
|
||||
merge-multiple: true
|
||||
- name: Publish to PyPi
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
skip-existing: true
|
||||
packages-dir: wheels
|
||||
verbose: true
|
||||
|
||||
tag-release:
|
||||
name: Tag release
|
||||
runs-on: ubuntu-latest
|
||||
needs: upload-release
|
||||
# If you don't set an input tag, it's a dry run (no uploads).
|
||||
if: ${{ inputs.tag }}
|
||||
permissions:
|
||||
# For git tag
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- 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
|
||||
|
||||
publish-release:
|
||||
name: Publish to GitHub
|
||||
runs-on: ubuntu-latest
|
||||
needs: tag-release
|
||||
# If you don't set an input tag, it's a dry run (no uploads).
|
||||
if: ${{ inputs.tag }}
|
||||
permissions:
|
||||
# For GitHub release publishing
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: binaries-*
|
||||
path: binaries
|
||||
merge-multiple: true
|
||||
- name: "Publish to GitHub"
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: true
|
||||
files: binaries/*
|
||||
tag_name: v${{ inputs.tag }}
|
||||
|
||||
docker-publish:
|
||||
# This action doesn't need to wait on any other task, it's easy to re-tag if something failed and we're validating
|
||||
# the tag here also
|
||||
name: Push Docker image ghcr.io/astral-sh/ruff
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: release
|
||||
permissions:
|
||||
# For the docker push
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/astral-sh/ruff
|
||||
|
||||
- name: Check tag consistency
|
||||
# Unlike validate-tag we don't check if the commit is on the main branch, but it seems good enough since we can
|
||||
# change docker tags
|
||||
if: ${{ inputs.tag }}
|
||||
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: "Build and push Docker image"
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
# Reuse the builder
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
push: ${{ inputs.tag != '' }}
|
||||
tags: ghcr.io/astral-sh/ruff:latest,ghcr.io/astral-sh/ruff:${{ inputs.tag || 'dry-run' }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
# 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: Update dependents
|
||||
runs-on: ubuntu-latest
|
||||
needs: publish-release
|
||||
steps:
|
||||
- name: "Update pre-commit mirror"
|
||||
uses: actions/github-script@v7
|
||||
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',
|
||||
})
|
||||
68
.github/workflows/build-docker.yml
vendored
Normal file
68
.github/workflows/build-docker.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
# Build and publish a Docker image.
|
||||
#
|
||||
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local
|
||||
# artifacts job within `cargo-dist`.
|
||||
#
|
||||
# TODO(charlie): Ideally, the publish step would happen as a publish job within `cargo-dist`, but
|
||||
# sharing the built image as an artifact between jobs is challenging.
|
||||
name: "[ruff] Build Docker image"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
plan:
|
||||
required: true
|
||||
type: string
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/build-docker.yml
|
||||
|
||||
jobs:
|
||||
docker-publish:
|
||||
name: Build Docker image (ghcr.io/astral-sh/ruff)
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/astral-sh/ruff
|
||||
|
||||
- name: Check tag consistency
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
run: |
|
||||
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
|
||||
if [ "${{ fromJson(inputs.plan).announcement_tag }}" != "${version}" ]; then
|
||||
echo "The input tag does not match the version from pyproject.toml:" >&2
|
||||
echo "${{ fromJson(inputs.plan).announcement_tag }}" >&2
|
||||
echo "${version}" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "Releasing ${version}"
|
||||
fi
|
||||
|
||||
- name: "Build and push Docker image"
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
# Reuse the builder
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
push: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
tags: ghcr.io/astral-sh/ruff:latest,ghcr.io/astral-sh/ruff:${{ (inputs.plan != '' && fromJson(inputs.plan).announcement_tag) || 'dry-run' }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
29
.github/workflows/notify-dependents.yml
vendored
Normal file
29
.github/workflows/notify-dependents.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# Notify downstream repositories of a new release.
|
||||
#
|
||||
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a post-announce
|
||||
# job within `cargo-dist`.
|
||||
name: "[ruff] Notify dependents"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
plan:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
update-dependents:
|
||||
name: Notify dependents
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Update pre-commit mirror"
|
||||
uses: actions/github-script@v7
|
||||
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',
|
||||
})
|
||||
34
.github/workflows/publish-pypi.yml
vendored
Normal file
34
.github/workflows/publish-pypi.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# Publish a release to PyPI.
|
||||
#
|
||||
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish job
|
||||
# within `cargo-dist`.
|
||||
name: "[ruff] Publish to PyPI"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
plan:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
pypi-publish:
|
||||
name: Upload to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: release
|
||||
permissions:
|
||||
# For PyPI's trusted publishing.
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: wheels-*
|
||||
path: wheels
|
||||
merge-multiple: true
|
||||
- name: Publish to PyPi
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
skip-existing: true
|
||||
packages-dir: wheels
|
||||
verbose: true
|
||||
249
.github/workflows/release.yml
vendored
Normal file
249
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
# Copyright 2022-2024, axodotdev
|
||||
# SPDX-License-Identifier: MIT or Apache-2.0
|
||||
#
|
||||
# CI that:
|
||||
#
|
||||
# * checks for a Git Tag that looks like a release
|
||||
# * builds artifacts with cargo-dist (archives, installers, hashes)
|
||||
# * uploads those artifacts to temporary workflow zip
|
||||
# * on success, uploads the artifacts to a GitHub Release
|
||||
#
|
||||
# Note that the GitHub Release will be created with a generated
|
||||
# title/body based on your changelogs.
|
||||
|
||||
name: Release
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# This task will run whenever you workflow_dispatch with a tag that looks like a version
|
||||
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
|
||||
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
|
||||
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
|
||||
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
|
||||
#
|
||||
# If PACKAGE_NAME is specified, then the announcement will be for that
|
||||
# package (erroring out if it doesn't have the given version or isn't cargo-dist-able).
|
||||
#
|
||||
# If PACKAGE_NAME isn't specified, then the announcement will be for all
|
||||
# (cargo-dist-able) packages in the workspace with that version (this mode is
|
||||
# intended for workspaces with only one dist-able package, or with all dist-able
|
||||
# packages versioned/released in lockstep).
|
||||
#
|
||||
# If you push multiple tags at once, separate instances of this workflow will
|
||||
# spin up, creating an independent announcement for each one. However, GitHub
|
||||
# will hard limit this to 3 tags per commit, as it will assume more tags is a
|
||||
# mistake.
|
||||
#
|
||||
# If there's a prerelease-style suffix to the version, then the release(s)
|
||||
# will be marked as a prerelease.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release Tag
|
||||
required: true
|
||||
default: dry-run
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
# Run 'cargo dist plan' (or host) to determine what tasks we need to do
|
||||
plan:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
val: ${{ steps.plan.outputs.manifest }}
|
||||
tag: ${{ (inputs.tag != 'dry-run' && inputs.tag) || '' }}
|
||||
tag-flag: ${{ inputs.tag && inputs.tag != 'dry-run' && format('--tag={0}', inputs.tag) || '' }}
|
||||
publishing: ${{ inputs.tag && inputs.tag != 'dry-run' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install cargo-dist
|
||||
# we specify bash to get pipefail; it guards against the `curl` command
|
||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.14.0/cargo-dist-installer.sh | sh"
|
||||
# sure would be cool if github gave us proper conditionals...
|
||||
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
|
||||
# functionality based on whether this is a pull_request, and whether it's from a fork.
|
||||
# (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
|
||||
# but also really annoying to build CI around when it needs secrets to work right.)
|
||||
- id: plan
|
||||
run: |
|
||||
cargo dist ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || 'plan' }} --output-format=json > plan-dist-manifest.json
|
||||
echo "cargo dist ran successfully"
|
||||
cat plan-dist-manifest.json
|
||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifacts-plan-dist-manifest
|
||||
path: plan-dist-manifest.json
|
||||
|
||||
custom-build-binaries:
|
||||
needs:
|
||||
- plan
|
||||
if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
|
||||
uses: ./.github/workflows/build-binaries.yml
|
||||
with:
|
||||
plan: ${{ needs.plan.outputs.val }}
|
||||
secrets: inherit
|
||||
|
||||
custom-build-docker:
|
||||
needs:
|
||||
- plan
|
||||
if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
|
||||
uses: ./.github/workflows/build-docker.yml
|
||||
with:
|
||||
plan: ${{ needs.plan.outputs.val }}
|
||||
secrets: inherit
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
# Build and package all the platform-agnostic(ish) things
|
||||
build-global-artifacts:
|
||||
needs:
|
||||
- plan
|
||||
- custom-build-binaries
|
||||
- custom-build-docker
|
||||
runs-on: "ubuntu-20.04"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install cargo-dist
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.14.0/cargo-dist-installer.sh | sh"
|
||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||
- name: Fetch local artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
merge-multiple: true
|
||||
- id: cargo-dist
|
||||
shell: bash
|
||||
run: |
|
||||
cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
|
||||
echo "cargo dist ran successfully"
|
||||
|
||||
# Parse out what we just built and upload it to scratch storage
|
||||
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
|
||||
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||
- name: "Upload artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifacts-build-global
|
||||
path: |
|
||||
${{ steps.cargo-dist.outputs.paths }}
|
||||
${{ env.BUILD_MANIFEST_NAME }}
|
||||
# Determines if we should publish/announce
|
||||
host:
|
||||
needs:
|
||||
- plan
|
||||
- custom-build-binaries
|
||||
- custom-build-docker
|
||||
- build-global-artifacts
|
||||
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
|
||||
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: "ubuntu-20.04"
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install cargo-dist
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.14.0/cargo-dist-installer.sh | sh"
|
||||
# Fetch artifacts from scratch-storage
|
||||
- name: Fetch artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
merge-multiple: true
|
||||
# This is a harmless no-op for GitHub Releases, hosting for that happens in "announce"
|
||||
- id: host
|
||||
shell: bash
|
||||
run: |
|
||||
cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
|
||||
echo "artifacts uploaded and released successfully"
|
||||
cat dist-manifest.json
|
||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
# Overwrite the previous copy
|
||||
name: artifacts-dist-manifest
|
||||
path: dist-manifest.json
|
||||
|
||||
custom-publish-pypi:
|
||||
needs:
|
||||
- plan
|
||||
- host
|
||||
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
|
||||
uses: ./.github/workflows/publish-pypi.yml
|
||||
with:
|
||||
plan: ${{ needs.plan.outputs.val }}
|
||||
secrets: inherit
|
||||
# publish jobs get escalated permissions
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
# Create a GitHub Release while uploading all files to it
|
||||
announce:
|
||||
needs:
|
||||
- plan
|
||||
- host
|
||||
- custom-publish-pypi
|
||||
# use "always() && ..." to allow us to wait for all publish jobs while
|
||||
# still allowing individual publish jobs to skip themselves (for prereleases).
|
||||
# "host" however must run to completion, no skipping allowed!
|
||||
if: ${{ always() && needs.host.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') }}
|
||||
runs-on: "ubuntu-20.04"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: "Download GitHub Artifacts"
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: artifacts
|
||||
merge-multiple: true
|
||||
- name: Cleanup
|
||||
run: |
|
||||
# Remove the granular manifests
|
||||
rm -f artifacts/*-dist-manifest.json
|
||||
- name: Create GitHub Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: ${{ needs.plan.outputs.tag }}
|
||||
name: ${{ fromJson(needs.host.outputs.val).announcement_title }}
|
||||
body: ${{ fromJson(needs.host.outputs.val).announcement_github_body }}
|
||||
prerelease: ${{ fromJson(needs.host.outputs.val).announcement_is_prerelease }}
|
||||
artifacts: "artifacts/*"
|
||||
|
||||
custom-notify-dependents:
|
||||
needs:
|
||||
- plan
|
||||
- announce
|
||||
uses: ./.github/workflows/notify-dependents.yml
|
||||
with:
|
||||
plan: ${{ needs.plan.outputs.val }}
|
||||
secrets: inherit
|
||||
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
# Auto-generated by `cargo-dist`.
|
||||
.github/workflows/release.yml
|
||||
@@ -1,5 +1,12 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.5.0
|
||||
|
||||
- Follow the XDG specification to discover user-level configurations on macOS (same as on other Unix platforms)
|
||||
- Selecting `ALL` now excludes deprecated rules
|
||||
- The released archives now include an extra level of nesting, which can be removed with `--strip-components=1` when untarring.
|
||||
- The release artifact's file name no longer includes the version tag. This enables users to install via `/latest` URLs on GitHub.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Ruff 2024.2 style
|
||||
|
||||
136
CHANGELOG.md
136
CHANGELOG.md
@@ -1,5 +1,141 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.0
|
||||
|
||||
Check out the [blog post](https://astral.sh/blog/ruff-v0.5.0) for a migration guide and overview of the changes!
|
||||
|
||||
### Breaking changes
|
||||
|
||||
See also, the "Remapped rules" section which may result in disabled rules.
|
||||
|
||||
- Follow the XDG specification to discover user-level configurations on macOS (same as on other Unix platforms)
|
||||
- Selecting `ALL` now excludes deprecated rules
|
||||
- The released archives now include an extra level of nesting, which can be removed with `--strip-components=1` when untarring.
|
||||
- The release artifact's file name no longer includes the version tag. This enables users to install via `/latest` URLs on GitHub.
|
||||
|
||||
### Deprecations
|
||||
|
||||
The following rules are now deprecated:
|
||||
|
||||
- [`syntax-error`](https://docs.astral.sh/ruff/rules/syntax-error/) (`E999`): Syntax errors are now always shown
|
||||
|
||||
### Remapped rules
|
||||
|
||||
The following rules have been remapped to new rule codes:
|
||||
|
||||
- [`blocking-http-call-in-async-function`](https://docs.astral.sh/ruff/rules/blocking-http-call-in-async-function/): `ASYNC100` to `ASYNC210`
|
||||
- [`open-sleep-or-subprocess-in-async-function`](https://docs.astral.sh/ruff/rules/open-sleep-or-subprocess-in-async-function/): `ASYNC101` split into `ASYNC220`, `ASYNC221`, `ASYNC230`, and `ASYNC251`
|
||||
- [`blocking-os-call-in-async-function`](https://docs.astral.sh/ruff/rules/blocking-os-call-in-async-function/): `ASYNC102` has been merged into `ASYNC220` and `ASYNC221`
|
||||
- [`trio-timeout-without-await`](https://docs.astral.sh/ruff/rules/trio-timeout-without-await/): `TRIO100` to `ASYNC100`
|
||||
- [`trio-sync-call`](https://docs.astral.sh/ruff/rules/trio-sync-call/): `TRIO105` to `ASYNC105`
|
||||
- [`trio-async-function-with-timeout`](https://docs.astral.sh/ruff/rules/trio-async-function-with-timeout/): `TRIO109` to `ASYNC109`
|
||||
- [`trio-unneeded-sleep`](https://docs.astral.sh/ruff/rules/trio-unneeded-sleep/): `TRIO110` to `ASYNC110`
|
||||
- [`trio-zero-sleep-call`](https://docs.astral.sh/ruff/rules/trio-zero-sleep-call/): `TRIO115` to `ASYNC115`
|
||||
- [`repeated-isinstance-calls`](https://docs.astral.sh/ruff/rules/repeated-isinstance-calls/): `PLR1701` to `SIM101`
|
||||
|
||||
### Stabilization
|
||||
|
||||
The following rules have been stabilized and are no longer in preview:
|
||||
|
||||
- [`mutable-fromkeys-value`](https://docs.astral.sh/ruff/rules/mutable-fromkeys-value/) (`RUF024`)
|
||||
- [`default-factory-kwarg`](https://docs.astral.sh/ruff/rules/default-factory-kwarg/) (`RUF026`)
|
||||
- [`django-extra`](https://docs.astral.sh/ruff/rules/django-extra/) (`S610`)
|
||||
- [`manual-dict-comprehension`](https://docs.astral.sh/ruff/rules/manual-dict-comprehension/) (`PERF403`)
|
||||
- [`print-empty-string`](https://docs.astral.sh/ruff/rules/print-empty-string/) (`FURB105`)
|
||||
- [`readlines-in-for`](https://docs.astral.sh/ruff/rules/readlines-in-for/) (`FURB129`)
|
||||
- [`if-expr-min-max`](https://docs.astral.sh/ruff/rules/if-expr-min-max/) (`FURB136`)
|
||||
- [`bit-count`](https://docs.astral.sh/ruff/rules/bit-count/) (`FURB161`)
|
||||
- [`redundant-log-base`](https://docs.astral.sh/ruff/rules/redundant-log-base/) (`FURB163`)
|
||||
- [`regex-flag-alias`](https://docs.astral.sh/ruff/rules/regex-flag-alias/) (`FURB167`)
|
||||
- [`isinstance-type-none`](https://docs.astral.sh/ruff/rules/isinstance-type-none/) (`FURB168`)
|
||||
- [`type-none-comparison`](https://docs.astral.sh/ruff/rules/type-none-comparison/) (`FURB169`)
|
||||
- [`implicit-cwd`](https://docs.astral.sh/ruff/rules/implicit-cwd/) (`FURB177`)
|
||||
- [`hashlib-digest-hex`](https://docs.astral.sh/ruff/rules/hashlib-digest-hex/) (`FURB181`)
|
||||
- [`list-reverse-copy`](https://docs.astral.sh/ruff/rules/list-reverse-copy/) (`FURB187`)
|
||||
- [`bad-open-mode`](https://docs.astral.sh/ruff/rules/bad-open-mode/) (`PLW1501`)
|
||||
- [`empty-comment`](https://docs.astral.sh/ruff/rules/empty-comment/) (`PLR2044`)
|
||||
- [`global-at-module-level`](https://docs.astral.sh/ruff/rules/global-at-module-level/) (`PLW0604`)
|
||||
- [`misplaced-bare-raise`](https://docs.astral.sh/ruff/rules/misplaced-bare-raise%60/) (`PLE0744`)
|
||||
- [`non-ascii-import-name`](https://docs.astral.sh/ruff/rules/non-ascii-import-name/) (`PLC2403`)
|
||||
- [`non-ascii-name`](https://docs.astral.sh/ruff/rules/non-ascii-name/) (`PLC2401`)
|
||||
- [`nonlocal-and-global`](https://docs.astral.sh/ruff/rules/nonlocal-and-global/) (`PLE0115`)
|
||||
- [`potential-index-error`](https://docs.astral.sh/ruff/rules/potential-index-error/) (`PLE0643`)
|
||||
- [`redeclared-assigned-name`](https://docs.astral.sh/ruff/rules/redeclared-assigned-name/) (`PLW0128`)
|
||||
- [`redefined-argument-from-local`](https://docs.astral.sh/ruff/rules/redefined-argument-from-local/) (`PLR1704`)
|
||||
- [`repeated-keyword-argument`](https://docs.astral.sh/ruff/rules/repeated-keyword-argument/) (`PLE1132`)
|
||||
- [`super-without-brackets`](https://docs.astral.sh/ruff/rules/super-without-brackets/) (`PLW0245`)
|
||||
- [`unnecessary-list-index-lookup`](https://docs.astral.sh/ruff/rules/unnecessary-list-index-lookup/) (`PLR1736`)
|
||||
- [`useless-exception-statement`](https://docs.astral.sh/ruff/rules/useless-exception-statement/) (`PLW0133`)
|
||||
- [`useless-with-lock`](https://docs.astral.sh/ruff/rules/useless-with-lock/) (`PLW2101`)
|
||||
|
||||
The following behaviors have been stabilized:
|
||||
|
||||
- [`is-literal`](https://docs.astral.sh/ruff/rules/is-literal/) (`F632`) now warns for identity checks against list, set or dictionary literals
|
||||
- [`needless-bool`](https://docs.astral.sh/ruff/rules/needless-bool/) (`SIM103`) now detects `if` expressions with implicit `else` branches
|
||||
- [`module-import-not-at-top-of-file`](https://docs.astral.sh/ruff/rules/module-import-not-at-top-of-file/) (`E402`) now allows `os.environ` modifications between import statements
|
||||
- [`type-comparison`](https://docs.astral.sh/ruff/rules/type-comparison/) (`E721`) now allows idioms such as `type(x) is int`
|
||||
- [`yoda-condition`](https://docs.astral.sh/ruff/rules/yoda-conditions/) (`SIM300`) now flags a wider range of expressions
|
||||
|
||||
### Removals
|
||||
|
||||
The following deprecated settings have been removed:
|
||||
|
||||
- `output-format=text`; use `output-format=concise` or `output-format=full`
|
||||
- `tab-size`; use `indent-width`
|
||||
|
||||
The following deprecated CLI options have been removed:
|
||||
|
||||
- `--show-source`; use `--output-format=full`
|
||||
- `--no-show-source`; use `--output-format=concise`
|
||||
|
||||
The following deprecated CLI commands have been removed:
|
||||
|
||||
- `ruff <path>`; use `ruff check <path>`
|
||||
- `ruff --clean`; use `ruff clean`
|
||||
- `ruff --generate-shell-completion`; use `ruff generate-shell-completion`
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`ruff`\] Add `assert-with-print-message` rule ([#11981](https://github.com/astral-sh/ruff/pull/11981))
|
||||
|
||||
### CLI
|
||||
|
||||
- Use rule name rather than message in `--statistics` ([#11697](https://github.com/astral-sh/ruff/pull/11697))
|
||||
- Use the output format `full` by default ([#12010](https://github.com/astral-sh/ruff/pull/12010))
|
||||
- Don't log syntax errors to the console ([#11902](https://github.com/astral-sh/ruff/pull/11902))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`ruff`\] Fix false positives if `gettext` is imported using an alias (`RUF027`) ([#12025](https://github.com/astral-sh/ruff/pull/12025))
|
||||
- \[`npy`\] Update `trapz` and `in1d` deprecation (`NPY201`) ([#11948](https://github.com/astral-sh/ruff/pull/11948))
|
||||
- \[`flake8-bandit`\] Modify diagnostic ranges for shell-related rules ([#10667](https://github.com/astral-sh/ruff/pull/10667))
|
||||
|
||||
### Server
|
||||
|
||||
- Closing an untitled, unsaved notebook document no longer throws an error ([#11942](https://github.com/astral-sh/ruff/pull/11942))
|
||||
- Support the usage of tildes and environment variables in `logFile` ([#11945](https://github.com/astral-sh/ruff/pull/11945))
|
||||
- Add option to configure whether to show syntax errors ([#12059](https://github.com/astral-sh/ruff/pull/12059))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`pycodestyle`\] Avoid `E203` for f-string debug expression ([#12024](https://github.com/astral-sh/ruff/pull/12024))
|
||||
- \[`pep8-naming`\] Match import-name ignores against both name and alias (`N812`, `N817`) ([#12033](https://github.com/astral-sh/ruff/pull/12033))
|
||||
- \[`pyflakes`\] Detect assignments that shadow definitions (`F811`) ([#11961](https://github.com/astral-sh/ruff/pull/11961))
|
||||
|
||||
### Parser
|
||||
|
||||
- Emit a syntax error for an empty type parameter list ([#12030](https://github.com/astral-sh/ruff/pull/12030))
|
||||
- Avoid consuming the newline for unterminated strings ([#12067](https://github.com/astral-sh/ruff/pull/12067))
|
||||
- Do not include the newline in the unterminated string range ([#12017](https://github.com/astral-sh/ruff/pull/12017))
|
||||
- Use the correct range to highlight line continuation errors ([#12016](https://github.com/astral-sh/ruff/pull/12016))
|
||||
- Consider 2-character EOL before line continuations ([#12035](https://github.com/astral-sh/ruff/pull/12035))
|
||||
- Consider line continuation character for re-lexing ([#12008](https://github.com/astral-sh/ruff/pull/12008))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Upgrade the Unicode table used for measuring the line-length ([#11194](https://github.com/astral-sh/ruff/pull/11194))
|
||||
- Remove the deprecation error message for the nursery selector ([#10172](https://github.com/astral-sh/ruff/pull/10172))
|
||||
|
||||
## 0.4.10
|
||||
|
||||
### Parser
|
||||
|
||||
@@ -348,7 +348,6 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
||||
1. Merge the PR
|
||||
1. Run the [release workflow](https://github.com/astral-sh/ruff/actions/workflows/release.yaml) with:
|
||||
- The new version number (without starting `v`)
|
||||
- The commit hash of the merged release pull request on `main`
|
||||
1. The release workflow will do the following:
|
||||
1. Build all the assets. If this fails (even though we tested in step 4), we haven't tagged or
|
||||
uploaded anything, you can restart after pushing a fix. If you just need to rerun the build,
|
||||
@@ -360,10 +359,8 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
||||
1. Attach artifacts to draft GitHub release
|
||||
1. Trigger downstream repositories. This can fail non-catastrophically, as we can run any
|
||||
downstream jobs manually if needed.
|
||||
1. Publish the GitHub release
|
||||
1. Open the draft release in the GitHub release section
|
||||
1. Copy the changelog for the release into the GitHub release
|
||||
- See previous releases for formatting of section headers
|
||||
1. Verify the GitHub release:
|
||||
1. The Changelog should match the content of `CHANGELOG.md`
|
||||
1. Append the contributors from the `bump.sh` script
|
||||
1. If needed, [update the schemastore](https://github.com/astral-sh/ruff/blob/main/scripts/update_schemastore.py).
|
||||
1. One can determine if an update is needed when
|
||||
|
||||
21
Cargo.lock
generated
21
Cargo.lock
generated
@@ -754,6 +754,17 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "etcetera"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"home",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.12"
|
||||
@@ -1974,7 +1985,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.4.10"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2153,7 +2164,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.4.10"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2493,7 +2504,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"colored",
|
||||
"dirs 5.0.1",
|
||||
"etcetera",
|
||||
"glob",
|
||||
"globset",
|
||||
"ignore",
|
||||
@@ -3248,9 +3259,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.11"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_names2"
|
||||
|
||||
59
Cargo.toml
59
Cargo.toml
@@ -58,9 +58,9 @@ countme = { version = "3.0.1" }
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dashmap = { version = "5.5.3" }
|
||||
dirs = { version = "5.0.0" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
env_logger = { version = "0.11.0" }
|
||||
etcetera = { version = "0.8.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.23" }
|
||||
glob = { version = "0.3.1" }
|
||||
@@ -219,3 +219,60 @@ opt-level = 1
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
debug = 1
|
||||
|
||||
# The profile that 'cargo dist' will build with.
|
||||
[profile.dist]
|
||||
inherits = "release"
|
||||
|
||||
# Config for 'cargo dist'
|
||||
[workspace.metadata.dist]
|
||||
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
|
||||
cargo-dist-version = "0.14.0"
|
||||
# CI backends to support
|
||||
ci = ["github"]
|
||||
# The installers to generate for each app
|
||||
installers = ["shell", "powershell"]
|
||||
# The archive format to use for windows builds (defaults .zip)
|
||||
windows-archive = ".zip"
|
||||
# The archive format to use for non-windows builds (defaults .tar.xz)
|
||||
unix-archive = ".tar.gz"
|
||||
# Target platforms to build apps for (Rust target-triple syntax)
|
||||
targets = [
|
||||
"aarch64-apple-darwin",
|
||||
"aarch64-pc-windows-msvc",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-musl",
|
||||
"arm-unknown-linux-musleabihf",
|
||||
"armv7-unknown-linux-gnueabihf",
|
||||
"armv7-unknown-linux-musleabihf",
|
||||
"i686-pc-windows-msvc",
|
||||
"i686-unknown-linux-gnu",
|
||||
"i686-unknown-linux-musl",
|
||||
"powerpc64-unknown-linux-gnu",
|
||||
"powerpc64le-unknown-linux-gnu",
|
||||
"s390x-unknown-linux-gnu",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-musl",
|
||||
]
|
||||
# Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true)
|
||||
auto-includes = false
|
||||
# Whether cargo-dist should create a Github Release or use an existing draft
|
||||
create-release = true
|
||||
# Publish jobs to run in CI
|
||||
pr-run-mode = "skip"
|
||||
# Whether CI should trigger releases with dispatches instead of tag pushes
|
||||
dispatch-releases = true
|
||||
# Whether CI should include auto-generated code to build local artifacts
|
||||
build-local-artifacts = false
|
||||
# Local artifacts jobs to run in CI
|
||||
local-artifacts-jobs = ["./build-binaries", "./build-docker"]
|
||||
# Publish jobs to run in CI
|
||||
publish-jobs = ["./publish-pypi"]
|
||||
# Announcement jobs to run in CI
|
||||
post-announce-jobs = ["./notify-dependents"]
|
||||
# Skip checking whether the specified configuration files are up to date
|
||||
allow-dirty = ["ci"]
|
||||
# Whether to install an updater program
|
||||
install-updater = false
|
||||
|
||||
@@ -152,7 +152,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.4.10
|
||||
rev: v0.5.0
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -334,7 +334,6 @@ quality tools, including:
|
||||
- [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-trio](https://pypi.org/project/flake8-trio/)
|
||||
- [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))
|
||||
|
||||
@@ -16,5 +16,6 @@ jod = "jod" # e.g., `jod-thread`
|
||||
[default]
|
||||
extend-ignore-re = [
|
||||
# Line ignore with trailing "spellchecker:disable-line"
|
||||
"(?Rm)^.*#\\s*spellchecker:disable-line$"
|
||||
"(?Rm)^.*#\\s*spellchecker:disable-line$",
|
||||
"LICENSEs",
|
||||
]
|
||||
|
||||
@@ -271,17 +271,16 @@ impl SourceOrderVisitor<'_> for SemanticIndexer {
|
||||
let node_key = NodeKey::from_node(expr.into());
|
||||
let expression_id = self.expressions_by_id.push(node_key);
|
||||
|
||||
debug_assert_eq!(
|
||||
expression_id,
|
||||
self.flow_graph_builder
|
||||
.record_expr(self.current_flow_node())
|
||||
);
|
||||
let flow_expression_id = self
|
||||
.flow_graph_builder
|
||||
.record_expr(self.current_flow_node());
|
||||
debug_assert_eq!(expression_id, flow_expression_id);
|
||||
|
||||
debug_assert_eq!(
|
||||
expression_id,
|
||||
self.symbol_table_builder
|
||||
.record_expression(self.cur_scope())
|
||||
);
|
||||
let symbol_expression_id = self
|
||||
.symbol_table_builder
|
||||
.record_expression(self.cur_scope());
|
||||
|
||||
debug_assert_eq!(expression_id, symbol_expression_id);
|
||||
|
||||
self.expressions.insert(node_key, expression_id);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.4.10"
|
||||
publish = false
|
||||
version = "0.5.0"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::{anyhow, bail};
|
||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||
use clap::{command, Parser};
|
||||
use colored::Colorize;
|
||||
@@ -18,10 +18,10 @@ use ruff_linter::line_width::LineLength;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::types::{
|
||||
ExtensionPair, FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion,
|
||||
SerializationFormat, UnsafeFixes,
|
||||
ExtensionPair, FilePattern, OutputFormat, PatternPrefixPair, PerFileIgnore, PreviewMode,
|
||||
PythonVersion, UnsafeFixes,
|
||||
};
|
||||
use ruff_linter::{warn_user, RuleParser, RuleSelector, RuleSelectorParser};
|
||||
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
||||
use ruff_source_file::{LineIndex, OneIndexed};
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
@@ -78,7 +78,7 @@ impl GlobalConfigArgs {
|
||||
#[command(
|
||||
author,
|
||||
name = "ruff",
|
||||
about = "Ruff: An extremely fast Python linter.",
|
||||
about = "Ruff: An extremely fast Python linter and code formatter.",
|
||||
after_help = "For help with a specific command, see: `ruff help <command>`."
|
||||
)]
|
||||
#[command(version)]
|
||||
@@ -95,7 +95,6 @@ pub enum Command {
|
||||
/// Run Ruff on the given files or directories (default).
|
||||
Check(CheckCommand),
|
||||
/// Explain a rule (or all rules).
|
||||
#[clap(alias = "--explain")]
|
||||
#[command(group = clap::ArgGroup::new("selector").multiple(false).required(true))]
|
||||
Rule {
|
||||
/// Rule to explain
|
||||
@@ -125,10 +124,9 @@ pub enum Command {
|
||||
output_format: HelpFormat,
|
||||
},
|
||||
/// Clear any caches in the current directory and any subdirectories.
|
||||
#[clap(alias = "--clean")]
|
||||
Clean,
|
||||
/// Generate shell completion.
|
||||
#[clap(alias = "--generate-shell-completion", hide = true)]
|
||||
#[clap(hide = true)]
|
||||
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
||||
/// Run the Ruff formatter on the given files or directories.
|
||||
Format(FormatCommand),
|
||||
@@ -160,13 +158,6 @@ pub struct CheckCommand {
|
||||
unsafe_fixes: bool,
|
||||
#[arg(long, overrides_with("unsafe_fixes"), hide = true)]
|
||||
no_unsafe_fixes: bool,
|
||||
/// Show violations with source code.
|
||||
/// Use `--no-show-source` to disable.
|
||||
/// (Deprecated: use `--output-format=full` or `--output-format=concise` instead of `--show-source` and `--no-show-source`, respectively)
|
||||
#[arg(long, overrides_with("no_show_source"))]
|
||||
show_source: bool,
|
||||
#[clap(long, overrides_with("show_source"), hide = true)]
|
||||
no_show_source: bool,
|
||||
/// Show an enumeration of all fixed lint violations.
|
||||
/// Use `--no-show-fixes` to disable.
|
||||
#[arg(long, overrides_with("no_show_fixes"))]
|
||||
@@ -194,7 +185,7 @@ pub struct CheckCommand {
|
||||
/// The default serialization format is "concise".
|
||||
/// In preview mode, the default serialization format is "full".
|
||||
#[arg(long, value_enum, env = "RUFF_OUTPUT_FORMAT")]
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
pub output_format: Option<OutputFormat>,
|
||||
|
||||
/// Specify file to write the linter output to (default: stdout).
|
||||
#[arg(short, long, env = "RUFF_OUTPUT_FILE")]
|
||||
@@ -365,7 +356,6 @@ pub struct CheckCommand {
|
||||
long,
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "diff",
|
||||
conflicts_with = "show_source",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub statistics: bool,
|
||||
@@ -701,11 +691,7 @@ impl CheckCommand {
|
||||
unsafe_fixes: resolve_bool_arg(self.unsafe_fixes, self.no_unsafe_fixes)
|
||||
.map(UnsafeFixes::from),
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
output_format: resolve_output_format(
|
||||
self.output_format,
|
||||
resolve_bool_arg(self.show_source, self.no_show_source),
|
||||
resolve_bool_arg(self.preview, self.no_preview).unwrap_or_default(),
|
||||
),
|
||||
output_format: resolve_output_format(self.output_format)?,
|
||||
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
||||
extension: self.extension,
|
||||
};
|
||||
@@ -933,41 +919,15 @@ The path `{value}` does not point to a configuration file"
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn resolve_output_format(
|
||||
output_format: Option<SerializationFormat>,
|
||||
show_sources: Option<bool>,
|
||||
preview: bool,
|
||||
) -> Option<SerializationFormat> {
|
||||
Some(match (output_format, show_sources) {
|
||||
(Some(o), None) => o,
|
||||
(Some(SerializationFormat::Grouped), Some(true)) => {
|
||||
warn_user!("`--show-source` with `--output-format=grouped` is deprecated, and will not show source files. Use `--output-format=full` to show source information.");
|
||||
SerializationFormat::Grouped
|
||||
}
|
||||
(Some(fmt), Some(true)) => {
|
||||
warn_user!("The `--show-source` argument is deprecated and has been ignored in favor of `--output-format={fmt}`.");
|
||||
fmt
|
||||
}
|
||||
(Some(fmt), Some(false)) => {
|
||||
warn_user!("The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format={fmt}`.");
|
||||
fmt
|
||||
}
|
||||
(None, Some(true)) => {
|
||||
warn_user!("The `--show-source` argument is deprecated. Use `--output-format=full` instead.");
|
||||
SerializationFormat::Full
|
||||
}
|
||||
(None, Some(false)) => {
|
||||
warn_user!("The `--no-show-source` argument is deprecated. Use `--output-format=concise` instead.");
|
||||
SerializationFormat::Concise
|
||||
}
|
||||
(None, None) => return None
|
||||
}).map(|format| match format {
|
||||
SerializationFormat::Text => {
|
||||
warn_user!("`--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `{}`.", SerializationFormat::default(preview));
|
||||
SerializationFormat::default(preview)
|
||||
},
|
||||
other => other
|
||||
})
|
||||
output_format: Option<OutputFormat>,
|
||||
) -> anyhow::Result<Option<OutputFormat>> {
|
||||
if let Some(OutputFormat::Text) = output_format {
|
||||
Err(anyhow!("`--output-format=text` is no longer supported. Use `--output-format=full` or `--output-format=concise` instead."))
|
||||
} else {
|
||||
Ok(output_format)
|
||||
}
|
||||
}
|
||||
|
||||
/// CLI settings that are distinct from configuration (commands, lists of files,
|
||||
@@ -1219,7 +1179,7 @@ struct ExplicitConfigOverrides {
|
||||
fix_only: Option<bool>,
|
||||
unsafe_fixes: Option<UnsafeFixes>,
|
||||
force_exclude: Option<bool>,
|
||||
output_format: Option<SerializationFormat>,
|
||||
output_format: Option<OutputFormat>,
|
||||
show_fixes: Option<bool>,
|
||||
extension: Option<Vec<ExtensionPair>>,
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use tempfile::NamedTempFile;
|
||||
|
||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||
use ruff_diagnostics::{DiagnosticKind, Fix};
|
||||
use ruff_linter::message::Message;
|
||||
use ruff_linter::message::{DiagnosticMessage, Message};
|
||||
use ruff_linter::{warn_user, VERSION};
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
@@ -333,12 +333,14 @@ impl FileCache {
|
||||
let file = SourceFileBuilder::new(path.to_string_lossy(), &*lint.source).finish();
|
||||
lint.messages
|
||||
.iter()
|
||||
.map(|msg| Message {
|
||||
kind: msg.kind.clone(),
|
||||
range: msg.range,
|
||||
fix: msg.fix.clone(),
|
||||
file: file.clone(),
|
||||
noqa_offset: msg.noqa_offset,
|
||||
.map(|msg| {
|
||||
Message::Diagnostic(DiagnosticMessage {
|
||||
kind: msg.kind.clone(),
|
||||
range: msg.range,
|
||||
fix: msg.fix.clone(),
|
||||
file: file.clone(),
|
||||
noqa_offset: msg.noqa_offset,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
@@ -412,18 +414,19 @@ impl LintCacheData {
|
||||
notebook_index: Option<NotebookIndex>,
|
||||
) -> Self {
|
||||
let source = if let Some(msg) = messages.first() {
|
||||
msg.file.source_text().to_owned()
|
||||
msg.source_file().source_text().to_owned()
|
||||
} else {
|
||||
String::new() // No messages, no need to keep the source!
|
||||
};
|
||||
|
||||
let messages = messages
|
||||
.iter()
|
||||
.filter_map(|message| message.as_diagnostic_message())
|
||||
.map(|msg| {
|
||||
// Make sure that all message use the same source file.
|
||||
assert_eq!(
|
||||
msg.file,
|
||||
messages.first().unwrap().file,
|
||||
&msg.file,
|
||||
messages.first().unwrap().source_file(),
|
||||
"message uses a different source file"
|
||||
);
|
||||
CacheMessage {
|
||||
@@ -571,6 +574,7 @@ mod tests {
|
||||
use test_case::test_case;
|
||||
|
||||
use ruff_cache::CACHE_DIR_NAME;
|
||||
use ruff_linter::message::Message;
|
||||
use ruff_linter::settings::flags;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_python_ast::PySourceType;
|
||||
@@ -633,11 +637,7 @@ mod tests {
|
||||
UnsafeFixes::Enabled,
|
||||
)
|
||||
.unwrap();
|
||||
if diagnostics
|
||||
.messages
|
||||
.iter()
|
||||
.any(|m| m.kind.name == "SyntaxError")
|
||||
{
|
||||
if diagnostics.messages.iter().any(Message::is_syntax_error) {
|
||||
parse_errors.push(path.clone());
|
||||
}
|
||||
paths.push(path);
|
||||
|
||||
@@ -36,7 +36,7 @@ impl<'a> Explanation<'a> {
|
||||
message_formats: rule.message_formats(),
|
||||
fix,
|
||||
explanation: rule.explanation(),
|
||||
preview: rule.is_preview() || rule.is_nursery(),
|
||||
preview: rule.is_preview(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ fn format_rule_text(rule: Rule) -> String {
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if rule.is_preview() || rule.is_nursery() {
|
||||
if rule.is_preview() {
|
||||
output.push_str(
|
||||
r"This rule is in preview and is not stable. The `--preview` flag is required for use.",
|
||||
);
|
||||
|
||||
@@ -9,19 +9,18 @@ use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use colored::Colorize;
|
||||
use log::{debug, error, warn};
|
||||
use log::{debug, warn};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_linter::codes::Rule;
|
||||
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource};
|
||||
use ruff_linter::logging::DisplayParseError;
|
||||
use ruff_linter::message::Message;
|
||||
use ruff_linter::message::{Message, SyntaxErrorMessage};
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::registry::AsRule;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::{fs, IOError, SyntaxError};
|
||||
use ruff_linter::{fs, IOError};
|
||||
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
|
||||
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
@@ -55,57 +54,61 @@ impl Diagnostics {
|
||||
path: Option<&Path>,
|
||||
settings: &LinterSettings,
|
||||
) -> Self {
|
||||
let diagnostic = match err {
|
||||
match err {
|
||||
// IO errors.
|
||||
SourceError::Io(_)
|
||||
| SourceError::Notebook(NotebookError::Io(_) | NotebookError::Json(_)) => {
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: err.to_string(),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
if settings.rules.enabled(Rule::IOError) {
|
||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||
let source_file = SourceFileBuilder::new(name, "").finish();
|
||||
Self::new(
|
||||
vec![Message::from_diagnostic(
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: err.to_string(),
|
||||
},
|
||||
TextRange::default(),
|
||||
),
|
||||
source_file,
|
||||
TextSize::default(),
|
||||
)],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
} else {
|
||||
match path {
|
||||
Some(path) => {
|
||||
warn!(
|
||||
"{}{}{} {err}",
|
||||
"Failed to lint ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
);
|
||||
}
|
||||
None => {
|
||||
warn!("{}{} {err}", "Failed to lint".bold(), ":".bold());
|
||||
}
|
||||
}
|
||||
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
// Syntax errors.
|
||||
SourceError::Notebook(
|
||||
NotebookError::InvalidJson(_)
|
||||
| NotebookError::InvalidSchema(_)
|
||||
| NotebookError::InvalidFormat(_),
|
||||
) => Diagnostic::new(
|
||||
SyntaxError {
|
||||
message: err.to_string(),
|
||||
},
|
||||
TextRange::default(),
|
||||
),
|
||||
};
|
||||
|
||||
if settings.rules.enabled(diagnostic.kind.rule()) {
|
||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||
let dummy = SourceFileBuilder::new(name, "").finish();
|
||||
Self::new(
|
||||
vec![Message::from_diagnostic(
|
||||
diagnostic,
|
||||
dummy,
|
||||
TextSize::default(),
|
||||
)],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
} else {
|
||||
match path {
|
||||
Some(path) => {
|
||||
warn!(
|
||||
"{}{}{} {err}",
|
||||
"Failed to lint ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
);
|
||||
}
|
||||
None => {
|
||||
warn!("{}{} {err}", "Failed to lint".bold(), ":".bold());
|
||||
}
|
||||
) => {
|
||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||
let dummy = SourceFileBuilder::new(name, "").finish();
|
||||
Self::new(
|
||||
vec![Message::SyntaxError(SyntaxErrorMessage {
|
||||
message: err.to_string(),
|
||||
range: TextRange::default(),
|
||||
file: dummy,
|
||||
})],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
}
|
||||
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,8 +264,8 @@ pub(crate) fn lint_path(
|
||||
// Lint the file.
|
||||
let (
|
||||
LinterResult {
|
||||
data: messages,
|
||||
error: parse_error,
|
||||
messages,
|
||||
has_syntax_error: has_error,
|
||||
},
|
||||
transformed,
|
||||
fixed,
|
||||
@@ -331,7 +334,7 @@ pub(crate) fn lint_path(
|
||||
|
||||
if let Some((cache, relative_path, key)) = caching {
|
||||
// We don't cache parsing errors.
|
||||
if parse_error.is_none() {
|
||||
if !has_error {
|
||||
// `FixMode::Apply` and `FixMode::Diff` rely on side-effects (writing to disk,
|
||||
// and writing the diff to stdout, respectively). If a file has diagnostics, we
|
||||
// need to avoid reading from and writing to the cache in these modes.
|
||||
@@ -353,13 +356,6 @@ pub(crate) fn lint_path(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(error) = parse_error {
|
||||
error!(
|
||||
"{}",
|
||||
DisplayParseError::from_source_kind(error, Some(path.to_path_buf()), &transformed)
|
||||
);
|
||||
}
|
||||
|
||||
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = transformed {
|
||||
FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook.into_index())])
|
||||
} else {
|
||||
@@ -404,52 +400,66 @@ pub(crate) fn lint_stdin(
|
||||
};
|
||||
|
||||
// Lint the inputs.
|
||||
let (
|
||||
LinterResult {
|
||||
data: messages,
|
||||
error: parse_error,
|
||||
},
|
||||
transformed,
|
||||
fixed,
|
||||
) = if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
|
||||
if let Ok(FixerResult {
|
||||
result,
|
||||
transformed,
|
||||
fixed,
|
||||
}) = lint_fix(
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
noqa,
|
||||
settings.unsafe_fixes,
|
||||
&settings.linter,
|
||||
&source_kind,
|
||||
source_type,
|
||||
) {
|
||||
match fix_mode {
|
||||
flags::FixMode::Apply => {
|
||||
// Write the contents to stdout, regardless of whether any errors were fixed.
|
||||
transformed.write(&mut io::stdout().lock())?;
|
||||
}
|
||||
flags::FixMode::Diff => {
|
||||
// But only write a diff if it's non-empty.
|
||||
if !fixed.is_empty() {
|
||||
write!(
|
||||
&mut io::stdout().lock(),
|
||||
"{}",
|
||||
source_kind.diff(&transformed, path).unwrap()
|
||||
)?;
|
||||
let (LinterResult { messages, .. }, transformed, fixed) =
|
||||
if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
|
||||
if let Ok(FixerResult {
|
||||
result,
|
||||
transformed,
|
||||
fixed,
|
||||
}) = lint_fix(
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
noqa,
|
||||
settings.unsafe_fixes,
|
||||
&settings.linter,
|
||||
&source_kind,
|
||||
source_type,
|
||||
) {
|
||||
match fix_mode {
|
||||
flags::FixMode::Apply => {
|
||||
// Write the contents to stdout, regardless of whether any errors were fixed.
|
||||
transformed.write(&mut io::stdout().lock())?;
|
||||
}
|
||||
flags::FixMode::Diff => {
|
||||
// But only write a diff if it's non-empty.
|
||||
if !fixed.is_empty() {
|
||||
write!(
|
||||
&mut io::stdout().lock(),
|
||||
"{}",
|
||||
source_kind.diff(&transformed, path).unwrap()
|
||||
)?;
|
||||
}
|
||||
}
|
||||
flags::FixMode::Generate => {}
|
||||
}
|
||||
flags::FixMode::Generate => {}
|
||||
}
|
||||
let transformed = if let Cow::Owned(transformed) = transformed {
|
||||
transformed
|
||||
let transformed = if let Cow::Owned(transformed) = transformed {
|
||||
transformed
|
||||
} else {
|
||||
source_kind
|
||||
};
|
||||
(result, transformed, fixed)
|
||||
} else {
|
||||
source_kind
|
||||
};
|
||||
(result, transformed, fixed)
|
||||
// If we fail to fix, lint the original source code.
|
||||
let result = lint_only(
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
&settings.linter,
|
||||
noqa,
|
||||
&source_kind,
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
|
||||
// Write the contents to stdout anyway.
|
||||
if fix_mode.is_apply() {
|
||||
source_kind.write(&mut io::stdout().lock())?;
|
||||
}
|
||||
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
(result, transformed, fixed)
|
||||
}
|
||||
} else {
|
||||
// If we fail to fix, lint the original source code.
|
||||
let result = lint_only(
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
@@ -459,37 +469,10 @@ pub(crate) fn lint_stdin(
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
|
||||
// Write the contents to stdout anyway.
|
||||
if fix_mode.is_apply() {
|
||||
source_kind.write(&mut io::stdout().lock())?;
|
||||
}
|
||||
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
(result, transformed, fixed)
|
||||
}
|
||||
} else {
|
||||
let result = lint_only(
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
&settings.linter,
|
||||
noqa,
|
||||
&source_kind,
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
(result, transformed, fixed)
|
||||
};
|
||||
|
||||
if let Some(error) = parse_error {
|
||||
error!(
|
||||
"{}",
|
||||
DisplayParseError::from_source_kind(error, path.map(Path::to_path_buf), &transformed)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = transformed {
|
||||
FxHashMap::from_iter([(
|
||||
|
||||
@@ -8,15 +8,15 @@ use std::process::ExitCode;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
use anyhow::Result;
|
||||
use args::{GlobalConfigArgs, ServerCommand};
|
||||
use clap::CommandFactory;
|
||||
use colored::Colorize;
|
||||
use log::warn;
|
||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||
|
||||
use args::{GlobalConfigArgs, ServerCommand};
|
||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||
use ruff_linter::settings::flags::FixMode;
|
||||
use ruff_linter::settings::types::SerializationFormat;
|
||||
use ruff_linter::settings::types::OutputFormat;
|
||||
use ruff_linter::{fs, warn_user, warn_user_once};
|
||||
use ruff_workspace::Settings;
|
||||
|
||||
@@ -121,7 +121,6 @@ pub fn run(
|
||||
command,
|
||||
global_options,
|
||||
}: Args,
|
||||
deprecated_alias_warning: Option<&'static str>,
|
||||
) -> Result<ExitStatus> {
|
||||
{
|
||||
let default_panic_hook = std::panic::take_hook();
|
||||
@@ -145,23 +144,8 @@ pub fn run(
|
||||
}));
|
||||
}
|
||||
|
||||
// Enabled ANSI colors on Windows 10.
|
||||
#[cfg(windows)]
|
||||
assert!(colored::control::set_virtual_terminal(true).is_ok());
|
||||
|
||||
// support FORCE_COLOR env var
|
||||
if let Some(force_color) = std::env::var_os("FORCE_COLOR") {
|
||||
if force_color.len() > 0 {
|
||||
colored::control::set_override(true);
|
||||
}
|
||||
}
|
||||
|
||||
set_up_logging(global_options.log_level())?;
|
||||
|
||||
if let Some(deprecated_alias_warning) = deprecated_alias_warning {
|
||||
warn_user!("{}", deprecated_alias_warning);
|
||||
}
|
||||
|
||||
match command {
|
||||
Command::Version { output_format } => {
|
||||
commands::version::version(output_format)?;
|
||||
@@ -351,10 +335,10 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
||||
let preview = pyproject_config.settings.linter.preview.is_enabled();
|
||||
|
||||
if cli.watch {
|
||||
if output_format != SerializationFormat::default(preview) {
|
||||
if output_format != OutputFormat::default() {
|
||||
warn_user!(
|
||||
"`--output-format {}` is always used in watch mode.",
|
||||
SerializationFormat::default(preview)
|
||||
OutputFormat::default()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ use std::process::ExitCode;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use colored::Colorize;
|
||||
use log::error;
|
||||
|
||||
use ruff::args::{Args, Command};
|
||||
use ruff::{run, ExitStatus};
|
||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[global_allocator]
|
||||
@@ -23,23 +25,33 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||
|
||||
pub fn main() -> ExitCode {
|
||||
// Enabled ANSI colors on Windows 10.
|
||||
#[cfg(windows)]
|
||||
assert!(colored::control::set_virtual_terminal(true).is_ok());
|
||||
|
||||
// support FORCE_COLOR env var
|
||||
if let Some(force_color) = std::env::var_os("FORCE_COLOR") {
|
||||
if force_color.len() > 0 {
|
||||
colored::control::set_override(true);
|
||||
}
|
||||
}
|
||||
|
||||
let args = wild::args_os();
|
||||
let mut args =
|
||||
argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX).unwrap();
|
||||
let args = argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX).unwrap();
|
||||
|
||||
// We can't use `warn_user` here because logging isn't set up at this point
|
||||
// and we also don't know if the user runs ruff with quiet.
|
||||
// Keep the message and pass it to `run` that is responsible for emitting the warning.
|
||||
let deprecated_alias_warning = match args.get(1).and_then(|arg| arg.to_str()) {
|
||||
let deprecated_alias_error = match args.get(1).and_then(|arg| arg.to_str()) {
|
||||
// Deprecated aliases that are handled by clap
|
||||
Some("--explain") => {
|
||||
Some("`ruff --explain <RULE>` is deprecated. Use `ruff rule <RULE>` instead.")
|
||||
Some("`ruff --explain <RULE>` has been removed. Use `ruff rule <RULE>` instead.")
|
||||
}
|
||||
Some("--clean") => {
|
||||
Some("`ruff --clean` is deprecated. Use `ruff clean` instead.")
|
||||
Some("`ruff --clean` has been removed. Use `ruff clean` instead.")
|
||||
}
|
||||
Some("--generate-shell-completion") => {
|
||||
Some("`ruff --generate-shell-completion <SHELL>` is deprecated. Use `ruff generate-shell-completion <SHELL>` instead.")
|
||||
Some("`ruff --generate-shell-completion <SHELL>` has been removed. Use `ruff generate-shell-completion <SHELL>` instead.")
|
||||
}
|
||||
// Deprecated `ruff` alias to `ruff check`
|
||||
// Clap doesn't support default subcommands but we want to run `check` by
|
||||
@@ -51,18 +63,26 @@ pub fn main() -> ExitCode {
|
||||
&& arg != "-V"
|
||||
&& arg != "--version"
|
||||
&& arg != "help" => {
|
||||
|
||||
{
|
||||
args.insert(1, "check".into());
|
||||
Some("`ruff <path>` is deprecated. Use `ruff check <path>` instead.")
|
||||
Some("`ruff <path>` has been removed. Use `ruff check <path>` instead.")
|
||||
}
|
||||
},
|
||||
_ => None
|
||||
};
|
||||
|
||||
if let Some(error) = deprecated_alias_error {
|
||||
#[allow(clippy::print_stderr)]
|
||||
if set_up_logging(LogLevel::Default).is_ok() {
|
||||
error!("{}", error);
|
||||
} else {
|
||||
eprintln!("{}", error.red().bold());
|
||||
}
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
let args = Args::parse_from(args);
|
||||
|
||||
match run(args, deprecated_alias_warning) {
|
||||
match run(args) {
|
||||
Ok(code) => code.into(),
|
||||
Err(err) => {
|
||||
#[allow(clippy::print_stderr)]
|
||||
|
||||
@@ -13,13 +13,13 @@ use ruff_linter::fs::relativize_path;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::message::{
|
||||
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
|
||||
JsonEmitter, JsonLinesEmitter, JunitEmitter, PylintEmitter, RdjsonEmitter, SarifEmitter,
|
||||
TextEmitter,
|
||||
JsonEmitter, JsonLinesEmitter, JunitEmitter, Message, MessageKind, PylintEmitter,
|
||||
RdjsonEmitter, SarifEmitter, TextEmitter,
|
||||
};
|
||||
use ruff_linter::notify_user;
|
||||
use ruff_linter::registry::{AsRule, Rule};
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::flags::{self};
|
||||
use ruff_linter::settings::types::{SerializationFormat, UnsafeFixes};
|
||||
use ruff_linter::settings::types::{OutputFormat, UnsafeFixes};
|
||||
|
||||
use crate::diagnostics::{Diagnostics, FixMap};
|
||||
|
||||
@@ -36,13 +36,14 @@ bitflags! {
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExpandedStatistics<'a> {
|
||||
code: SerializeRuleAsCode,
|
||||
message: &'a str,
|
||||
struct ExpandedStatistics {
|
||||
code: Option<SerializeRuleAsCode>,
|
||||
name: SerializeMessageKindAsTitle,
|
||||
count: usize,
|
||||
fixable: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct SerializeRuleAsCode(Rule);
|
||||
|
||||
impl Serialize for SerializeRuleAsCode {
|
||||
@@ -66,8 +67,31 @@ impl From<Rule> for SerializeRuleAsCode {
|
||||
}
|
||||
}
|
||||
|
||||
struct SerializeMessageKindAsTitle(MessageKind);
|
||||
|
||||
impl Serialize for SerializeMessageKindAsTitle {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.0.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SerializeMessageKindAsTitle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.0.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MessageKind> for SerializeMessageKindAsTitle {
|
||||
fn from(kind: MessageKind) -> Self {
|
||||
Self(kind)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Printer {
|
||||
format: SerializationFormat,
|
||||
format: OutputFormat,
|
||||
log_level: LogLevel,
|
||||
fix_mode: flags::FixMode,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
@@ -76,7 +100,7 @@ pub(crate) struct Printer {
|
||||
|
||||
impl Printer {
|
||||
pub(crate) const fn new(
|
||||
format: SerializationFormat,
|
||||
format: OutputFormat,
|
||||
log_level: LogLevel,
|
||||
fix_mode: flags::FixMode,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
@@ -217,12 +241,13 @@ impl Printer {
|
||||
}
|
||||
|
||||
if !self.flags.intersects(Flags::SHOW_VIOLATIONS) {
|
||||
#[allow(deprecated)]
|
||||
if matches!(
|
||||
self.format,
|
||||
SerializationFormat::Text
|
||||
| SerializationFormat::Full
|
||||
| SerializationFormat::Concise
|
||||
| SerializationFormat::Grouped
|
||||
OutputFormat::Text
|
||||
| OutputFormat::Full
|
||||
| OutputFormat::Concise
|
||||
| OutputFormat::Grouped
|
||||
) {
|
||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||
if !diagnostics.fixed.is_empty() {
|
||||
@@ -240,24 +265,23 @@ impl Printer {
|
||||
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
|
||||
|
||||
match self.format {
|
||||
SerializationFormat::Json => {
|
||||
OutputFormat::Json => {
|
||||
JsonEmitter.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::Rdjson => {
|
||||
OutputFormat::Rdjson => {
|
||||
RdjsonEmitter.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::JsonLines => {
|
||||
OutputFormat::JsonLines => {
|
||||
JsonLinesEmitter.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::Junit => {
|
||||
OutputFormat::Junit => {
|
||||
JunitEmitter.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::Concise
|
||||
| SerializationFormat::Full => {
|
||||
OutputFormat::Concise | OutputFormat::Full => {
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
|
||||
.with_show_source(self.format == SerializationFormat::Full)
|
||||
.with_show_source(self.format == OutputFormat::Full)
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.emit(writer, &diagnostics.messages, &context)?;
|
||||
|
||||
@@ -271,7 +295,7 @@ impl Printer {
|
||||
|
||||
self.write_summary_text(writer, diagnostics)?;
|
||||
}
|
||||
SerializationFormat::Grouped => {
|
||||
OutputFormat::Grouped => {
|
||||
GroupedEmitter::default()
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
@@ -286,22 +310,23 @@ impl Printer {
|
||||
}
|
||||
self.write_summary_text(writer, diagnostics)?;
|
||||
}
|
||||
SerializationFormat::Github => {
|
||||
OutputFormat::Github => {
|
||||
GithubEmitter.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::Gitlab => {
|
||||
OutputFormat::Gitlab => {
|
||||
GitlabEmitter::default().emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::Pylint => {
|
||||
OutputFormat::Pylint => {
|
||||
PylintEmitter.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::Azure => {
|
||||
OutputFormat::Azure => {
|
||||
AzureEmitter.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::Sarif => {
|
||||
OutputFormat::Sarif => {
|
||||
SarifEmitter.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::Text => unreachable!("Text is deprecated and should have been automatically converted to the default serialization format")
|
||||
#[allow(deprecated)]
|
||||
OutputFormat::Text => unreachable!("Text is deprecated and should have been automatically converted to the default serialization format")
|
||||
}
|
||||
|
||||
writer.flush()?;
|
||||
@@ -317,30 +342,23 @@ impl Printer {
|
||||
let statistics: Vec<ExpandedStatistics> = diagnostics
|
||||
.messages
|
||||
.iter()
|
||||
.map(|message| {
|
||||
(
|
||||
message.kind.rule(),
|
||||
&message.kind.body,
|
||||
message.fix.is_some(),
|
||||
)
|
||||
})
|
||||
.sorted()
|
||||
.fold(vec![], |mut acc, (rule, body, fixable)| {
|
||||
if let Some((prev_rule, _, _, count)) = acc.last_mut() {
|
||||
if *prev_rule == rule {
|
||||
.sorted_by_key(|message| (message.rule(), message.fixable()))
|
||||
.fold(vec![], |mut acc: Vec<(&Message, usize)>, message| {
|
||||
if let Some((prev_message, count)) = acc.last_mut() {
|
||||
if prev_message.rule() == message.rule() {
|
||||
*count += 1;
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
acc.push((rule, body, fixable, 1));
|
||||
acc.push((message, 1));
|
||||
acc
|
||||
})
|
||||
.iter()
|
||||
.map(|(rule, message, fixable, count)| ExpandedStatistics {
|
||||
code: (*rule).into(),
|
||||
count: *count,
|
||||
message,
|
||||
fixable: *fixable,
|
||||
.map(|&(message, count)| ExpandedStatistics {
|
||||
code: message.rule().map(std::convert::Into::into),
|
||||
name: message.kind().into(),
|
||||
count,
|
||||
fixable: message.fixable(),
|
||||
})
|
||||
.sorted_by_key(|statistic| Reverse(statistic.count))
|
||||
.collect();
|
||||
@@ -350,9 +368,8 @@ impl Printer {
|
||||
}
|
||||
|
||||
match self.format {
|
||||
SerializationFormat::Text
|
||||
| SerializationFormat::Full
|
||||
| SerializationFormat::Concise => {
|
||||
#[allow(deprecated)]
|
||||
OutputFormat::Text | OutputFormat::Full | OutputFormat::Concise => {
|
||||
// Compute the maximum number of digits in the count and code, for all messages,
|
||||
// to enable pretty-printing.
|
||||
let count_width = num_digits(
|
||||
@@ -364,7 +381,12 @@ impl Printer {
|
||||
);
|
||||
let code_width = statistics
|
||||
.iter()
|
||||
.map(|statistic| statistic.code.to_string().len())
|
||||
.map(|statistic| {
|
||||
statistic
|
||||
.code
|
||||
.map_or_else(String::new, |rule| rule.to_string())
|
||||
.len()
|
||||
})
|
||||
.max()
|
||||
.unwrap();
|
||||
let any_fixable = statistics.iter().any(|statistic| statistic.fixable);
|
||||
@@ -378,7 +400,11 @@ impl Printer {
|
||||
writer,
|
||||
"{:>count_width$}\t{:<code_width$}\t{}{}",
|
||||
statistic.count.to_string().bold(),
|
||||
statistic.code.to_string().red().bold(),
|
||||
statistic
|
||||
.code
|
||||
.map_or_else(String::new, |rule| rule.to_string())
|
||||
.red()
|
||||
.bold(),
|
||||
if any_fixable {
|
||||
if statistic.fixable {
|
||||
&fixable
|
||||
@@ -388,12 +414,12 @@ impl Printer {
|
||||
} else {
|
||||
""
|
||||
},
|
||||
statistic.message,
|
||||
statistic.name,
|
||||
)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
SerializationFormat::Json => {
|
||||
OutputFormat::Json => {
|
||||
writeln!(writer, "{}", serde_json::to_string_pretty(&statistics)?)?;
|
||||
}
|
||||
_ => {
|
||||
@@ -528,7 +554,7 @@ impl FixableStatistics {
|
||||
let mut unapplicable_unsafe = 0;
|
||||
|
||||
for message in &diagnostics.messages {
|
||||
if let Some(fix) = &message.fix {
|
||||
if let Some(fix) = message.fix() {
|
||||
if fix.applies(unsafe_fixes.required_applicability()) {
|
||||
applicable += 1;
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! A test suite that ensures deprecated command line options have appropriate warnings / behaviors
|
||||
|
||||
use ruff_linter::settings::types::SerializationFormat;
|
||||
use ruff_linter::settings::types::OutputFormat;
|
||||
use std::process::Command;
|
||||
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
@@ -9,142 +9,28 @@ const BIN_NAME: &str = "ruff";
|
||||
|
||||
const STDIN: &str = "l = 1";
|
||||
|
||||
fn ruff_check(show_source: Option<bool>, output_format: Option<String>) -> Command {
|
||||
fn ruff_check(output_format: OutputFormat) -> Command {
|
||||
let mut cmd = Command::new(get_cargo_bin(BIN_NAME));
|
||||
let output_format = output_format.unwrap_or(format!("{}", SerializationFormat::default(false)));
|
||||
let output_format = output_format.to_string();
|
||||
cmd.arg("check")
|
||||
.arg("--output-format")
|
||||
.arg(output_format)
|
||||
.arg("--no-cache");
|
||||
match show_source {
|
||||
Some(true) => {
|
||||
cmd.arg("--show-source");
|
||||
}
|
||||
Some(false) => {
|
||||
cmd.arg("--no-show-source");
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
cmd.arg("-");
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_show_source_is_deprecated() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(true), None).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_no_show_source_is_deprecated() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(false), None).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn ensure_output_format_is_deprecated() {
|
||||
assert_cmd_snapshot!(ruff_check(None, Some("text".into())).pass_stdin(STDIN), @r###"
|
||||
assert_cmd_snapshot!(ruff_check(OutputFormat::Text).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: `--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_output_format_overrides_show_source() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(true), Some("concise".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_full_output_format_overrides_no_show_source() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(false), Some("full".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
|
|
||||
1 | l = 1
|
||||
| ^ E741
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=full`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_output_format_uses_concise_over_no_show_source() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(false), Some("concise".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_deprecated_output_format_overrides_show_source() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(true), Some("text".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--show-source` argument is deprecated and has been ignored in favor of `--output-format=text`.
|
||||
warning: `--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_deprecated_output_format_overrides_no_show_source() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(false), Some("text".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=text`.
|
||||
warning: `--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `concise`.
|
||||
ruff failed
|
||||
Cause: `--output-format=text` is no longer supported. Use `--output-format=full` or `--output-format=concise` instead.
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -812,14 +812,13 @@ tab-size = 2
|
||||
if True:
|
||||
pass
|
||||
"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
if True:
|
||||
pass
|
||||
|
||||
----- stderr -----
|
||||
warning: The `tab-size` option has been renamed to `indent-width` to emphasize that it configures the indentation used by the formatter as well as the tab width. Please update your configuration to use `indent-width = <value>` instead.
|
||||
ruff failed
|
||||
Cause: The `tab-size` option has been renamed to `indent-width` to emphasize that it configures the indentation used by the formatter as well as the tab width. Please update `[RUFF-TOML-PATH]` to use `indent-width = <value>` instead.
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
|
||||
@@ -116,6 +116,12 @@ fn stdin_error() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
@@ -134,6 +140,12 @@ fn stdin_filename() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
F401.py:1:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
@@ -163,7 +175,19 @@ import bar # unused import
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
bar.py:2:8: F401 [*] `bar` imported but unused
|
||||
|
|
||||
2 | import bar # unused import
|
||||
| ^^^ F401
|
||||
|
|
||||
= help: Remove unused import: `bar`
|
||||
|
||||
foo.py:2:8: F401 [*] `foo` imported but unused
|
||||
|
|
||||
2 | import foo # unused import
|
||||
| ^^^ F401
|
||||
|
|
||||
= help: Remove unused import: `foo`
|
||||
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
@@ -185,6 +209,12 @@ fn check_warn_stdin_filename_with_files() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
F401.py:1:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
@@ -205,6 +235,12 @@ fn stdin_source_type_py() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
TCH.py:1:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
@@ -436,6 +472,11 @@ fn stdin_fix_jupyter() {
|
||||
}
|
||||
----- stderr -----
|
||||
Jupyter.ipynb:cell 3:1:7: F821 Undefined name `x`
|
||||
|
|
||||
1 | print(x)
|
||||
| ^ F821
|
||||
|
|
||||
|
||||
Found 3 errors (2 fixed, 1 remaining).
|
||||
"###);
|
||||
}
|
||||
@@ -529,7 +570,19 @@ fn stdin_override_parser_ipynb() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Jupyter.py:cell 1:1:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
Jupyter.py:cell 3:1:8: F401 [*] `sys` imported but unused
|
||||
|
|
||||
1 | import sys
|
||||
| ^^^ F401
|
||||
|
|
||||
= help: Remove unused import: `sys`
|
||||
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
@@ -553,6 +606,12 @@ fn stdin_override_parser_py() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
F401.ipynb:1:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
@@ -575,6 +634,14 @@ fn stdin_fix_when_not_fixable_should_still_print_contents() {
|
||||
|
||||
----- stderr -----
|
||||
-:3:4: F634 If test is a tuple, which is always `True`
|
||||
|
|
||||
1 | import sys
|
||||
2 |
|
||||
3 | if (1, 2):
|
||||
| ^^^^^^ F634
|
||||
4 | print(sys.version)
|
||||
|
|
||||
|
||||
Found 2 errors (1 fixed, 1 remaining).
|
||||
"###);
|
||||
}
|
||||
@@ -731,11 +798,15 @@ fn stdin_parse_error() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:16: E999 SyntaxError: Expected one or more symbol names after import
|
||||
-:1:16: SyntaxError: Expected one or more symbol names after import
|
||||
|
|
||||
1 | from foo import
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to parse at 1:16: Expected one or more symbol names after import
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -747,12 +818,65 @@ fn stdin_multiple_parse_error() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:16: E999 SyntaxError: Expected one or more symbol names after import
|
||||
-:2:6: E999 SyntaxError: Expected an expression
|
||||
-:1:16: SyntaxError: Expected one or more symbol names after import
|
||||
|
|
||||
1 | from foo import
|
||||
| ^
|
||||
2 | bar =
|
||||
|
|
||||
|
||||
-:2:6: SyntaxError: Expected an expression
|
||||
|
|
||||
1 | from foo import
|
||||
2 | bar =
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 2 errors.
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to parse at 1:16: Expected one or more symbol names after import
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_error_not_included() {
|
||||
// Select any rule except for `E999`, syntax error should still be shown.
|
||||
let mut cmd = RuffCheck::default().args(["--select=I"]).build();
|
||||
assert_cmd_snapshot!(cmd
|
||||
.pass_stdin("foo =\n"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:6: SyntaxError: Expected an expression
|
||||
|
|
||||
1 | foo =
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deprecated_parse_error_selection() {
|
||||
let mut cmd = RuffCheck::default().args(["--select=E999"]).build();
|
||||
assert_cmd_snapshot!(cmd
|
||||
.pass_stdin("foo =\n"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:6: SyntaxError: Expected an expression
|
||||
|
|
||||
1 | foo =
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: Rule `E999` is deprecated and will be removed in a future release. Syntax errors will always be shown regardless of whether this rule is selected or not.
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -854,117 +978,41 @@ fn show_statistics() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
1 F401 [*] `sys` imported but unused
|
||||
1 F401 [*] unused-import
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nursery_prefix() {
|
||||
// Should only detect RUF90X, but not the unstable test rules
|
||||
fn show_statistics_json() {
|
||||
let mut cmd = RuffCheck::default()
|
||||
.args(["--select", "RUF9", "--output-format=concise"])
|
||||
.args([
|
||||
"--select",
|
||||
"F401",
|
||||
"--statistics",
|
||||
"--output-format",
|
||||
"json",
|
||||
])
|
||||
.build();
|
||||
assert_cmd_snapshot!(cmd, @r###"
|
||||
assert_cmd_snapshot!(cmd
|
||||
.pass_stdin("import sys\nimport os\n\nprint(os.getuid())\n"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF900 Hey this is a stable test rule.
|
||||
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
|
||||
-:1:1: RUF920 Hey this is a deprecated test rule.
|
||||
-:1:1: RUF921 Hey this is another deprecated test rule.
|
||||
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
|
||||
Found 7 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
[
|
||||
{
|
||||
"code": "F401",
|
||||
"name": "unused-import",
|
||||
"count": 1,
|
||||
"fixable": true
|
||||
}
|
||||
]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nursery_all() {
|
||||
// Should detect RUF90X, but not the unstable test rules
|
||||
let mut cmd = RuffCheck::default()
|
||||
.args(["--select", "ALL", "--output-format=concise"])
|
||||
.build();
|
||||
assert_cmd_snapshot!(cmd, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: D100 Missing docstring in public module
|
||||
-:1:1: RUF900 Hey this is a stable test rule.
|
||||
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
|
||||
-:1:1: RUF920 Hey this is a deprecated test rule.
|
||||
-:1:1: RUF921 Hey this is another deprecated test rule.
|
||||
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
|
||||
Found 8 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
|
||||
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nursery_direct() {
|
||||
// Should fail when a nursery rule is selected without the preview flag
|
||||
// Before Ruff v0.2.0 this would warn
|
||||
let mut cmd = RuffCheck::default()
|
||||
.args(["--select", "RUF912", "--output-format=concise"])
|
||||
.build();
|
||||
assert_cmd_snapshot!(cmd, @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: Selection of unstable rule `RUF912` without the `--preview` flag is not allowed.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nursery_group_selector() {
|
||||
// The NURSERY selector is removed but parses in the CLI for a nicer error message
|
||||
// Before Ruff v0.2.0 this would warn
|
||||
let mut cmd = RuffCheck::default()
|
||||
.args(["--select", "NURSERY", "--output-format=concise"])
|
||||
.build();
|
||||
assert_cmd_snapshot!(cmd, @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: The `NURSERY` selector was removed. Use the `--preview` flag instead.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nursery_group_selector_preview_enabled() {
|
||||
// When preview mode is enabled, we shouldn't suggest using the `--preview` flag.
|
||||
// Before Ruff v0.2.0 this would warn
|
||||
let mut cmd = RuffCheck::default()
|
||||
.args(["--select", "NURSERY", "--preview"])
|
||||
.build();
|
||||
assert_cmd_snapshot!(cmd, @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: The `NURSERY` selector was removed. Unstable rules should be selected individually or by their respective groups.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_enabled_prefix() {
|
||||
// All the RUF9XX test rules should be triggered
|
||||
@@ -980,9 +1028,8 @@ fn preview_enabled_prefix() {
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
|
||||
-:1:1: RUF911 Hey this is a preview test rule.
|
||||
-:1:1: RUF912 Hey this is a nursery test rule.
|
||||
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
|
||||
Found 7 errors.
|
||||
Found 6 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
@@ -1005,9 +1052,8 @@ fn preview_enabled_all() {
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
|
||||
-:1:1: RUF911 Hey this is a preview test rule.
|
||||
-:1:1: RUF912 Hey this is a nursery test rule.
|
||||
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
|
||||
Found 9 errors.
|
||||
Found 8 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
@@ -1145,9 +1191,8 @@ fn preview_enabled_group_ignore() {
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
|
||||
-:1:1: RUF911 Hey this is a preview test rule.
|
||||
-:1:1: RUF912 Hey this is a nursery test rule.
|
||||
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
|
||||
Found 7 errors.
|
||||
Found 6 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
@@ -1214,6 +1259,9 @@ fn redirect_direct() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
|
||||
|
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
@@ -1246,6 +1294,9 @@ fn redirect_prefix() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
|
||||
|
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
@@ -1263,6 +1314,9 @@ fn deprecated_direct() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF920 Hey this is a deprecated test rule.
|
||||
|
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
@@ -1280,7 +1334,13 @@ fn deprecated_multiple_direct() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF920 Hey this is a deprecated test rule.
|
||||
|
|
||||
|
|
||||
|
||||
-:1:1: RUF921 Hey this is another deprecated test rule.
|
||||
|
|
||||
|
|
||||
|
||||
Found 2 errors.
|
||||
|
||||
----- stderr -----
|
||||
@@ -1299,7 +1359,13 @@ fn deprecated_indirect() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF920 Hey this is a deprecated test rule.
|
||||
|
|
||||
|
|
||||
|
||||
-:1:1: RUF921 Hey this is another deprecated test rule.
|
||||
|
|
||||
|
|
||||
|
||||
Found 2 errors.
|
||||
|
||||
----- stderr -----
|
||||
@@ -1377,7 +1443,8 @@ fn unreadable_pyproject_toml() -> Result<()> {
|
||||
|
||||
// Don't `--isolated` since the configuration discovery is where the error happens
|
||||
let args = Args::parse_from(["", "check", "--no-cache", tempdir.path().to_str().unwrap()]);
|
||||
let err = run(args, None).err().context("Unexpected success")?;
|
||||
let err = run(args).err().context("Unexpected success")?;
|
||||
|
||||
assert_eq!(
|
||||
err.chain()
|
||||
.map(std::string::ToString::to_string)
|
||||
@@ -1450,6 +1517,12 @@ fn check_input_from_argfile() -> Result<()> {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
/path/to/a.py:1:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
@@ -1471,7 +1544,13 @@ fn check_hints_hidden_unsafe_fixes() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
|
|
||||
|
|
||||
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
|
|
||||
|
||||
Found 2 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
@@ -1489,6 +1568,11 @@ fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
1 | x = {'a': 1, 'a': 1}
|
||||
| RUF902
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
@@ -1507,7 +1591,13 @@ fn check_no_hint_for_hidden_unsafe_fixes_when_disabled() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
|
|
||||
|
|
||||
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
|
|
||||
|
||||
Found 2 errors.
|
||||
[*] 1 fixable with the --fix option.
|
||||
|
||||
@@ -1527,6 +1617,11 @@ fn check_no_hint_for_hidden_unsafe_fixes_with_no_safe_fixes_when_disabled() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
1 | x = {'a': 1, 'a': 1}
|
||||
| RUF902
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
@@ -1544,7 +1639,13 @@ fn check_shows_unsafe_fixes_with_opt_in() {
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
|
|
||||
|
|
||||
|
||||
-:1:1: RUF902 [*] Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
|
|
||||
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the --fix option.
|
||||
|
||||
@@ -1566,6 +1667,11 @@ fn fix_applies_safe_fixes_by_default() {
|
||||
|
||||
----- stderr -----
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
1 | # fix from stable-test-rule-safe-fix
|
||||
| RUF902
|
||||
|
|
||||
|
||||
Found 2 errors (1 fixed, 1 remaining).
|
||||
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
"###);
|
||||
@@ -1603,6 +1709,11 @@ fn fix_does_not_apply_display_only_fixes() {
|
||||
def add_to_list(item, some_list=[]): ...
|
||||
----- stderr -----
|
||||
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
|
||||
|
|
||||
1 | def add_to_list(item, some_list=[]): ...
|
||||
| RUF903
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
"###);
|
||||
}
|
||||
@@ -1621,6 +1732,11 @@ fn fix_does_not_apply_display_only_fixes_with_unsafe_fixes_enabled() {
|
||||
def add_to_list(item, some_list=[]): ...
|
||||
----- stderr -----
|
||||
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
|
||||
|
|
||||
1 | def add_to_list(item, some_list=[]): ...
|
||||
| RUF903
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
"###);
|
||||
}
|
||||
@@ -1638,6 +1754,9 @@ fn fix_only_unsafe_fixes_available() {
|
||||
|
||||
----- stderr -----
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
"###);
|
||||
@@ -1774,7 +1893,13 @@ extend-unsafe-fixes = ["RUF901"]
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF901 Hey this is a stable test rule with a safe fix.
|
||||
|
|
||||
|
|
||||
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
|
|
||||
|
||||
Found 2 errors.
|
||||
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
@@ -1806,7 +1931,13 @@ extend-safe-fixes = ["RUF902"]
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
|
|
||||
|
|
||||
|
||||
-:1:1: RUF902 [*] Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
|
|
||||
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
@@ -1840,7 +1971,13 @@ extend-safe-fixes = ["RUF902"]
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
|
|
||||
|
|
||||
|
||||
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
|
|
||||
|
||||
Found 2 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
@@ -1876,12 +2013,61 @@ extend-safe-fixes = ["RUF9"]
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: RUF900 Hey this is a stable test rule.
|
||||
|
|
||||
1 | x = {'a': 1, 'a': 1}
|
||||
| RUF900
|
||||
2 | print(('foo'))
|
||||
3 | print(str('foo'))
|
||||
|
|
||||
|
||||
-:1:1: RUF901 Hey this is a stable test rule with a safe fix.
|
||||
|
|
||||
1 | x = {'a': 1, 'a': 1}
|
||||
| RUF901
|
||||
2 | print(('foo'))
|
||||
3 | print(str('foo'))
|
||||
|
|
||||
|
||||
-:1:1: RUF902 [*] Hey this is a stable test rule with an unsafe fix.
|
||||
|
|
||||
1 | x = {'a': 1, 'a': 1}
|
||||
| RUF902
|
||||
2 | print(('foo'))
|
||||
3 | print(str('foo'))
|
||||
|
|
||||
|
||||
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
|
||||
|
|
||||
1 | x = {'a': 1, 'a': 1}
|
||||
| RUF903
|
||||
2 | print(('foo'))
|
||||
3 | print(str('foo'))
|
||||
|
|
||||
|
||||
-:1:1: RUF920 Hey this is a deprecated test rule.
|
||||
|
|
||||
1 | x = {'a': 1, 'a': 1}
|
||||
| RUF920
|
||||
2 | print(('foo'))
|
||||
3 | print(str('foo'))
|
||||
|
|
||||
|
||||
-:1:1: RUF921 Hey this is another deprecated test rule.
|
||||
|
|
||||
1 | x = {'a': 1, 'a': 1}
|
||||
| RUF921
|
||||
2 | print(('foo'))
|
||||
3 | print(str('foo'))
|
||||
|
|
||||
|
||||
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
|
||||
|
|
||||
1 | x = {'a': 1, 'a': 1}
|
||||
| RUF950
|
||||
2 | print(('foo'))
|
||||
3 | print(str('foo'))
|
||||
|
|
||||
|
||||
Found 7 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
@@ -1943,6 +2129,12 @@ def log(x, base) -> float:
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:2:5: D417 Missing argument description in the docstring for `log`: `base`
|
||||
|
|
||||
2 | def log(x, base) -> float:
|
||||
| ^^^ D417
|
||||
3 | """Calculate natural log of a value
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
@@ -1973,6 +2165,14 @@ select = ["RUF017"]
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:3:1: RUF017 Avoid quadratic list summation
|
||||
|
|
||||
1 | x = [1, 2, 3]
|
||||
2 | y = [4, 5, 6]
|
||||
3 | sum([x, y], [])
|
||||
| ^^^^^^^^^^^^^^^ RUF017
|
||||
|
|
||||
= help: Replace with `functools.reduce`
|
||||
|
||||
Found 1 error.
|
||||
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
@@ -2005,6 +2205,14 @@ unfixable = ["RUF"]
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:3:1: RUF017 Avoid quadratic list summation
|
||||
|
|
||||
1 | x = [1, 2, 3]
|
||||
2 | y = [4, 5, 6]
|
||||
3 | sum([x, y], [])
|
||||
| ^^^^^^^^^^^^^^^ RUF017
|
||||
|
|
||||
= help: Replace with `functools.reduce`
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -17,7 +17,7 @@ Settings path: "[BASEPATH]/pyproject.toml"
|
||||
cache_dir = "[BASEPATH]/.ruff_cache"
|
||||
fix = false
|
||||
fix_only = false
|
||||
output_format = concise
|
||||
output_format = full
|
||||
show_fixes = false
|
||||
unsafe_fixes = hint
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ fn benchmark_linter(mut group: BenchmarkGroup, settings: &LinterSettings) {
|
||||
);
|
||||
|
||||
// Assert that file contains no parse errors
|
||||
assert_eq!(result.error, None);
|
||||
assert!(!result.has_syntax_error);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{self, Debug};
|
||||
@@ -30,7 +31,7 @@ impl VendoredFileSystem {
|
||||
}
|
||||
|
||||
pub fn exists(&self, path: &VendoredPath) -> bool {
|
||||
let normalized = normalize_vendored_path(path);
|
||||
let normalized = NormalizedVendoredPath::from(path);
|
||||
let inner_locked = self.inner.lock();
|
||||
let mut archive = inner_locked.borrow_mut();
|
||||
|
||||
@@ -45,7 +46,7 @@ impl VendoredFileSystem {
|
||||
}
|
||||
|
||||
pub fn metadata(&self, path: &VendoredPath) -> Option<Metadata> {
|
||||
let normalized = normalize_vendored_path(path);
|
||||
let normalized = NormalizedVendoredPath::from(path);
|
||||
let inner_locked = self.inner.lock();
|
||||
|
||||
// Must probe the zipfile twice, as "stdlib" and "stdlib/" are considered
|
||||
@@ -72,7 +73,7 @@ impl VendoredFileSystem {
|
||||
pub fn read(&self, path: &VendoredPath) -> Result<String> {
|
||||
let inner_locked = self.inner.lock();
|
||||
let mut archive = inner_locked.borrow_mut();
|
||||
let mut zip_file = archive.lookup_path(&normalize_vendored_path(path))?;
|
||||
let mut zip_file = archive.lookup_path(&NormalizedVendoredPath::from(path))?;
|
||||
let mut buffer = String::new();
|
||||
zip_file.read_to_string(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
@@ -240,45 +241,76 @@ impl VendoredZipArchive {
|
||||
/// but trailing slashes are crucial for distinguishing between
|
||||
/// files and directories inside zip archives.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct NormalizedVendoredPath(String);
|
||||
struct NormalizedVendoredPath<'a>(Cow<'a, str>);
|
||||
|
||||
impl NormalizedVendoredPath {
|
||||
fn with_trailing_slash(mut self) -> Self {
|
||||
impl<'a> NormalizedVendoredPath<'a> {
|
||||
fn with_trailing_slash(self) -> Self {
|
||||
debug_assert!(!self.0.ends_with('/'));
|
||||
self.0.push('/');
|
||||
self
|
||||
let mut data = self.0.into_owned();
|
||||
data.push('/');
|
||||
Self(Cow::Owned(data))
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes the path by removing `.` and `..` components.
|
||||
///
|
||||
/// ## Panics:
|
||||
/// If a path with an unsupported component for vendored paths is passed.
|
||||
/// Unsupported components are path prefixes,
|
||||
/// and path root directories appearing anywhere except at the start of the path.
|
||||
fn normalize_vendored_path(path: &VendoredPath) -> NormalizedVendoredPath {
|
||||
// Allow the `RootDir` component, but only if it is at the very start of the string.
|
||||
let mut components = path.components().peekable();
|
||||
if let Some(camino::Utf8Component::RootDir) = components.peek() {
|
||||
components.next();
|
||||
}
|
||||
|
||||
let mut normalized_parts = Vec::new();
|
||||
for component in components {
|
||||
match component {
|
||||
camino::Utf8Component::Normal(part) => normalized_parts.push(part),
|
||||
camino::Utf8Component::CurDir => continue,
|
||||
camino::Utf8Component::ParentDir => {
|
||||
normalized_parts.pop();
|
||||
impl<'a> From<&'a VendoredPath> for NormalizedVendoredPath<'a> {
|
||||
/// Normalize the path.
|
||||
///
|
||||
/// The normalizations are:
|
||||
/// - Remove `.` and `..` components
|
||||
/// - Strip trailing slashes
|
||||
/// - Normalize `\\` separators to `/`
|
||||
/// - Validate that the path does not have any unsupported components
|
||||
///
|
||||
/// ## Panics:
|
||||
/// If a path with an unsupported component for vendored paths is passed.
|
||||
/// Unsupported components are path prefixes and path root directories.
|
||||
fn from(path: &'a VendoredPath) -> Self {
|
||||
/// Remove `.` and `..` components, and validate that unsupported components are not present.
|
||||
///
|
||||
/// This inner routine also strips trailing slashes,
|
||||
/// and normalizes paths to use Unix `/` separators.
|
||||
/// However, it always allocates, so avoid calling it if possible.
|
||||
/// In most cases, the path should already be normalized.
|
||||
fn normalize_unnormalized_path(path: &VendoredPath) -> String {
|
||||
let mut normalized_parts = Vec::new();
|
||||
for component in path.components() {
|
||||
match component {
|
||||
camino::Utf8Component::Normal(part) => normalized_parts.push(part),
|
||||
camino::Utf8Component::CurDir => continue,
|
||||
camino::Utf8Component::ParentDir => {
|
||||
// `VendoredPath("")`, `VendoredPath("..")` and `VendoredPath("../..")`
|
||||
// all resolve to the same path relative to the zip archive
|
||||
// (see https://github.com/astral-sh/ruff/pull/11991#issuecomment-2185278014)
|
||||
normalized_parts.pop();
|
||||
}
|
||||
unsupported => {
|
||||
panic!("Unsupported component in a vendored path: {unsupported}")
|
||||
}
|
||||
}
|
||||
}
|
||||
unsupported => panic!("Unsupported component in a vendored path: {unsupported}"),
|
||||
normalized_parts.join("/")
|
||||
}
|
||||
|
||||
let path_str = path.as_str();
|
||||
|
||||
if std::path::MAIN_SEPARATOR == '\\' && path_str.contains('\\') {
|
||||
// Normalize paths so that they always use Unix path separators
|
||||
NormalizedVendoredPath(Cow::Owned(normalize_unnormalized_path(path)))
|
||||
} else if !path
|
||||
.components()
|
||||
.all(|component| matches!(component, camino::Utf8Component::Normal(_)))
|
||||
{
|
||||
// Remove non-`Normal` components
|
||||
NormalizedVendoredPath(Cow::Owned(normalize_unnormalized_path(path)))
|
||||
} else {
|
||||
// Strip trailing slashes from the path
|
||||
NormalizedVendoredPath(Cow::Borrowed(path_str.trim_end_matches('/')))
|
||||
}
|
||||
}
|
||||
NormalizedVendoredPath(normalized_parts.join("/"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -64,7 +64,7 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if rule.is_preview() || rule.is_nursery() {
|
||||
if rule.is_preview() {
|
||||
output.push_str(
|
||||
r"This rule is unstable and in [preview](../preview.md). The `--preview` flag is required for use.",
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||
format!("<span title='Rule has been deprecated'>{WARNING_SYMBOL}</span>")
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
RuleGroup::Preview | RuleGroup::Nursery => {
|
||||
RuleGroup::Preview => {
|
||||
format!("<span title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
|
||||
}
|
||||
RuleGroup::Stable => {
|
||||
@@ -165,9 +165,9 @@ pub(crate) fn generate() -> String {
|
||||
table_out.push('\n');
|
||||
}
|
||||
|
||||
if Options::metadata().has(linter.name()) {
|
||||
if Options::metadata().has(&format!("lint.{}", linter.name())) {
|
||||
table_out.push_str(&format!(
|
||||
"For related settings, see [{}](settings.md#{}).",
|
||||
"For related settings, see [{}](settings.md#lint{}).",
|
||||
linter.name(),
|
||||
linter.name(),
|
||||
));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.4.10"
|
||||
version = "0.5.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import urllib.request
|
||||
import requests
|
||||
import httpx
|
||||
import trio
|
||||
|
||||
|
||||
async def foo():
|
||||
urllib.request.urlopen("http://example.com/foo/bar").read()
|
||||
async def func():
|
||||
with trio.fail_after():
|
||||
...
|
||||
|
||||
|
||||
async def foo():
|
||||
requests.get()
|
||||
async def func():
|
||||
with trio.fail_at():
|
||||
await ...
|
||||
|
||||
|
||||
async def foo():
|
||||
httpx.get()
|
||||
async def func():
|
||||
with trio.move_on_after():
|
||||
...
|
||||
|
||||
|
||||
async def foo():
|
||||
requests.post()
|
||||
async def func():
|
||||
with trio.move_at():
|
||||
await ...
|
||||
|
||||
|
||||
async def foo():
|
||||
httpx.post()
|
||||
async def func():
|
||||
with trio.move_at():
|
||||
async with trio.open_nursery() as nursery:
|
||||
...
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import os
|
||||
|
||||
|
||||
async def foo():
|
||||
os.popen()
|
||||
|
||||
|
||||
async def foo():
|
||||
os.spawnl()
|
||||
|
||||
|
||||
async def foo():
|
||||
os.fspath("foo")
|
||||
@@ -26,7 +26,7 @@ async def func() -> None:
|
||||
await trio.lowlevel.wait_task_rescheduled(foo)
|
||||
await trio.lowlevel.wait_writable(foo)
|
||||
|
||||
# TRIO105
|
||||
# ASYNC105
|
||||
trio.aclose_forcefully(foo)
|
||||
trio.open_file(foo)
|
||||
trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
@@ -55,10 +55,10 @@ async def func() -> None:
|
||||
async with await trio.open_file(foo): # Ok
|
||||
pass
|
||||
|
||||
async with trio.open_file(foo): # TRIO105
|
||||
async with trio.open_file(foo): # ASYNC105
|
||||
pass
|
||||
|
||||
|
||||
def func() -> None:
|
||||
# TRIO105 (without fix)
|
||||
# ASYNC105 (without fix)
|
||||
trio.open_file(foo)
|
||||
@@ -2,19 +2,19 @@ async def func():
|
||||
import trio
|
||||
from trio import sleep
|
||||
|
||||
await trio.sleep(0) # TRIO115
|
||||
await trio.sleep(0) # ASYNC115
|
||||
await trio.sleep(1) # OK
|
||||
await trio.sleep(0, 1) # OK
|
||||
await trio.sleep(...) # OK
|
||||
await trio.sleep() # OK
|
||||
|
||||
trio.sleep(0) # TRIO115
|
||||
trio.sleep(0) # ASYNC115
|
||||
foo = 0
|
||||
trio.sleep(foo) # OK
|
||||
trio.sleep(1) # OK
|
||||
time.sleep(0) # OK
|
||||
|
||||
sleep(0) # TRIO115
|
||||
sleep(0) # ASYNC115
|
||||
|
||||
bar = "bar"
|
||||
trio.sleep(bar)
|
||||
@@ -45,18 +45,18 @@ async def func():
|
||||
def func():
|
||||
import trio
|
||||
|
||||
trio.run(trio.sleep(0)) # TRIO115
|
||||
trio.run(trio.sleep(0)) # ASYNC115
|
||||
|
||||
|
||||
from trio import Event, sleep
|
||||
|
||||
|
||||
def func():
|
||||
sleep(0) # TRIO115
|
||||
sleep(0) # ASYNC115
|
||||
|
||||
|
||||
async def func():
|
||||
await sleep(seconds=0) # TRIO115
|
||||
await sleep(seconds=0) # ASYNC115
|
||||
|
||||
|
||||
def func():
|
||||
69
crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC210.py
vendored
Normal file
69
crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC210.py
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
import urllib
|
||||
import requests
|
||||
import httpx
|
||||
import urllib3
|
||||
|
||||
|
||||
async def foo():
|
||||
urllib.request.urlopen("http://example.com/foo/bar").read() # ASYNC210
|
||||
|
||||
|
||||
async def foo():
|
||||
requests.get() # ASYNC210
|
||||
|
||||
|
||||
async def foo():
|
||||
httpx.get() # ASYNC210
|
||||
|
||||
|
||||
async def foo():
|
||||
requests.post() # ASYNC210
|
||||
|
||||
|
||||
async def foo():
|
||||
httpx.post() # ASYNC210
|
||||
|
||||
|
||||
async def foo():
|
||||
requests.get() # ASYNC210
|
||||
requests.get(...) # ASYNC210
|
||||
requests.get # Ok
|
||||
print(requests.get()) # ASYNC210
|
||||
print(requests.get(requests.get())) # ASYNC210
|
||||
|
||||
requests.options() # ASYNC210
|
||||
requests.head() # ASYNC210
|
||||
requests.post() # ASYNC210
|
||||
requests.put() # ASYNC210
|
||||
requests.patch() # ASYNC210
|
||||
requests.delete() # ASYNC210
|
||||
requests.foo()
|
||||
|
||||
httpx.options("") # ASYNC210
|
||||
httpx.head("") # ASYNC210
|
||||
httpx.post("") # ASYNC210
|
||||
httpx.put("") # ASYNC210
|
||||
httpx.patch("") # ASYNC210
|
||||
httpx.delete("") # ASYNC210
|
||||
httpx.foo() # Ok
|
||||
|
||||
urllib3.request() # ASYNC210
|
||||
urllib3.request(...) # ASYNC210
|
||||
|
||||
urllib.request.urlopen("") # ASYNC210
|
||||
|
||||
r = {}
|
||||
r.get("not a sync http client") # Ok
|
||||
|
||||
|
||||
async def bar():
|
||||
|
||||
def request():
|
||||
pass
|
||||
|
||||
request() # Ok
|
||||
|
||||
def urlopen():
|
||||
pass
|
||||
|
||||
urlopen() # Ok
|
||||
98
crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC22x.py
vendored
Normal file
98
crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC22x.py
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
# Violation cases:
|
||||
|
||||
|
||||
async def func():
|
||||
subprocess.run("foo") # ASYNC221
|
||||
|
||||
|
||||
async def func():
|
||||
subprocess.call("foo") # ASYNC221
|
||||
|
||||
|
||||
async def func():
|
||||
subprocess.foo(0) # OK
|
||||
|
||||
|
||||
async def func():
|
||||
os.wait4(10) # ASYNC222
|
||||
|
||||
|
||||
async def func():
|
||||
os.wait(12) # ASYNC222
|
||||
|
||||
|
||||
async def foo():
|
||||
await async_fun(
|
||||
subprocess.getoutput() # ASYNC221
|
||||
)
|
||||
subprocess.Popen() # ASYNC220
|
||||
os.system() # ASYNC221
|
||||
|
||||
system()
|
||||
os.system.anything()
|
||||
os.anything()
|
||||
|
||||
subprocess.run() # ASYNC221
|
||||
subprocess.call() # ASYNC221
|
||||
subprocess.check_call() # ASYNC221
|
||||
subprocess.check_output() # ASYNC221
|
||||
subprocess.getoutput() # ASYNC221
|
||||
subprocess.getstatusoutput() # ASYNC221
|
||||
|
||||
await async_fun(
|
||||
subprocess.getoutput() # ASYNC221
|
||||
)
|
||||
|
||||
subprocess.anything()
|
||||
subprocess.foo()
|
||||
subprocess.bar.foo()
|
||||
subprocess()
|
||||
|
||||
os.posix_spawn() # ASYNC221
|
||||
os.posix_spawnp() # ASYNC221
|
||||
|
||||
os.spawn()
|
||||
os.spawn
|
||||
os.spawnllll()
|
||||
|
||||
os.spawnl() # ASYNC221
|
||||
os.spawnle() # ASYNC221
|
||||
os.spawnlp() # ASYNC221
|
||||
os.spawnlpe() # ASYNC221
|
||||
os.spawnv() # ASYNC221
|
||||
os.spawnve() # ASYNC221
|
||||
os.spawnvp() # ASYNC221
|
||||
os.spawnvpe() # ASYNC221
|
||||
|
||||
P_NOWAIT = os.P_NOWAIT
|
||||
|
||||
# if mode is given, and is not os.P_WAIT: ASYNC220
|
||||
os.spawnl(os.P_NOWAIT) # ASYNC220
|
||||
os.spawnl(P_NOWAIT) # ASYNC220
|
||||
os.spawnl(mode=os.P_NOWAIT) # ASYNC220
|
||||
os.spawnl(mode=P_NOWAIT) # ASYNC220
|
||||
|
||||
P_WAIT = os.P_WAIT
|
||||
|
||||
# if it is P_WAIT, ASYNC221
|
||||
os.spawnl(P_WAIT) # ASYNC221
|
||||
os.spawnl(mode=os.P_WAIT) # ASYNC221
|
||||
os.spawnl(mode=P_WAIT) # ASYNC221
|
||||
|
||||
# other weird cases: ASYNC220
|
||||
os.spawnl(0) # ASYNC220
|
||||
os.spawnl(1) # ASYNC220
|
||||
os.spawnl(foo()) # ASYNC220
|
||||
|
||||
# ASYNC222
|
||||
os.wait() # ASYNC222
|
||||
os.wait3() # ASYNC222
|
||||
os.wait4() # ASYNC222
|
||||
os.waitid() # ASYNC222
|
||||
os.waitpid() # ASYNC222
|
||||
|
||||
os.waitpi()
|
||||
os.waiti()
|
||||
@@ -1,53 +1,48 @@
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import io
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
async def foo():
|
||||
open("") # ASYNC230
|
||||
io.open_code("") # ASYNC230
|
||||
|
||||
with open(""): # ASYNC230
|
||||
...
|
||||
|
||||
with open("") as f: # ASYNC230
|
||||
...
|
||||
|
||||
with foo(), open(""): # ASYNC230
|
||||
...
|
||||
|
||||
async with open(""): # ASYNC230
|
||||
...
|
||||
|
||||
|
||||
def foo_sync():
|
||||
open("")
|
||||
|
||||
# Violation cases:
|
||||
|
||||
|
||||
async def func():
|
||||
open("foo")
|
||||
|
||||
|
||||
async def func():
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
async def func():
|
||||
subprocess.run("foo")
|
||||
|
||||
|
||||
async def func():
|
||||
subprocess.call("foo")
|
||||
|
||||
|
||||
async def func():
|
||||
subprocess.foo(0)
|
||||
|
||||
|
||||
async def func():
|
||||
os.wait4(10)
|
||||
|
||||
|
||||
async def func():
|
||||
os.wait(12)
|
||||
open("foo") # ASYNC230
|
||||
|
||||
|
||||
# Violation cases for pathlib:
|
||||
|
||||
|
||||
async def func():
|
||||
Path("foo").open() # ASYNC101
|
||||
Path("foo").open() # ASYNC230
|
||||
|
||||
|
||||
async def func():
|
||||
p = Path("foo")
|
||||
p.open() # ASYNC101
|
||||
p.open() # ASYNC230
|
||||
|
||||
|
||||
async def func():
|
||||
with Path("foo").open() as f: # ASYNC101
|
||||
with Path("foo").open() as f: # ASYNC230
|
||||
pass
|
||||
|
||||
|
||||
@@ -55,13 +50,13 @@ async def func() -> None:
|
||||
p = Path("foo")
|
||||
|
||||
async def bar():
|
||||
p.open() # ASYNC101
|
||||
p.open() # ASYNC230
|
||||
|
||||
|
||||
async def func() -> None:
|
||||
(p1, p2) = (Path("foo"), Path("bar"))
|
||||
|
||||
p1.open() # ASYNC101
|
||||
p1.open() # ASYNC230
|
||||
|
||||
|
||||
# Non-violation cases for pathlib:
|
||||
14
crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC251.py
vendored
Normal file
14
crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC251.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
|
||||
async def func():
|
||||
time.sleep(1) # ASYNC251
|
||||
|
||||
|
||||
def func():
|
||||
time.sleep(1) # OK
|
||||
|
||||
|
||||
async def func():
|
||||
asyncio.sleep(1) # OK
|
||||
@@ -510,7 +510,7 @@ image[:,]
|
||||
|
||||
image[:,:,]
|
||||
|
||||
lambda x, :
|
||||
lambda x, : x
|
||||
|
||||
# ==> unpack.py <==
|
||||
def function(
|
||||
|
||||
@@ -48,3 +48,7 @@ def f():
|
||||
|
||||
if isinstance(a, int) or isinstance(a, float):
|
||||
pass
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722460483
|
||||
if(isinstance(a, int)) or (isinstance(a, float)):
|
||||
pass
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import trio
|
||||
|
||||
|
||||
async def func():
|
||||
with trio.fail_after():
|
||||
...
|
||||
|
||||
|
||||
async def func():
|
||||
with trio.fail_at():
|
||||
await ...
|
||||
|
||||
|
||||
async def func():
|
||||
with trio.move_on_after():
|
||||
...
|
||||
|
||||
|
||||
async def func():
|
||||
with trio.move_at():
|
||||
await ...
|
||||
|
||||
|
||||
async def func():
|
||||
with trio.move_at():
|
||||
async with trio.open_nursery() as nursery:
|
||||
...
|
||||
@@ -185,3 +185,7 @@ f"{ham[lower +1 :, "columnname"]}"
|
||||
|
||||
#: E203:1:13
|
||||
f"{ham[lower + 1 :, "columnname"]}"
|
||||
|
||||
#: Okay: https://github.com/astral-sh/ruff/issues/12023
|
||||
f"{x = :.2f}"
|
||||
f"{(x) = :.2f}"
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
def x():
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
"""Checks use of consider-merging-isinstance"""
|
||||
# pylint:disable=line-too-long, simplifiable-condition
|
||||
|
||||
|
||||
def isinstances():
|
||||
"Examples of isinstances"
|
||||
var = range(10)
|
||||
|
||||
# merged
|
||||
if isinstance(var[1], (int, float)):
|
||||
pass
|
||||
result = isinstance(var[2], (int, float))
|
||||
|
||||
# not merged
|
||||
if isinstance(var[3], int) or isinstance(var[3], float) or isinstance(var[3], list) and True: # [consider-merging-isinstance]
|
||||
pass
|
||||
result = isinstance(var[4], int) or isinstance(var[4], float) or isinstance(var[5], list) and False # [consider-merging-isinstance]
|
||||
|
||||
result = isinstance(var[5], int) or True or isinstance(var[5], float) # [consider-merging-isinstance]
|
||||
|
||||
inferred_isinstance = isinstance
|
||||
result = inferred_isinstance(var[6], int) or inferred_isinstance(var[6], float) or inferred_isinstance(var[6], list) and False # [consider-merging-isinstance]
|
||||
result = isinstance(var[10], str) or isinstance(var[10], int) and var[8] * 14 or isinstance(var[10], float) and var[5] * 14.4 or isinstance(var[10], list) # [consider-merging-isinstance]
|
||||
result = isinstance(var[11], int) or isinstance(var[11], int) or isinstance(var[11], float) # [consider-merging-isinstance]
|
||||
|
||||
result = isinstance(var[20])
|
||||
result = isinstance()
|
||||
|
||||
# Combination merged and not merged
|
||||
result = isinstance(var[12], (int, float)) or isinstance(var[12], list) # [consider-merging-isinstance]
|
||||
|
||||
# not merged but valid
|
||||
result = isinstance(var[5], int) and var[5] * 14 or isinstance(var[5], float) and var[5] * 14.4
|
||||
result = isinstance(var[7], int) or not isinstance(var[7], float)
|
||||
result = isinstance(var[6], int) or isinstance(var[7], float)
|
||||
result = isinstance(var[6], int) or isinstance(var[7], int)
|
||||
result = isinstance(var[6], (float, int)) or False
|
||||
return result
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722460483
|
||||
if(isinstance(self.k, int)) or (isinstance(self.k, float)):
|
||||
...
|
||||
@@ -110,7 +110,7 @@ def func():
|
||||
|
||||
|
||||
def func():
|
||||
defaultdict(dict, defaultdict=list) # OK
|
||||
defaultdict(dict, default_factory=list) # OK
|
||||
|
||||
|
||||
def func():
|
||||
|
||||
@@ -38,3 +38,10 @@ def negative_cases():
|
||||
print(("{a}" "{c}").format(a=1, c=2))
|
||||
print("{a}".attribute.chaining.call(a=2))
|
||||
print("{a} {c}".format(a))
|
||||
|
||||
from gettext import gettext as foo
|
||||
should = 42
|
||||
x = foo("This {should} also be understood as a translation string")
|
||||
|
||||
import django.utils.translations
|
||||
y = django.utils.translations.gettext("This {should} be understood as a translation string too!")
|
||||
|
||||
@@ -15,8 +15,8 @@ use crate::rules::{
|
||||
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
|
||||
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
|
||||
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
|
||||
flake8_simplify, flake8_tidy_imports, flake8_trio, flake8_type_checking, flake8_use_pathlib,
|
||||
flynt, numpy, pandas_vet, pep8_naming, pycodestyle, pyflakes, pylint, pyupgrade, refurb, ruff,
|
||||
flake8_simplify, flake8_tidy_imports, flake8_type_checking, flake8_use_pathlib, flynt, numpy,
|
||||
pandas_vet, pep8_naming, pycodestyle, pyflakes, pylint, pyupgrade, refurb, ruff,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
@@ -505,11 +505,18 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::BlockingHttpCallInAsyncFunction) {
|
||||
flake8_async::rules::blocking_http_call(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::OpenSleepOrSubprocessInAsyncFunction) {
|
||||
flake8_async::rules::open_sleep_or_subprocess_call(checker, call);
|
||||
if checker.enabled(Rule::BlockingOpenCallInAsyncFunction) {
|
||||
flake8_async::rules::blocking_open_call(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::BlockingOsCallInAsyncFunction) {
|
||||
flake8_async::rules::blocking_os_call(checker, call);
|
||||
if checker.any_enabled(&[
|
||||
Rule::CreateSubprocessInAsyncFunction,
|
||||
Rule::RunProcessInAsyncFunction,
|
||||
Rule::WaitForProcessInAsyncFunction,
|
||||
]) {
|
||||
flake8_async::rules::blocking_process_invocation(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::BlockingSleepInAsyncFunction) {
|
||||
flake8_async::rules::blocking_sleep(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::SleepForeverCall) {
|
||||
flake8_async::rules::sleep_forever_call(checker, call);
|
||||
@@ -963,10 +970,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
refurb::rules::no_implicit_cwd(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::TrioSyncCall) {
|
||||
flake8_trio::rules::sync_call(checker, call);
|
||||
flake8_async::rules::sync_call(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::TrioZeroSleepCall) {
|
||||
flake8_trio::rules::zero_sleep_call(checker, call);
|
||||
flake8_async::rules::zero_sleep_call(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDunderCall) {
|
||||
pylint::rules::unnecessary_dunder_call(checker, call);
|
||||
@@ -1515,16 +1522,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
refurb::rules::reimplemented_starmap(checker, &generator.into());
|
||||
}
|
||||
}
|
||||
Expr::BoolOp(
|
||||
bool_op @ ast::ExprBoolOp {
|
||||
op,
|
||||
values,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::RepeatedIsinstanceCalls) {
|
||||
pylint::rules::repeated_isinstance_calls(checker, expr, *op, values);
|
||||
}
|
||||
Expr::BoolOp(bool_op) => {
|
||||
if checker.enabled(Rule::MultipleStartsEndsWith) {
|
||||
flake8_pie::rules::multiple_starts_ends_with(checker, expr);
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ use ruff_text_size::Ranged;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::{
|
||||
airflow, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_debugger,
|
||||
flake8_django, flake8_errmsg, flake8_import_conventions, flake8_pie, flake8_pyi,
|
||||
flake8_pytest_style, flake8_raise, flake8_return, flake8_simplify, flake8_slots,
|
||||
flake8_tidy_imports, flake8_trio, flake8_type_checking, mccabe, pandas_vet, pep8_naming,
|
||||
perflint, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops,
|
||||
airflow, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins,
|
||||
flake8_debugger, flake8_django, flake8_errmsg, flake8_import_conventions, flake8_pie,
|
||||
flake8_pyi, flake8_pytest_style, flake8_raise, flake8_return, flake8_simplify, flake8_slots,
|
||||
flake8_tidy_imports, flake8_type_checking, mccabe, pandas_vet, pep8_naming, perflint,
|
||||
pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
@@ -357,7 +357,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::TrioAsyncFunctionWithTimeout) {
|
||||
flake8_trio::rules::async_function_with_timeout(checker, function_def);
|
||||
flake8_async::rules::async_function_with_timeout(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::ReimplementedOperator) {
|
||||
refurb::rules::reimplemented_operator(checker, &function_def.into());
|
||||
@@ -1303,7 +1303,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pylint::rules::useless_with_lock(checker, with_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::TrioTimeoutWithoutAwait) {
|
||||
flake8_trio::rules::timeout_without_await(checker, with_stmt, items);
|
||||
flake8_async::rules::timeout_without_await(checker, with_stmt, items);
|
||||
}
|
||||
}
|
||||
Stmt::While(while_stmt @ ast::StmtWhile { body, orelse, .. }) => {
|
||||
@@ -1320,7 +1320,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
perflint::rules::try_except_in_loop(checker, body);
|
||||
}
|
||||
if checker.enabled(Rule::TrioUnneededSleep) {
|
||||
flake8_trio::rules::unneeded_sleep(checker, while_stmt);
|
||||
flake8_async::rules::unneeded_sleep(checker, while_stmt);
|
||||
}
|
||||
}
|
||||
Stmt::For(
|
||||
|
||||
@@ -462,8 +462,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
|| helpers::in_nested_block(self.semantic.current_statements())
|
||||
|| imports::is_matplotlib_activation(stmt, self.semantic())
|
||||
|| imports::is_sys_path_modification(stmt, self.semantic())
|
||||
|| (self.settings.preview.is_enabled()
|
||||
&& imports::is_os_environ_modification(stmt, self.semantic())))
|
||||
|| imports::is_os_environ_modification(stmt, self.semantic()))
|
||||
{
|
||||
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
|
||||
}
|
||||
|
||||
@@ -57,9 +57,6 @@ pub enum RuleGroup {
|
||||
Deprecated,
|
||||
/// The rule has been removed, errors will be displayed on use.
|
||||
Removed,
|
||||
/// Legacy category for unstable rules, supports backwards compatible selection.
|
||||
#[deprecated(note = "Use `RuleGroup::Preview` for new rules instead")]
|
||||
Nursery,
|
||||
}
|
||||
|
||||
#[ruff_macros::map_codes]
|
||||
@@ -71,72 +68,39 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
Some(match (linter, code) {
|
||||
// pycodestyle errors
|
||||
(Pycodestyle, "E101") => (RuleGroup::Stable, rules::pycodestyle::rules::MixedSpacesAndTabs),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E111") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultiple),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E112") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlock),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E113") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentation),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E114") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultipleComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E115") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E116") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentationComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E117") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::OverIndented),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E201") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E202") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E203") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E211") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E221") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E222") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E223") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E224") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E225") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E226") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundArithmeticOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E227") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E228") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E231") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E241") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E242") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterComma),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E251") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E252") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E261") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E262") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterInlineComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E265") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E266") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleLeadingHashesForBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E271") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E272") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E273") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E274") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E275") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword),
|
||||
(Pycodestyle, "E111") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultiple),
|
||||
(Pycodestyle, "E112") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::NoIndentedBlock),
|
||||
(Pycodestyle, "E113") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::UnexpectedIndentation),
|
||||
(Pycodestyle, "E114") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultipleComment),
|
||||
(Pycodestyle, "E115") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::NoIndentedBlockComment),
|
||||
(Pycodestyle, "E116") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::UnexpectedIndentationComment),
|
||||
(Pycodestyle, "E117") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::OverIndented),
|
||||
(Pycodestyle, "E201") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket),
|
||||
(Pycodestyle, "E202") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket),
|
||||
(Pycodestyle, "E203") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation),
|
||||
(Pycodestyle, "E211") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters),
|
||||
(Pycodestyle, "E221") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator),
|
||||
(Pycodestyle, "E222") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator),
|
||||
(Pycodestyle, "E223") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TabBeforeOperator),
|
||||
(Pycodestyle, "E224") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TabAfterOperator),
|
||||
(Pycodestyle, "E225") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundOperator),
|
||||
(Pycodestyle, "E226") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundArithmeticOperator),
|
||||
(Pycodestyle, "E227") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
|
||||
(Pycodestyle, "E228") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
|
||||
(Pycodestyle, "E231") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
|
||||
(Pycodestyle, "E241") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
|
||||
(Pycodestyle, "E242") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TabAfterComma),
|
||||
(Pycodestyle, "E251") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
|
||||
(Pycodestyle, "E252") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
|
||||
(Pycodestyle, "E261") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),
|
||||
(Pycodestyle, "E262") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::NoSpaceAfterInlineComment),
|
||||
(Pycodestyle, "E265") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::NoSpaceAfterBlockComment),
|
||||
(Pycodestyle, "E266") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleLeadingHashesForBlockComment),
|
||||
(Pycodestyle, "E271") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterKeyword),
|
||||
(Pycodestyle, "E272") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeKeyword),
|
||||
(Pycodestyle, "E273") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TabAfterKeyword),
|
||||
(Pycodestyle, "E274") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword),
|
||||
(Pycodestyle, "E275") => (RuleGroup::Preview, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword),
|
||||
(Pycodestyle, "E301") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLineBetweenMethods),
|
||||
(Pycodestyle, "E302") => (RuleGroup::Preview, rules::pycodestyle::rules::BlankLinesTopLevel),
|
||||
(Pycodestyle, "E303") => (RuleGroup::Preview, rules::pycodestyle::rules::TooManyBlankLines),
|
||||
@@ -161,7 +125,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pycodestyle, "E742") => (RuleGroup::Stable, rules::pycodestyle::rules::AmbiguousClassName),
|
||||
(Pycodestyle, "E743") => (RuleGroup::Stable, rules::pycodestyle::rules::AmbiguousFunctionName),
|
||||
(Pycodestyle, "E902") => (RuleGroup::Stable, rules::pycodestyle::rules::IOError),
|
||||
(Pycodestyle, "E999") => (RuleGroup::Stable, rules::pycodestyle::rules::SyntaxError),
|
||||
(Pycodestyle, "E999") => (RuleGroup::Deprecated, rules::pycodestyle::rules::SyntaxError),
|
||||
|
||||
// pycodestyle warnings
|
||||
(Pycodestyle, "W191") => (RuleGroup::Stable, rules::pycodestyle::rules::TabIndentation),
|
||||
@@ -226,16 +190,15 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet),
|
||||
(Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias),
|
||||
(Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C2401") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiName),
|
||||
(Pylint, "C2403") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiImportName),
|
||||
(Pylint, "C1901") => (RuleGroup::Preview, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C2401") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiName),
|
||||
(Pylint, "C2403") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiImportName),
|
||||
(Pylint, "C2701") => (RuleGroup::Preview, rules::pylint::rules::ImportPrivateName),
|
||||
(Pylint, "C2801") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDunderCall),
|
||||
(Pylint, "C3002") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryDirectLambdaCall),
|
||||
(Pylint, "E0100") => (RuleGroup::Stable, rules::pylint::rules::YieldInInit),
|
||||
(Pylint, "E0101") => (RuleGroup::Stable, rules::pylint::rules::ReturnInInit),
|
||||
(Pylint, "E0115") => (RuleGroup::Preview, rules::pylint::rules::NonlocalAndGlobal),
|
||||
(Pylint, "E0115") => (RuleGroup::Stable, rules::pylint::rules::NonlocalAndGlobal),
|
||||
(Pylint, "E0116") => (RuleGroup::Stable, rules::pylint::rules::ContinueInFinally),
|
||||
(Pylint, "E0117") => (RuleGroup::Stable, rules::pylint::rules::NonlocalWithoutBinding),
|
||||
(Pylint, "E0118") => (RuleGroup::Stable, rules::pylint::rules::LoadBeforeGlobalDeclaration),
|
||||
@@ -250,9 +213,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "E0309") => (RuleGroup::Preview, rules::pylint::rules::InvalidHashReturnType),
|
||||
(Pylint, "E0604") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllObject),
|
||||
(Pylint, "E0605") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllFormat),
|
||||
(Pylint, "E0643") => (RuleGroup::Preview, rules::pylint::rules::PotentialIndexError),
|
||||
(Pylint, "E0704") => (RuleGroup::Preview, rules::pylint::rules::MisplacedBareRaise),
|
||||
(Pylint, "E1132") => (RuleGroup::Preview, rules::pylint::rules::RepeatedKeywordArgument),
|
||||
(Pylint, "E0643") => (RuleGroup::Stable, rules::pylint::rules::PotentialIndexError),
|
||||
(Pylint, "E0704") => (RuleGroup::Stable, rules::pylint::rules::MisplacedBareRaise),
|
||||
(Pylint, "E1132") => (RuleGroup::Stable, rules::pylint::rules::RepeatedKeywordArgument),
|
||||
(Pylint, "E1141") => (RuleGroup::Preview, rules::pylint::rules::DictIterMissingItems),
|
||||
(Pylint, "E1142") => (RuleGroup::Stable, rules::pylint::rules::AwaitOutsideAsync),
|
||||
(Pylint, "E1205") => (RuleGroup::Stable, rules::pylint::rules::LoggingTooManyArgs),
|
||||
@@ -285,64 +248,62 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R0915") => (RuleGroup::Stable, rules::pylint::rules::TooManyStatements),
|
||||
(Pylint, "R0916") => (RuleGroup::Preview, rules::pylint::rules::TooManyBooleanExpressions),
|
||||
(Pylint, "R0917") => (RuleGroup::Preview, rules::pylint::rules::TooManyPositional),
|
||||
(Pylint, "R1701") => (RuleGroup::Stable, rules::pylint::rules::RepeatedIsinstanceCalls),
|
||||
(Pylint, "R1701") => (RuleGroup::Removed, rules::pylint::rules::RepeatedIsinstanceCalls),
|
||||
(Pylint, "R1702") => (RuleGroup::Preview, rules::pylint::rules::TooManyNestedBlocks),
|
||||
(Pylint, "R1704") => (RuleGroup::Preview, rules::pylint::rules::RedefinedArgumentFromLocal),
|
||||
(Pylint, "R1704") => (RuleGroup::Stable, rules::pylint::rules::RedefinedArgumentFromLocal),
|
||||
(Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary),
|
||||
(Pylint, "R1711") => (RuleGroup::Stable, rules::pylint::rules::UselessReturn),
|
||||
(Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison),
|
||||
(Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R1730") => (RuleGroup::Preview, rules::pylint::rules::IfStmtMinMax),
|
||||
(Pylint, "R1733") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDictIndexLookup),
|
||||
(Pylint, "R1736") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryListIndexLookup),
|
||||
(Pylint, "R1736") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryListIndexLookup),
|
||||
(Pylint, "R2004") => (RuleGroup::Stable, rules::pylint::rules::MagicValueComparison),
|
||||
(Pylint, "R2044") => (RuleGroup::Preview, rules::pylint::rules::EmptyComment),
|
||||
(Pylint, "R2044") => (RuleGroup::Stable, rules::pylint::rules::EmptyComment),
|
||||
(Pylint, "R5501") => (RuleGroup::Stable, rules::pylint::rules::CollapsibleElseIf),
|
||||
(Pylint, "R6104") => (RuleGroup::Preview, rules::pylint::rules::NonAugmentedAssignment),
|
||||
(Pylint, "R6201") => (RuleGroup::Preview, rules::pylint::rules::LiteralMembership),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
|
||||
(Pylint, "R6301") => (RuleGroup::Preview, rules::pylint::rules::NoSelfUse),
|
||||
(Pylint, "W0108") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryLambda),
|
||||
(Pylint, "W0177") => (RuleGroup::Preview, rules::pylint::rules::NanComparison),
|
||||
(Pylint, "W0120") => (RuleGroup::Stable, rules::pylint::rules::UselessElseOnLoop),
|
||||
(Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable),
|
||||
(Pylint, "W0128") => (RuleGroup::Preview, rules::pylint::rules::RedeclaredAssignedName),
|
||||
(Pylint, "W0128") => (RuleGroup::Stable, rules::pylint::rules::RedeclaredAssignedName),
|
||||
(Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral),
|
||||
(Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext),
|
||||
(Pylint, "W0133") => (RuleGroup::Preview, rules::pylint::rules::UselessExceptionStatement),
|
||||
(Pylint, "W0133") => (RuleGroup::Stable, rules::pylint::rules::UselessExceptionStatement),
|
||||
(Pylint, "W0211") => (RuleGroup::Preview, rules::pylint::rules::BadStaticmethodArgument),
|
||||
(Pylint, "W0245") => (RuleGroup::Preview, rules::pylint::rules::SuperWithoutBrackets),
|
||||
(Pylint, "W0245") => (RuleGroup::Stable, rules::pylint::rules::SuperWithoutBrackets),
|
||||
(Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf),
|
||||
(Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
|
||||
(Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement),
|
||||
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),
|
||||
(Pylint, "W0604") => (RuleGroup::Stable, rules::pylint::rules::GlobalAtModuleLevel),
|
||||
(Pylint, "W0642") => (RuleGroup::Preview, rules::pylint::rules::SelfOrClsAssignment),
|
||||
(Pylint, "W0711") => (RuleGroup::Stable, rules::pylint::rules::BinaryOpException),
|
||||
(Pylint, "W1501") => (RuleGroup::Preview, rules::pylint::rules::BadOpenMode),
|
||||
(Pylint, "W1501") => (RuleGroup::Stable, rules::pylint::rules::BadOpenMode),
|
||||
(Pylint, "W1508") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarDefault),
|
||||
(Pylint, "W1509") => (RuleGroup::Stable, rules::pylint::rules::SubprocessPopenPreexecFn),
|
||||
(Pylint, "W1510") => (RuleGroup::Stable, rules::pylint::rules::SubprocessRunWithoutCheck),
|
||||
(Pylint, "W1514") => (RuleGroup::Preview, rules::pylint::rules::UnspecifiedEncoding),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "W2101") => (RuleGroup::Preview, rules::pylint::rules::UselessWithLock),
|
||||
(Pylint, "W1641") => (RuleGroup::Preview, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "W2101") => (RuleGroup::Stable, rules::pylint::rules::UselessWithLock),
|
||||
(Pylint, "W2901") => (RuleGroup::Stable, rules::pylint::rules::RedefinedLoopName),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
|
||||
(Pylint, "W3201") => (RuleGroup::Preview, rules::pylint::rules::BadDunderMethodName),
|
||||
(Pylint, "W3301") => (RuleGroup::Stable, rules::pylint::rules::NestedMinMax),
|
||||
|
||||
// flake8-async
|
||||
(Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction),
|
||||
(Flake8Async, "101") => (RuleGroup::Stable, rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction),
|
||||
(Flake8Async, "102") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOsCallInAsyncFunction),
|
||||
(Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_async::rules::TrioTimeoutWithoutAwait),
|
||||
(Flake8Async, "105") => (RuleGroup::Stable, rules::flake8_async::rules::TrioSyncCall),
|
||||
(Flake8Async, "109") => (RuleGroup::Stable, rules::flake8_async::rules::TrioAsyncFunctionWithTimeout),
|
||||
(Flake8Async, "110") => (RuleGroup::Stable, rules::flake8_async::rules::TrioUnneededSleep),
|
||||
(Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::TrioZeroSleepCall),
|
||||
(Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::SleepForeverCall),
|
||||
|
||||
// flake8-trio
|
||||
(Flake8Trio, "100") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
|
||||
(Flake8Trio, "105") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioSyncCall),
|
||||
(Flake8Trio, "109") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioAsyncFunctionWithTimeout),
|
||||
(Flake8Trio, "110") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioUnneededSleep),
|
||||
(Flake8Trio, "115") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioZeroSleepCall),
|
||||
(Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction),
|
||||
(Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::CreateSubprocessInAsyncFunction),
|
||||
(Flake8Async, "221") => (RuleGroup::Stable, rules::flake8_async::rules::RunProcessInAsyncFunction),
|
||||
(Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::WaitForProcessInAsyncFunction),
|
||||
(Flake8Async, "230") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOpenCallInAsyncFunction),
|
||||
(Flake8Async, "251") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingSleepInAsyncFunction),
|
||||
|
||||
// flake8-builtins
|
||||
(Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing),
|
||||
@@ -515,8 +476,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Simplify, "911") => (RuleGroup::Stable, rules::flake8_simplify::rules::ZipDictKeysAndValues),
|
||||
|
||||
// flake8-copyright
|
||||
#[allow(deprecated)]
|
||||
(Flake8Copyright, "001") => (RuleGroup::Nursery, rules::flake8_copyright::rules::MissingCopyrightNotice),
|
||||
(Flake8Copyright, "001") => (RuleGroup::Preview, rules::flake8_copyright::rules::MissingCopyrightNotice),
|
||||
|
||||
// pyupgrade
|
||||
(Pyupgrade, "001") => (RuleGroup::Stable, rules::pyupgrade::rules::UselessMetaclassType),
|
||||
@@ -701,7 +661,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bandit, "607") => (RuleGroup::Stable, rules::flake8_bandit::rules::StartProcessWithPartialPath),
|
||||
(Flake8Bandit, "608") => (RuleGroup::Stable, rules::flake8_bandit::rules::HardcodedSQLExpression),
|
||||
(Flake8Bandit, "609") => (RuleGroup::Stable, rules::flake8_bandit::rules::UnixCommandWildcardInjection),
|
||||
(Flake8Bandit, "610") => (RuleGroup::Preview, rules::flake8_bandit::rules::DjangoExtra),
|
||||
(Flake8Bandit, "610") => (RuleGroup::Stable, rules::flake8_bandit::rules::DjangoExtra),
|
||||
(Flake8Bandit, "611") => (RuleGroup::Stable, rules::flake8_bandit::rules::DjangoRawSql),
|
||||
(Flake8Bandit, "612") => (RuleGroup::Stable, rules::flake8_bandit::rules::LoggingConfigInsecureListen),
|
||||
(Flake8Bandit, "701") => (RuleGroup::Stable, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
|
||||
@@ -971,9 +931,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "021") => (RuleGroup::Preview, rules::ruff::rules::ParenthesizeChainedOperators),
|
||||
(Ruff, "022") => (RuleGroup::Preview, rules::ruff::rules::UnsortedDunderAll),
|
||||
(Ruff, "023") => (RuleGroup::Preview, rules::ruff::rules::UnsortedDunderSlots),
|
||||
(Ruff, "024") => (RuleGroup::Preview, rules::ruff::rules::MutableFromkeysValue),
|
||||
(Ruff, "024") => (RuleGroup::Stable, rules::ruff::rules::MutableFromkeysValue),
|
||||
(Ruff, "025") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryDictComprehensionForIterable),
|
||||
(Ruff, "026") => (RuleGroup::Preview, rules::ruff::rules::DefaultFactoryKwarg),
|
||||
(Ruff, "026") => (RuleGroup::Stable, rules::ruff::rules::DefaultFactoryKwarg),
|
||||
(Ruff, "027") => (RuleGroup::Preview, rules::ruff::rules::MissingFStringSyntax),
|
||||
(Ruff, "028") => (RuleGroup::Preview, rules::ruff::rules::InvalidFormatterSuppressionComment),
|
||||
(Ruff, "029") => (RuleGroup::Preview, rules::ruff::rules::UnusedAsync),
|
||||
@@ -992,9 +952,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
(Ruff, "911") => (RuleGroup::Preview, rules::ruff::rules::PreviewTestRule),
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
#[allow(deprecated)]
|
||||
(Ruff, "912") => (RuleGroup::Nursery, rules::ruff::rules::NurseryTestRule),
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
(Ruff, "920") => (RuleGroup::Deprecated, rules::ruff::rules::DeprecatedTestRule),
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
(Ruff, "921") => (RuleGroup::Deprecated, rules::ruff::rules::AnotherDeprecatedTestRule),
|
||||
@@ -1041,7 +998,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Perflint, "203") => (RuleGroup::Stable, rules::perflint::rules::TryExceptInLoop),
|
||||
(Perflint, "401") => (RuleGroup::Stable, rules::perflint::rules::ManualListComprehension),
|
||||
(Perflint, "402") => (RuleGroup::Stable, rules::perflint::rules::ManualListCopy),
|
||||
(Perflint, "403") => (RuleGroup::Preview, rules::perflint::rules::ManualDictComprehension),
|
||||
(Perflint, "403") => (RuleGroup::Stable, rules::perflint::rules::ManualDictComprehension),
|
||||
|
||||
// flake8-fixme
|
||||
(Flake8Fixme, "001") => (RuleGroup::Stable, rules::flake8_fixme::rules::LineContainsFixme),
|
||||
@@ -1057,18 +1014,15 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
// refurb
|
||||
(Refurb, "101") => (RuleGroup::Preview, rules::refurb::rules::ReadWholeFile),
|
||||
(Refurb, "103") => (RuleGroup::Preview, rules::refurb::rules::WriteWholeFile),
|
||||
(Refurb, "105") => (RuleGroup::Preview, rules::refurb::rules::PrintEmptyString),
|
||||
(Refurb, "105") => (RuleGroup::Stable, rules::refurb::rules::PrintEmptyString),
|
||||
(Refurb, "110") => (RuleGroup::Preview, rules::refurb::rules::IfExpInsteadOfOrOperator),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
|
||||
(Refurb, "113") => (RuleGroup::Preview, rules::refurb::rules::RepeatedAppend),
|
||||
(Refurb, "116") => (RuleGroup::Preview, rules::refurb::rules::FStringNumberFormat),
|
||||
(Refurb, "118") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedOperator),
|
||||
(Refurb, "129") => (RuleGroup::Preview, rules::refurb::rules::ReadlinesInFor),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
(Refurb, "136") => (RuleGroup::Preview, rules::refurb::rules::IfExprMinMax),
|
||||
(Refurb, "129") => (RuleGroup::Stable, rules::refurb::rules::ReadlinesInFor),
|
||||
(Refurb, "131") => (RuleGroup::Preview, rules::refurb::rules::DeleteFullSlice),
|
||||
(Refurb, "132") => (RuleGroup::Preview, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
(Refurb, "136") => (RuleGroup::Stable, rules::refurb::rules::IfExprMinMax),
|
||||
(Refurb, "140") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedStarmap),
|
||||
(Refurb, "142") => (RuleGroup::Preview, rules::refurb::rules::ForLoopSetMutations),
|
||||
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
|
||||
@@ -1076,18 +1030,18 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "152") => (RuleGroup::Preview, rules::refurb::rules::MathConstant),
|
||||
(Refurb, "154") => (RuleGroup::Preview, rules::refurb::rules::RepeatedGlobal),
|
||||
(Refurb, "157") => (RuleGroup::Preview, rules::refurb::rules::VerboseDecimalConstructor),
|
||||
(Refurb, "161") => (RuleGroup::Preview, rules::refurb::rules::BitCount),
|
||||
(Refurb, "163") => (RuleGroup::Preview, rules::refurb::rules::RedundantLogBase),
|
||||
(Refurb, "161") => (RuleGroup::Stable, rules::refurb::rules::BitCount),
|
||||
(Refurb, "163") => (RuleGroup::Stable, rules::refurb::rules::RedundantLogBase),
|
||||
(Refurb, "164") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryFromFloat),
|
||||
(Refurb, "166") => (RuleGroup::Preview, rules::refurb::rules::IntOnSlicedStr),
|
||||
(Refurb, "167") => (RuleGroup::Preview, rules::refurb::rules::RegexFlagAlias),
|
||||
(Refurb, "168") => (RuleGroup::Preview, rules::refurb::rules::IsinstanceTypeNone),
|
||||
(Refurb, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison),
|
||||
(Refurb, "167") => (RuleGroup::Stable, rules::refurb::rules::RegexFlagAlias),
|
||||
(Refurb, "168") => (RuleGroup::Stable, rules::refurb::rules::IsinstanceTypeNone),
|
||||
(Refurb, "169") => (RuleGroup::Stable, rules::refurb::rules::TypeNoneComparison),
|
||||
(Refurb, "171") => (RuleGroup::Preview, rules::refurb::rules::SingleItemMembershipTest),
|
||||
(Refurb, "177") => (RuleGroup::Preview, rules::refurb::rules::ImplicitCwd),
|
||||
(Refurb, "177") => (RuleGroup::Stable, rules::refurb::rules::ImplicitCwd),
|
||||
(Refurb, "180") => (RuleGroup::Preview, rules::refurb::rules::MetaClassABCMeta),
|
||||
(Refurb, "181") => (RuleGroup::Preview, rules::refurb::rules::HashlibDigestHex),
|
||||
(Refurb, "187") => (RuleGroup::Preview, rules::refurb::rules::ListReverseCopy),
|
||||
(Refurb, "181") => (RuleGroup::Stable, rules::refurb::rules::HashlibDigestHex),
|
||||
(Refurb, "187") => (RuleGroup::Stable, rules::refurb::rules::ListReverseCopy),
|
||||
(Refurb, "192") => (RuleGroup::Preview, rules::refurb::rules::SortedMinMax),
|
||||
|
||||
// flake8-logging
|
||||
|
||||
@@ -11,7 +11,7 @@ pub use registry::clap_completion::RuleParser;
|
||||
#[cfg(feature = "clap")]
|
||||
pub use rule_selector::clap_completion::RuleSelectorParser;
|
||||
pub use rule_selector::RuleSelector;
|
||||
pub use rules::pycodestyle::rules::{IOError, SyntaxError};
|
||||
pub use rules::pycodestyle::rules::IOError;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::path::Path;
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
@@ -26,11 +25,9 @@ use crate::checkers::tokens::check_tokens;
|
||||
use crate::directives::Directives;
|
||||
use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
|
||||
use crate::fix::{fix_file, FixResult};
|
||||
use crate::logging::DisplayParseError;
|
||||
use crate::message::Message;
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::registry::{AsRule, Rule, RuleSet};
|
||||
use crate::rules::pycodestyle;
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
use crate::rules::ruff::rules::test_rules::{self, TestRule, TEST_RULES};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
@@ -38,29 +35,19 @@ use crate::settings::{flags, LinterSettings};
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::{directives, fs};
|
||||
|
||||
/// A [`Result`]-like type that returns both data and an error. Used to return
|
||||
/// diagnostics even in the face of parse errors, since many diagnostics can be
|
||||
/// generated without a full AST.
|
||||
pub struct LinterResult<T> {
|
||||
pub data: T,
|
||||
pub error: Option<ParseError>,
|
||||
}
|
||||
|
||||
impl<T> LinterResult<T> {
|
||||
const fn new(data: T, error: Option<ParseError>) -> Self {
|
||||
Self { data, error }
|
||||
}
|
||||
|
||||
fn map<U, F: FnOnce(T) -> U>(self, f: F) -> LinterResult<U> {
|
||||
LinterResult::new(f(self.data), self.error)
|
||||
}
|
||||
pub struct LinterResult {
|
||||
/// A collection of diagnostic messages generated by the linter.
|
||||
pub messages: Vec<Message>,
|
||||
/// A flag indicating the presence of syntax errors in the source file.
|
||||
/// If `true`, at least one syntax error was detected in the source file.
|
||||
pub has_syntax_error: bool,
|
||||
}
|
||||
|
||||
pub type FixTable = FxHashMap<Rule, usize>;
|
||||
|
||||
pub struct FixerResult<'a> {
|
||||
/// The result returned by the linter, after applying any fixes.
|
||||
pub result: LinterResult<Vec<Message>>,
|
||||
pub result: LinterResult,
|
||||
/// The resulting source code, after applying any fixes.
|
||||
pub transformed: Cow<'a, SourceKind>,
|
||||
/// The number of fixes applied for each [`Rule`].
|
||||
@@ -82,10 +69,9 @@ pub fn check_path(
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
parsed: &Parsed<ModModule>,
|
||||
) -> LinterResult<Vec<Diagnostic>> {
|
||||
) -> Vec<Diagnostic> {
|
||||
// Aggregate all diagnostics.
|
||||
let mut diagnostics = vec![];
|
||||
let mut error = None;
|
||||
|
||||
let tokens = parsed.tokens();
|
||||
let comment_ranges = indexer.comment_ranges();
|
||||
@@ -142,67 +128,53 @@ pub fn check_path(
|
||||
));
|
||||
}
|
||||
|
||||
// Run the AST-based rules.
|
||||
let use_ast = settings
|
||||
.rules
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_ast());
|
||||
let use_imports = !directives.isort.skip_file
|
||||
&& settings
|
||||
// Run the AST-based rules only if there are no syntax errors.
|
||||
if parsed.is_valid() {
|
||||
let use_ast = settings
|
||||
.rules
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_imports());
|
||||
if use_ast || use_imports || use_doc_lines {
|
||||
match parsed.as_result() {
|
||||
Ok(parsed) => {
|
||||
let cell_offsets = source_kind.as_ipy_notebook().map(Notebook::cell_offsets);
|
||||
let notebook_index = source_kind.as_ipy_notebook().map(Notebook::index);
|
||||
if use_ast {
|
||||
diagnostics.extend(check_ast(
|
||||
parsed,
|
||||
locator,
|
||||
stylist,
|
||||
indexer,
|
||||
&directives.noqa_line_for,
|
||||
settings,
|
||||
noqa,
|
||||
path,
|
||||
package,
|
||||
source_type,
|
||||
cell_offsets,
|
||||
notebook_index,
|
||||
));
|
||||
}
|
||||
if use_imports {
|
||||
let import_diagnostics = check_imports(
|
||||
parsed,
|
||||
locator,
|
||||
indexer,
|
||||
&directives.isort,
|
||||
settings,
|
||||
stylist,
|
||||
package,
|
||||
source_type,
|
||||
cell_offsets,
|
||||
);
|
||||
|
||||
diagnostics.extend(import_diagnostics);
|
||||
}
|
||||
if use_doc_lines {
|
||||
doc_lines.extend(doc_lines_from_ast(parsed.suite(), locator));
|
||||
}
|
||||
.any(|rule_code| rule_code.lint_source().is_ast());
|
||||
let use_imports = !directives.isort.skip_file
|
||||
&& settings
|
||||
.rules
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_imports());
|
||||
if use_ast || use_imports || use_doc_lines {
|
||||
let cell_offsets = source_kind.as_ipy_notebook().map(Notebook::cell_offsets);
|
||||
let notebook_index = source_kind.as_ipy_notebook().map(Notebook::index);
|
||||
if use_ast {
|
||||
diagnostics.extend(check_ast(
|
||||
parsed,
|
||||
locator,
|
||||
stylist,
|
||||
indexer,
|
||||
&directives.noqa_line_for,
|
||||
settings,
|
||||
noqa,
|
||||
path,
|
||||
package,
|
||||
source_type,
|
||||
cell_offsets,
|
||||
notebook_index,
|
||||
));
|
||||
}
|
||||
Err(parse_errors) => {
|
||||
// Always add a diagnostic for the syntax error, regardless of whether
|
||||
// `Rule::SyntaxError` is enabled. We avoid propagating the syntax error
|
||||
// if it's disabled via any of the usual mechanisms (e.g., `noqa`,
|
||||
// `per-file-ignores`), and the easiest way to detect that suppression is
|
||||
// to see if the diagnostic persists to the end of the function.
|
||||
for parse_error in parse_errors {
|
||||
pycodestyle::rules::syntax_error(&mut diagnostics, parse_error, locator);
|
||||
}
|
||||
// TODO(dhruvmanila): Remove this clone
|
||||
error = parse_errors.iter().next().cloned();
|
||||
if use_imports {
|
||||
let import_diagnostics = check_imports(
|
||||
parsed,
|
||||
locator,
|
||||
indexer,
|
||||
&directives.isort,
|
||||
settings,
|
||||
stylist,
|
||||
package,
|
||||
source_type,
|
||||
cell_offsets,
|
||||
);
|
||||
|
||||
diagnostics.extend(import_diagnostics);
|
||||
}
|
||||
if use_doc_lines {
|
||||
doc_lines.extend(doc_lines_from_ast(parsed.suite(), locator));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,9 +216,6 @@ pub fn check_path(
|
||||
Rule::StableTestRuleDisplayOnlyFix => {
|
||||
test_rules::StableTestRuleDisplayOnlyFix::diagnostic(locator, comment_ranges)
|
||||
}
|
||||
Rule::NurseryTestRule => {
|
||||
test_rules::NurseryTestRule::diagnostic(locator, comment_ranges)
|
||||
}
|
||||
Rule::PreviewTestRule => {
|
||||
test_rules::PreviewTestRule::diagnostic(locator, comment_ranges)
|
||||
}
|
||||
@@ -308,7 +277,7 @@ pub fn check_path(
|
||||
locator,
|
||||
comment_ranges,
|
||||
&directives.noqa_line_for,
|
||||
error.is_none(),
|
||||
parsed.is_valid(),
|
||||
&per_file_ignores,
|
||||
settings,
|
||||
);
|
||||
@@ -319,23 +288,6 @@ pub fn check_path(
|
||||
}
|
||||
}
|
||||
|
||||
// If there was a syntax error, check if it should be discarded.
|
||||
if error.is_some() {
|
||||
// If the syntax error was removed by _any_ of the above disablement methods (e.g., a
|
||||
// `noqa` directive, or a `per-file-ignore`), discard it.
|
||||
if !diagnostics
|
||||
.iter()
|
||||
.any(|diagnostic| diagnostic.kind.rule() == Rule::SyntaxError)
|
||||
{
|
||||
error = None;
|
||||
}
|
||||
|
||||
// If the syntax error _diagnostic_ is disabled, discard the _diagnostic_.
|
||||
if !settings.rules.enabled(Rule::SyntaxError) {
|
||||
diagnostics.retain(|diagnostic| diagnostic.kind.rule() != Rule::SyntaxError);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove fixes for any rules marked as unfixable.
|
||||
for diagnostic in &mut diagnostics {
|
||||
if !settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
@@ -355,7 +307,7 @@ pub fn check_path(
|
||||
}
|
||||
}
|
||||
|
||||
LinterResult::new(diagnostics, error)
|
||||
diagnostics
|
||||
}
|
||||
|
||||
const MAX_ITERATIONS: usize = 100;
|
||||
@@ -389,10 +341,7 @@ pub fn add_noqa_to_path(
|
||||
);
|
||||
|
||||
// Generate diagnostics, ignoring any existing `noqa` directives.
|
||||
let LinterResult {
|
||||
data: diagnostics,
|
||||
error,
|
||||
} = check_path(
|
||||
let diagnostics = check_path(
|
||||
path,
|
||||
package,
|
||||
&locator,
|
||||
@@ -406,19 +355,6 @@ pub fn add_noqa_to_path(
|
||||
&parsed,
|
||||
);
|
||||
|
||||
// Log any parse errors.
|
||||
if let Some(error) = error {
|
||||
error!(
|
||||
"{}",
|
||||
DisplayParseError::from_source_code(
|
||||
error,
|
||||
Some(path.to_path_buf()),
|
||||
&locator.to_source_code(),
|
||||
source_kind,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Add any missing `# noqa` pragmas.
|
||||
// TODO(dhruvmanila): Add support for Jupyter Notebooks
|
||||
add_noqa(
|
||||
@@ -442,7 +378,7 @@ pub fn lint_only(
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
source: ParseSource,
|
||||
) -> LinterResult<Vec<Message>> {
|
||||
) -> LinterResult {
|
||||
let parsed = source.into_parsed(source_kind, source_type);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
@@ -463,7 +399,7 @@ pub fn lint_only(
|
||||
);
|
||||
|
||||
// Generate diagnostics.
|
||||
let result = check_path(
|
||||
let diagnostics = check_path(
|
||||
path,
|
||||
package,
|
||||
&locator,
|
||||
@@ -477,12 +413,22 @@ pub fn lint_only(
|
||||
&parsed,
|
||||
);
|
||||
|
||||
result.map(|diagnostics| diagnostics_to_messages(diagnostics, path, &locator, &directives))
|
||||
LinterResult {
|
||||
messages: diagnostics_to_messages(
|
||||
diagnostics,
|
||||
parsed.errors(),
|
||||
path,
|
||||
&locator,
|
||||
&directives,
|
||||
),
|
||||
has_syntax_error: !parsed.is_valid(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from diagnostics to messages.
|
||||
fn diagnostics_to_messages(
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
parse_errors: &[ParseError],
|
||||
path: &Path,
|
||||
locator: &Locator,
|
||||
directives: &Directives,
|
||||
@@ -498,12 +444,13 @@ fn diagnostics_to_messages(
|
||||
builder.finish()
|
||||
});
|
||||
|
||||
diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| {
|
||||
parse_errors
|
||||
.iter()
|
||||
.map(|parse_error| Message::from_parse_error(parse_error, locator, file.deref().clone()))
|
||||
.chain(diagnostics.into_iter().map(|diagnostic| {
|
||||
let noqa_offset = directives.noqa_line_for.resolve(diagnostic.start());
|
||||
Message::from_diagnostic(diagnostic, file.deref().clone(), noqa_offset)
|
||||
})
|
||||
}))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -527,8 +474,8 @@ pub fn lint_fix<'a>(
|
||||
// As an escape hatch, bail after 100 iterations.
|
||||
let mut iterations = 0;
|
||||
|
||||
// Track whether the _initial_ source code was parseable.
|
||||
let mut parseable = false;
|
||||
// Track whether the _initial_ source code is valid syntax.
|
||||
let mut is_valid_syntax = false;
|
||||
|
||||
// Continuously fix until the source code stabilizes.
|
||||
loop {
|
||||
@@ -554,7 +501,7 @@ pub fn lint_fix<'a>(
|
||||
);
|
||||
|
||||
// Generate diagnostics.
|
||||
let result = check_path(
|
||||
let diagnostics = check_path(
|
||||
path,
|
||||
package,
|
||||
&locator,
|
||||
@@ -569,19 +516,21 @@ pub fn lint_fix<'a>(
|
||||
);
|
||||
|
||||
if iterations == 0 {
|
||||
parseable = result.error.is_none();
|
||||
is_valid_syntax = parsed.is_valid();
|
||||
} else {
|
||||
// If the source code was parseable on the first pass, but is no
|
||||
// longer parseable on a subsequent pass, then we've introduced a
|
||||
// syntax error. Return the original code.
|
||||
if parseable && result.error.is_some() {
|
||||
report_fix_syntax_error(
|
||||
path,
|
||||
transformed.source_code(),
|
||||
&result.error.unwrap(),
|
||||
fixed.keys().copied(),
|
||||
);
|
||||
return Err(anyhow!("Fix introduced a syntax error"));
|
||||
if is_valid_syntax {
|
||||
if let Some(error) = parsed.errors().first() {
|
||||
report_fix_syntax_error(
|
||||
path,
|
||||
transformed.source_code(),
|
||||
error,
|
||||
fixed.keys().copied(),
|
||||
);
|
||||
return Err(anyhow!("Fix introduced a syntax error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,7 +539,7 @@ pub fn lint_fix<'a>(
|
||||
code: fixed_contents,
|
||||
fixes: applied,
|
||||
source_map,
|
||||
}) = fix_file(&result.data, &locator, unsafe_fixes)
|
||||
}) = fix_file(&diagnostics, &locator, unsafe_fixes)
|
||||
{
|
||||
if iterations < MAX_ITERATIONS {
|
||||
// Count the number of fixed errors.
|
||||
@@ -607,13 +556,20 @@ pub fn lint_fix<'a>(
|
||||
continue;
|
||||
}
|
||||
|
||||
report_failed_to_converge_error(path, transformed.source_code(), &result.data);
|
||||
report_failed_to_converge_error(path, transformed.source_code(), &diagnostics);
|
||||
}
|
||||
|
||||
return Ok(FixerResult {
|
||||
result: result.map(|diagnostics| {
|
||||
diagnostics_to_messages(diagnostics, path, &locator, &directives)
|
||||
}),
|
||||
result: LinterResult {
|
||||
messages: diagnostics_to_messages(
|
||||
diagnostics,
|
||||
parsed.errors(),
|
||||
path,
|
||||
&locator,
|
||||
&directives,
|
||||
),
|
||||
has_syntax_error: !is_valid_syntax,
|
||||
},
|
||||
transformed,
|
||||
fixed,
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::io::Write;
|
||||
use ruff_source_file::SourceLocation;
|
||||
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// Generate error logging commands for Azure Pipelines format.
|
||||
/// See [documentation](https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#logissue-log-an-error-or-warning)
|
||||
@@ -29,12 +28,14 @@ impl Emitter for AzureEmitter {
|
||||
writeln!(
|
||||
writer,
|
||||
"##vso[task.logissue type=error\
|
||||
;sourcepath={filename};linenumber={line};columnnumber={col};code={code};]{body}",
|
||||
;sourcepath={filename};linenumber={line};columnnumber={col};{code}]{body}",
|
||||
filename = message.filename(),
|
||||
line = location.row,
|
||||
col = location.column,
|
||||
code = message.kind.rule().noqa_code(),
|
||||
body = message.kind.body,
|
||||
code = message
|
||||
.rule()
|
||||
.map_or_else(String::new, |rule| format!("code={};", rule.noqa_code())),
|
||||
body = message.body(),
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -46,7 +47,9 @@ impl Emitter for AzureEmitter {
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_messages, create_syntax_error_messages,
|
||||
};
|
||||
use crate::message::AzureEmitter;
|
||||
|
||||
#[test]
|
||||
@@ -56,4 +59,12 @@ mod tests {
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = AzureEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ pub(super) struct Diff<'a> {
|
||||
|
||||
impl<'a> Diff<'a> {
|
||||
pub(crate) fn from_message(message: &'a Message) -> Option<Diff> {
|
||||
message.fix.as_ref().map(|fix| Diff {
|
||||
source_code: &message.file,
|
||||
message.fix().map(|fix| Diff {
|
||||
source_code: message.source_file(),
|
||||
fix,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use ruff_source_file::SourceLocation;
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// Generate error workflow command in GitHub Actions format.
|
||||
/// See: [GitHub documentation](https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message)
|
||||
@@ -32,9 +31,8 @@ impl Emitter for GithubEmitter {
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"::error title=Ruff \
|
||||
({code}),file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
code = message.kind.rule().noqa_code(),
|
||||
"::error title=Ruff{code},file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
code = message.rule().map_or_else(String::new, |rule| format!(" ({})", rule.noqa_code())),
|
||||
file = message.filename(),
|
||||
row = source_location.row,
|
||||
column = source_location.column,
|
||||
@@ -42,15 +40,19 @@ impl Emitter for GithubEmitter {
|
||||
end_column = end_location.column,
|
||||
)?;
|
||||
|
||||
writeln!(
|
||||
write!(
|
||||
writer,
|
||||
"{path}:{row}:{column}: {code} {body}",
|
||||
"{path}:{row}:{column}:",
|
||||
path = relativize_path(message.filename()),
|
||||
row = location.row,
|
||||
column = location.column,
|
||||
code = message.kind.rule().noqa_code(),
|
||||
body = message.kind.body,
|
||||
)?;
|
||||
|
||||
if let Some(rule) = message.rule() {
|
||||
write!(writer, " {}", rule.noqa_code())?;
|
||||
}
|
||||
|
||||
writeln!(writer, " {}", message.body())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -61,7 +63,9 @@ impl Emitter for GithubEmitter {
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_messages, create_syntax_error_messages,
|
||||
};
|
||||
use crate::message::GithubEmitter;
|
||||
|
||||
#[test]
|
||||
@@ -71,4 +75,12 @@ mod tests {
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = GithubEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use serde_json::json;
|
||||
|
||||
use crate::fs::{relativize_path, relativize_path_to};
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// Generate JSON with violations in GitLab CI format
|
||||
// https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool
|
||||
@@ -91,8 +90,14 @@ impl Serialize for SerializedMessages<'_> {
|
||||
}
|
||||
fingerprints.insert(message_fingerprint);
|
||||
|
||||
let description = if let Some(rule) = message.rule() {
|
||||
format!("({}) {}", rule.noqa_code(), message.body())
|
||||
} else {
|
||||
message.body().to_string()
|
||||
};
|
||||
|
||||
let value = json!({
|
||||
"description": format!("({}) {}", message.kind.rule().noqa_code(), message.kind.body),
|
||||
"description": description,
|
||||
"severity": "major",
|
||||
"fingerprint": format!("{:x}", message_fingerprint),
|
||||
"location": {
|
||||
@@ -110,18 +115,10 @@ impl Serialize for SerializedMessages<'_> {
|
||||
|
||||
/// Generate a unique fingerprint to identify a violation.
|
||||
fn fingerprint(message: &Message, project_path: &str, salt: u64) -> u64 {
|
||||
let Message {
|
||||
kind,
|
||||
range: _,
|
||||
fix: _fix,
|
||||
file: _,
|
||||
noqa_offset: _,
|
||||
} = message;
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
||||
salt.hash(&mut hasher);
|
||||
kind.name.hash(&mut hasher);
|
||||
message.name().hash(&mut hasher);
|
||||
project_path.hash(&mut hasher);
|
||||
|
||||
hasher.finish()
|
||||
@@ -131,7 +128,9 @@ fn fingerprint(message: &Message, project_path: &str, salt: u64) -> u64 {
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_messages, create_syntax_error_messages,
|
||||
};
|
||||
use crate::message::GitlabEmitter;
|
||||
|
||||
#[test]
|
||||
@@ -142,6 +141,14 @@ mod tests {
|
||||
assert_snapshot!(redact_fingerprint(&content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = GitlabEmitter::default();
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
|
||||
assert_snapshot!(redact_fingerprint(&content));
|
||||
}
|
||||
|
||||
// Redact the fingerprint because the default hasher isn't stable across platforms.
|
||||
fn redact_fingerprint(content: &str) -> String {
|
||||
static FINGERPRINT_HAY_KEY: &str = r#""fingerprint": ""#;
|
||||
|
||||
@@ -205,7 +205,9 @@ impl std::fmt::Write for PadAdapter<'_> {
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_messages, create_syntax_error_messages,
|
||||
};
|
||||
use crate::message::GroupedEmitter;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
@@ -217,6 +219,14 @@ mod tests {
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = GroupedEmitter::default();
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_source() {
|
||||
let mut emitter = GroupedEmitter::default().with_show_source(true);
|
||||
|
||||
@@ -10,7 +10,6 @@ use ruff_source_file::{OneIndexed, SourceCode, SourceLocation};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JsonEmitter;
|
||||
@@ -50,20 +49,22 @@ impl Serialize for ExpandedMessages<'_> {
|
||||
}
|
||||
|
||||
pub(crate) fn message_to_json_value(message: &Message, context: &EmitterContext) -> Value {
|
||||
let source_code = message.file.to_source_code();
|
||||
let source_code = message.source_file().to_source_code();
|
||||
let notebook_index = context.notebook_index(message.filename());
|
||||
|
||||
let fix = message.fix.as_ref().map(|fix| {
|
||||
let fix = message.fix().map(|fix| {
|
||||
json!({
|
||||
"applicability": fix.applicability(),
|
||||
"message": message.kind.suggestion.as_deref(),
|
||||
"message": message.suggestion(),
|
||||
"edits": &ExpandedEdits { edits: fix.edits(), source_code: &source_code, notebook_index },
|
||||
})
|
||||
});
|
||||
|
||||
let mut start_location = source_code.source_location(message.start());
|
||||
let mut end_location = source_code.source_location(message.end());
|
||||
let mut noqa_location = source_code.source_location(message.noqa_offset);
|
||||
let mut noqa_location = message
|
||||
.noqa_offset()
|
||||
.map(|offset| source_code.source_location(offset));
|
||||
let mut notebook_cell_index = None;
|
||||
|
||||
if let Some(notebook_index) = notebook_index {
|
||||
@@ -74,19 +75,19 @@ pub(crate) fn message_to_json_value(message: &Message, context: &EmitterContext)
|
||||
);
|
||||
start_location = notebook_index.translate_location(&start_location);
|
||||
end_location = notebook_index.translate_location(&end_location);
|
||||
noqa_location = notebook_index.translate_location(&noqa_location);
|
||||
noqa_location = noqa_location.map(|location| notebook_index.translate_location(&location));
|
||||
}
|
||||
|
||||
json!({
|
||||
"code": message.kind.rule().noqa_code().to_string(),
|
||||
"url": message.kind.rule().url(),
|
||||
"message": message.kind.body,
|
||||
"code": message.rule().map(|rule| rule.noqa_code().to_string()),
|
||||
"url": message.rule().and_then(|rule| rule.url()),
|
||||
"message": message.body(),
|
||||
"fix": fix,
|
||||
"cell": notebook_cell_index,
|
||||
"location": start_location,
|
||||
"end_location": end_location,
|
||||
"filename": message.filename(),
|
||||
"noqa_row": noqa_location.row
|
||||
"noqa_row": noqa_location.map(|location| location.row)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -170,7 +171,7 @@ mod tests {
|
||||
|
||||
use crate::message::tests::{
|
||||
capture_emitter_notebook_output, capture_emitter_output, create_messages,
|
||||
create_notebook_messages,
|
||||
create_notebook_messages, create_syntax_error_messages,
|
||||
};
|
||||
use crate::message::JsonEmitter;
|
||||
|
||||
@@ -182,6 +183,14 @@ mod tests {
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = JsonEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notebook_output() {
|
||||
let mut emitter = JsonEmitter;
|
||||
|
||||
@@ -29,7 +29,7 @@ mod tests {
|
||||
use crate::message::json_lines::JsonLinesEmitter;
|
||||
use crate::message::tests::{
|
||||
capture_emitter_notebook_output, capture_emitter_output, create_messages,
|
||||
create_notebook_messages,
|
||||
create_notebook_messages, create_syntax_error_messages,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -40,6 +40,14 @@ mod tests {
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = JsonLinesEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notebook_output() {
|
||||
let mut emitter = JsonLinesEmitter;
|
||||
|
||||
@@ -8,7 +8,6 @@ use ruff_source_file::SourceLocation;
|
||||
use crate::message::{
|
||||
group_messages_by_filename, Emitter, EmitterContext, Message, MessageWithLocation,
|
||||
};
|
||||
use crate::registry::AsRule;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JunitEmitter;
|
||||
@@ -44,7 +43,7 @@ impl Emitter for JunitEmitter {
|
||||
start_location,
|
||||
} = message;
|
||||
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
|
||||
status.set_message(message.kind.body.clone());
|
||||
status.set_message(message.body());
|
||||
let location = if context.is_notebook(message.filename()) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
@@ -57,10 +56,14 @@ impl Emitter for JunitEmitter {
|
||||
"line {row}, col {col}, {body}",
|
||||
row = location.row,
|
||||
col = location.column,
|
||||
body = message.kind.body
|
||||
body = message.body()
|
||||
));
|
||||
let mut case = TestCase::new(
|
||||
format!("org.ruff.{}", message.kind.rule().noqa_code()),
|
||||
if let Some(rule) = message.rule() {
|
||||
format!("org.ruff.{}", rule.noqa_code())
|
||||
} else {
|
||||
"org.ruff".to_string()
|
||||
},
|
||||
status,
|
||||
);
|
||||
let file_path = Path::new(filename);
|
||||
@@ -88,7 +91,9 @@ impl Emitter for JunitEmitter {
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_messages, create_syntax_error_messages,
|
||||
};
|
||||
use crate::message::JunitEmitter;
|
||||
|
||||
#[test]
|
||||
@@ -98,4 +103,12 @@ mod tests {
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = JunitEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,18 @@ pub use json_lines::JsonLinesEmitter;
|
||||
pub use junit::JunitEmitter;
|
||||
pub use pylint::PylintEmitter;
|
||||
pub use rdjson::RdjsonEmitter;
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{SourceFile, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
pub use sarif::SarifEmitter;
|
||||
pub use text::TextEmitter;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_python_parser::ParseError;
|
||||
use ruff_source_file::{Locator, SourceFile, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::logging::DisplayParseErrorType;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
mod azure;
|
||||
mod diff;
|
||||
mod github;
|
||||
@@ -34,8 +39,17 @@ mod rdjson;
|
||||
mod sarif;
|
||||
mod text;
|
||||
|
||||
/// Message represents either a diagnostic message corresponding to a rule violation or a syntax
|
||||
/// error message raised by the parser.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Message {
|
||||
pub enum Message {
|
||||
Diagnostic(DiagnosticMessage),
|
||||
SyntaxError(SyntaxErrorMessage),
|
||||
}
|
||||
|
||||
/// A diagnostic message corresponding to a rule violation.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct DiagnosticMessage {
|
||||
pub kind: DiagnosticKind,
|
||||
pub range: TextRange,
|
||||
pub fix: Option<Fix>,
|
||||
@@ -43,37 +57,174 @@ pub struct Message {
|
||||
pub noqa_offset: TextSize,
|
||||
}
|
||||
|
||||
/// A syntax error message raised by the parser.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SyntaxErrorMessage {
|
||||
pub message: String,
|
||||
pub range: TextRange,
|
||||
pub file: SourceFile,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum MessageKind {
|
||||
Diagnostic(Rule),
|
||||
SyntaxError,
|
||||
}
|
||||
|
||||
impl MessageKind {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
MessageKind::Diagnostic(rule) => rule.as_ref(),
|
||||
MessageKind::SyntaxError => "syntax-error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Message {
|
||||
/// Create a [`Message`] from the given [`Diagnostic`] corresponding to a rule violation.
|
||||
pub fn from_diagnostic(
|
||||
diagnostic: Diagnostic,
|
||||
file: SourceFile,
|
||||
noqa_offset: TextSize,
|
||||
) -> Self {
|
||||
Self {
|
||||
) -> Message {
|
||||
Message::Diagnostic(DiagnosticMessage {
|
||||
range: diagnostic.range(),
|
||||
kind: diagnostic.kind,
|
||||
fix: diagnostic.fix,
|
||||
file,
|
||||
noqa_offset,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a [`Message`] from the given [`ParseError`].
|
||||
pub fn from_parse_error(
|
||||
parse_error: &ParseError,
|
||||
locator: &Locator,
|
||||
file: SourceFile,
|
||||
) -> Message {
|
||||
// Try to create a non-empty range so that the diagnostic can print a caret at the right
|
||||
// position. This requires that we retrieve the next character, if any, and take its length
|
||||
// to maintain char-boundaries.
|
||||
let len = locator
|
||||
.after(parse_error.location.start())
|
||||
.chars()
|
||||
.next()
|
||||
.map_or(TextSize::new(0), TextLen::text_len);
|
||||
|
||||
Message::SyntaxError(SyntaxErrorMessage {
|
||||
message: format!(
|
||||
"SyntaxError: {}",
|
||||
DisplayParseErrorType::new(&parse_error.error)
|
||||
),
|
||||
range: TextRange::at(parse_error.location.start(), len),
|
||||
file,
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn as_diagnostic_message(&self) -> Option<&DiagnosticMessage> {
|
||||
match self {
|
||||
Message::Diagnostic(m) => Some(m),
|
||||
Message::SyntaxError(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is a syntax error message.
|
||||
pub const fn is_syntax_error(&self) -> bool {
|
||||
matches!(self, Message::SyntaxError(_))
|
||||
}
|
||||
|
||||
/// Returns a message kind.
|
||||
pub fn kind(&self) -> MessageKind {
|
||||
match self {
|
||||
Message::Diagnostic(m) => MessageKind::Diagnostic(m.kind.rule()),
|
||||
Message::SyntaxError(_) => MessageKind::SyntaxError,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the name used to represent the diagnostic.
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
Message::Diagnostic(m) => &m.kind.name,
|
||||
Message::SyntaxError(_) => "SyntaxError",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the message body to display to the user.
|
||||
pub fn body(&self) -> &str {
|
||||
match self {
|
||||
Message::Diagnostic(m) => &m.kind.body,
|
||||
Message::SyntaxError(m) => &m.message,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the fix suggestion for the violation.
|
||||
pub fn suggestion(&self) -> Option<&str> {
|
||||
match self {
|
||||
Message::Diagnostic(m) => m.kind.suggestion.as_deref(),
|
||||
Message::SyntaxError(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the offset at which the `noqa` comment will be placed if it's a diagnostic message.
|
||||
pub fn noqa_offset(&self) -> Option<TextSize> {
|
||||
match self {
|
||||
Message::Diagnostic(m) => Some(m.noqa_offset),
|
||||
Message::SyntaxError(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Fix`] for the message, if there is any.
|
||||
pub fn fix(&self) -> Option<&Fix> {
|
||||
match self {
|
||||
Message::Diagnostic(m) => m.fix.as_ref(),
|
||||
Message::SyntaxError(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the message contains a [`Fix`].
|
||||
pub fn fixable(&self) -> bool {
|
||||
self.fix().is_some()
|
||||
}
|
||||
|
||||
/// Returns the [`Rule`] corresponding to the diagnostic message.
|
||||
pub fn rule(&self) -> Option<Rule> {
|
||||
match self {
|
||||
Message::Diagnostic(m) => Some(m.kind.rule()),
|
||||
Message::SyntaxError(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the filename for the message.
|
||||
pub fn filename(&self) -> &str {
|
||||
self.file.name()
|
||||
self.source_file().name()
|
||||
}
|
||||
|
||||
/// Computes the start source location for the message.
|
||||
pub fn compute_start_location(&self) -> SourceLocation {
|
||||
self.file.to_source_code().source_location(self.start())
|
||||
self.source_file()
|
||||
.to_source_code()
|
||||
.source_location(self.start())
|
||||
}
|
||||
|
||||
/// Computes the end source location for the message.
|
||||
pub fn compute_end_location(&self) -> SourceLocation {
|
||||
self.file.to_source_code().source_location(self.end())
|
||||
self.source_file()
|
||||
.to_source_code()
|
||||
.source_location(self.end())
|
||||
}
|
||||
|
||||
/// Returns the [`SourceFile`] which the message belongs to.
|
||||
pub fn source_file(&self) -> &SourceFile {
|
||||
match self {
|
||||
Message::Diagnostic(m) => &m.file,
|
||||
Message::SyntaxError(m) => &m.file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Message {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
(&self.file, self.start()).cmp(&(&other.file, other.start()))
|
||||
(self.source_file(), self.start()).cmp(&(other.source_file(), other.start()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +236,10 @@ impl PartialOrd for Message {
|
||||
|
||||
impl Ranged for Message {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range
|
||||
match self {
|
||||
Message::Diagnostic(m) => m.range,
|
||||
Message::SyntaxError(m) => m.range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,11 +309,30 @@ mod tests {
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Edit, Fix};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{OneIndexed, SourceFileBuilder};
|
||||
use ruff_python_parser::{parse_unchecked, Mode};
|
||||
use ruff_source_file::{Locator, OneIndexed, SourceFileBuilder};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
|
||||
pub(super) fn create_syntax_error_messages() -> Vec<Message> {
|
||||
let source = r"from os import
|
||||
|
||||
if call(foo
|
||||
def bar():
|
||||
pass
|
||||
";
|
||||
let locator = Locator::new(source);
|
||||
let source_file = SourceFileBuilder::new("syntax_errors.py", source).finish();
|
||||
parse_unchecked(source, Mode::Module)
|
||||
.errors()
|
||||
.iter()
|
||||
.map(|parse_error| {
|
||||
Message::from_parse_error(parse_error, &locator, source_file.clone())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(super) fn create_messages() -> Vec<Message> {
|
||||
let fib = r#"import os
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use ruff_source_file::OneIndexed;
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// Generate violations in Pylint format.
|
||||
/// See: [Flake8 documentation](https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter)
|
||||
@@ -27,12 +26,20 @@ impl Emitter for PylintEmitter {
|
||||
message.compute_start_location().row
|
||||
};
|
||||
|
||||
let body = if let Some(rule) = message.rule() {
|
||||
format!(
|
||||
"[{code}] {body}",
|
||||
code = rule.noqa_code(),
|
||||
body = message.body()
|
||||
)
|
||||
} else {
|
||||
message.body().to_string()
|
||||
};
|
||||
|
||||
writeln!(
|
||||
writer,
|
||||
"{path}:{row}: [{code}] {body}",
|
||||
"{path}:{row}: {body}",
|
||||
path = relativize_path(message.filename()),
|
||||
code = message.kind.rule().noqa_code(),
|
||||
body = message.kind.body,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -44,7 +51,9 @@ impl Emitter for PylintEmitter {
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_messages, create_syntax_error_messages,
|
||||
};
|
||||
use crate::message::PylintEmitter;
|
||||
|
||||
#[test]
|
||||
@@ -54,4 +63,12 @@ mod tests {
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = PylintEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use ruff_source_file::SourceCode;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::message::{Emitter, EmitterContext, Message, SourceLocation};
|
||||
use crate::registry::AsRule;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RdjsonEmitter;
|
||||
@@ -58,34 +57,34 @@ impl Serialize for ExpandedMessages<'_> {
|
||||
}
|
||||
|
||||
fn message_to_rdjson_value(message: &Message) -> Value {
|
||||
let source_code = message.file.to_source_code();
|
||||
let source_code = message.source_file().to_source_code();
|
||||
|
||||
let start_location = source_code.source_location(message.start());
|
||||
let end_location = source_code.source_location(message.end());
|
||||
|
||||
if let Some(fix) = message.fix.as_ref() {
|
||||
if let Some(fix) = message.fix() {
|
||||
json!({
|
||||
"message": message.kind.body,
|
||||
"message": message.body(),
|
||||
"location": {
|
||||
"path": message.filename(),
|
||||
"range": rdjson_range(&start_location, &end_location),
|
||||
},
|
||||
"code": {
|
||||
"value": message.kind.rule().noqa_code().to_string(),
|
||||
"url": message.kind.rule().url(),
|
||||
"value": message.rule().map(|rule| rule.noqa_code().to_string()),
|
||||
"url": message.rule().and_then(|rule| rule.url()),
|
||||
},
|
||||
"suggestions": rdjson_suggestions(fix.edits(), &source_code),
|
||||
})
|
||||
} else {
|
||||
json!({
|
||||
"message": message.kind.body,
|
||||
"message": message.body(),
|
||||
"location": {
|
||||
"path": message.filename(),
|
||||
"range": rdjson_range(&start_location, &end_location),
|
||||
},
|
||||
"code": {
|
||||
"value": message.kind.rule().noqa_code().to_string(),
|
||||
"url": message.kind.rule().url(),
|
||||
"value": message.rule().map(|rule| rule.noqa_code().to_string()),
|
||||
"url": message.rule().and_then(|rule| rule.url()),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -125,7 +124,9 @@ fn rdjson_range(start: &SourceLocation, end: &SourceLocation) -> Value {
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_messages, create_syntax_error_messages,
|
||||
};
|
||||
use crate::message::RdjsonEmitter;
|
||||
|
||||
#[test]
|
||||
@@ -135,4 +136,12 @@ mod tests {
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = RdjsonEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,16 @@ use std::io::Write;
|
||||
use anyhow::Result;
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde_json::json;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff_source_file::OneIndexed;
|
||||
|
||||
use crate::codes::Rule;
|
||||
use crate::fs::normalize_path;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::{AsRule, Linter, RuleNamespace};
|
||||
use crate::registry::{Linter, RuleNamespace};
|
||||
use crate::VERSION;
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
pub struct SarifEmitter;
|
||||
|
||||
impl Emitter for SarifEmitter {
|
||||
@@ -103,7 +102,7 @@ impl Serialize for SarifRule<'_> {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SarifResult {
|
||||
rule: Rule,
|
||||
rule: Option<Rule>,
|
||||
level: String,
|
||||
message: String,
|
||||
uri: String,
|
||||
@@ -120,9 +119,9 @@ impl SarifResult {
|
||||
let end_location = message.compute_end_location();
|
||||
let path = normalize_path(message.filename());
|
||||
Ok(Self {
|
||||
rule: message.kind.rule(),
|
||||
rule: message.rule(),
|
||||
level: "error".to_string(),
|
||||
message: message.kind.name.clone(),
|
||||
message: message.name().to_string(),
|
||||
uri: url::Url::from_file_path(&path)
|
||||
.map_err(|()| anyhow::anyhow!("Failed to convert path to URL: {}", path.display()))?
|
||||
.to_string(),
|
||||
@@ -140,9 +139,9 @@ impl SarifResult {
|
||||
let end_location = message.compute_end_location();
|
||||
let path = normalize_path(message.filename());
|
||||
Ok(Self {
|
||||
rule: message.kind.rule(),
|
||||
rule: message.rule(),
|
||||
level: "error".to_string(),
|
||||
message: message.kind.name.clone(),
|
||||
message: message.name().to_string(),
|
||||
uri: path.display().to_string(),
|
||||
start_line: start_location.row,
|
||||
start_column: start_location.column,
|
||||
@@ -175,7 +174,7 @@ impl Serialize for SarifResult {
|
||||
}
|
||||
}
|
||||
}],
|
||||
"ruleId": self.rule.noqa_code().to_string(),
|
||||
"ruleId": self.rule.map(|rule| rule.noqa_code().to_string()),
|
||||
})
|
||||
.serialize(serializer)
|
||||
}
|
||||
@@ -184,7 +183,9 @@ impl Serialize for SarifResult {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_messages, create_syntax_error_messages,
|
||||
};
|
||||
use crate::message::SarifEmitter;
|
||||
|
||||
fn get_output() -> String {
|
||||
@@ -198,6 +199,13 @@ mod tests {
|
||||
serde_json::from_str::<serde_json::Value>(&content).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_syntax_error_json() {
|
||||
let mut emitter = SarifEmitter {};
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
serde_json::from_str::<serde_json::Value>(&content).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_results() {
|
||||
let content = get_output();
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/azure.rs
|
||||
expression: content
|
||||
---
|
||||
##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=1;columnnumber=15;]SyntaxError: Expected one or more symbol names after import
|
||||
##vso[task.logissue type=error;sourcepath=syntax_errors.py;linenumber=3;columnnumber=12;]SyntaxError: Expected ')', found newline
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/github.rs
|
||||
expression: content
|
||||
---
|
||||
::error title=Ruff,file=syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after import
|
||||
::error title=Ruff,file=syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: SyntaxError: Expected ')', found newline
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/gitlab.rs
|
||||
expression: redact_fingerprint(&content)
|
||||
---
|
||||
[
|
||||
{
|
||||
"description": "SyntaxError: Expected one or more symbol names after import",
|
||||
"fingerprint": "<redacted>",
|
||||
"location": {
|
||||
"lines": {
|
||||
"begin": 1,
|
||||
"end": 2
|
||||
},
|
||||
"path": "syntax_errors.py"
|
||||
},
|
||||
"severity": "major"
|
||||
},
|
||||
{
|
||||
"description": "SyntaxError: Expected ')', found newline",
|
||||
"fingerprint": "<redacted>",
|
||||
"location": {
|
||||
"lines": {
|
||||
"begin": 3,
|
||||
"end": 4
|
||||
},
|
||||
"path": "syntax_errors.py"
|
||||
},
|
||||
"severity": "major"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/grouped.rs
|
||||
expression: content
|
||||
---
|
||||
syntax_errors.py:
|
||||
1:15 SyntaxError: Expected one or more symbol names after import
|
||||
3:12 SyntaxError: Expected ')', found newline
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/json.rs
|
||||
expression: content
|
||||
---
|
||||
[
|
||||
{
|
||||
"cell": null,
|
||||
"code": null,
|
||||
"end_location": {
|
||||
"column": 1,
|
||||
"row": 2
|
||||
},
|
||||
"filename": "syntax_errors.py",
|
||||
"fix": null,
|
||||
"location": {
|
||||
"column": 15,
|
||||
"row": 1
|
||||
},
|
||||
"message": "SyntaxError: Expected one or more symbol names after import",
|
||||
"noqa_row": null,
|
||||
"url": null
|
||||
},
|
||||
{
|
||||
"cell": null,
|
||||
"code": null,
|
||||
"end_location": {
|
||||
"column": 1,
|
||||
"row": 4
|
||||
},
|
||||
"filename": "syntax_errors.py",
|
||||
"fix": null,
|
||||
"location": {
|
||||
"column": 12,
|
||||
"row": 3
|
||||
},
|
||||
"message": "SyntaxError: Expected ')', found newline",
|
||||
"noqa_row": null,
|
||||
"url": null
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/json_lines.rs
|
||||
expression: content
|
||||
---
|
||||
{"cell":null,"code":null,"end_location":{"column":1,"row":2},"filename":"syntax_errors.py","fix":null,"location":{"column":15,"row":1},"message":"SyntaxError: Expected one or more symbol names after import","noqa_row":null,"url":null}
|
||||
{"cell":null,"code":null,"end_location":{"column":1,"row":4},"filename":"syntax_errors.py","fix":null,"location":{"column":12,"row":3},"message":"SyntaxError: Expected ')', found newline","noqa_row":null,"url":null}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/junit.rs
|
||||
expression: content
|
||||
---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites name="ruff" tests="2" failures="2" errors="0">
|
||||
<testsuite name="syntax_errors.py" tests="2" disabled="0" errors="0" failures="2" package="org.ruff">
|
||||
<testcase name="org.ruff" classname="syntax_errors" line="1" column="15">
|
||||
<failure message="SyntaxError: Expected one or more symbol names after import">line 1, col 15, SyntaxError: Expected one or more symbol names after import</failure>
|
||||
</testcase>
|
||||
<testcase name="org.ruff" classname="syntax_errors" line="3" column="12">
|
||||
<failure message="SyntaxError: Expected ')', found newline">line 3, col 12, SyntaxError: Expected ')', found newline</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/pylint.rs
|
||||
expression: content
|
||||
---
|
||||
syntax_errors.py:1: SyntaxError: Expected one or more symbol names after import
|
||||
syntax_errors.py:3: SyntaxError: Expected ')', found newline
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/rdjson.rs
|
||||
expression: content
|
||||
---
|
||||
{
|
||||
"diagnostics": [
|
||||
{
|
||||
"code": {
|
||||
"url": null,
|
||||
"value": null
|
||||
},
|
||||
"location": {
|
||||
"path": "syntax_errors.py",
|
||||
"range": {
|
||||
"end": {
|
||||
"column": 1,
|
||||
"line": 2
|
||||
},
|
||||
"start": {
|
||||
"column": 15,
|
||||
"line": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"message": "SyntaxError: Expected one or more symbol names after import"
|
||||
},
|
||||
{
|
||||
"code": {
|
||||
"url": null,
|
||||
"value": null
|
||||
},
|
||||
"location": {
|
||||
"path": "syntax_errors.py",
|
||||
"range": {
|
||||
"end": {
|
||||
"column": 1,
|
||||
"line": 4
|
||||
},
|
||||
"start": {
|
||||
"column": 12,
|
||||
"line": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
"message": "SyntaxError: Expected ')', found newline"
|
||||
}
|
||||
],
|
||||
"severity": "warning",
|
||||
"source": {
|
||||
"name": "ruff",
|
||||
"url": "https://docs.astral.sh/ruff"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/text.rs
|
||||
expression: content
|
||||
---
|
||||
syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after import
|
||||
|
|
||||
1 | from os import
|
||||
| ^
|
||||
2 |
|
||||
3 | if call(foo
|
||||
4 | def bar():
|
||||
|
|
||||
|
||||
syntax_errors.py:3:12: SyntaxError: Expected ')', found newline
|
||||
|
|
||||
1 | from os import
|
||||
2 |
|
||||
3 | if call(foo
|
||||
| ^
|
||||
4 | def bar():
|
||||
5 | pass
|
||||
|
|
||||
@@ -15,7 +15,6 @@ use crate::fs::relativize_path;
|
||||
use crate::line_width::{IndentWidth, LineWidthBuilder};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
use crate::text_helpers::ShowNonprinting;
|
||||
|
||||
@@ -146,28 +145,33 @@ pub(super) struct RuleCodeAndBody<'a> {
|
||||
|
||||
impl Display for RuleCodeAndBody<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let kind = &self.message.kind;
|
||||
if self.show_fix_status {
|
||||
if let Some(fix) = self.message.fix.as_ref() {
|
||||
if let Some(fix) = self.message.fix() {
|
||||
// Do not display an indicator for unapplicable fixes
|
||||
if fix.applies(self.unsafe_fixes.required_applicability()) {
|
||||
if let Some(rule) = self.message.rule() {
|
||||
write!(f, "{} ", rule.noqa_code().to_string().red().bold())?;
|
||||
}
|
||||
return write!(
|
||||
f,
|
||||
"{code} {fix}{body}",
|
||||
code = kind.rule().noqa_code().to_string().red().bold(),
|
||||
"{fix}{body}",
|
||||
fix = format_args!("[{}] ", "*".cyan()),
|
||||
body = kind.body,
|
||||
body = self.message.body(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{code} {body}",
|
||||
code = kind.rule().noqa_code().to_string().red().bold(),
|
||||
body = kind.body,
|
||||
)
|
||||
if let Some(rule) = self.message.rule() {
|
||||
write!(
|
||||
f,
|
||||
"{code} {body}",
|
||||
code = rule.noqa_code().to_string().red().bold(),
|
||||
body = self.message.body(),
|
||||
)
|
||||
} else {
|
||||
f.write_str(self.message.body())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,11 +182,7 @@ pub(super) struct MessageCodeFrame<'a> {
|
||||
|
||||
impl Display for MessageCodeFrame<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let Message {
|
||||
kind, file, range, ..
|
||||
} = self.message;
|
||||
|
||||
let suggestion = kind.suggestion.as_deref();
|
||||
let suggestion = self.message.suggestion();
|
||||
let footer = if suggestion.is_some() {
|
||||
vec![Annotation {
|
||||
id: None,
|
||||
@@ -193,9 +193,9 @@ impl Display for MessageCodeFrame<'_> {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let source_code = file.to_source_code();
|
||||
let source_code = self.message.source_file().to_source_code();
|
||||
|
||||
let content_start_index = source_code.line_index(range.start());
|
||||
let content_start_index = source_code.line_index(self.message.start());
|
||||
let mut start_index = content_start_index.saturating_sub(2);
|
||||
|
||||
// If we're working with a Jupyter Notebook, skip the lines which are
|
||||
@@ -218,7 +218,7 @@ impl Display for MessageCodeFrame<'_> {
|
||||
start_index = start_index.saturating_add(1);
|
||||
}
|
||||
|
||||
let content_end_index = source_code.line_index(range.end());
|
||||
let content_end_index = source_code.line_index(self.message.end());
|
||||
let mut end_index = content_end_index
|
||||
.saturating_add(2)
|
||||
.min(OneIndexed::from_zero_indexed(source_code.line_count()));
|
||||
@@ -249,7 +249,7 @@ impl Display for MessageCodeFrame<'_> {
|
||||
|
||||
let source = replace_whitespace(
|
||||
source_code.slice(TextRange::new(start_offset, end_offset)),
|
||||
range - start_offset,
|
||||
self.message.range() - start_offset,
|
||||
);
|
||||
|
||||
let source_text = source.text.show_nonprinting();
|
||||
@@ -260,7 +260,10 @@ impl Display for MessageCodeFrame<'_> {
|
||||
|
||||
let char_length = source.text[source.annotation_range].chars().count();
|
||||
|
||||
let label = kind.rule().noqa_code().to_string();
|
||||
let label = self
|
||||
.message
|
||||
.rule()
|
||||
.map_or_else(String::new, |rule| rule.noqa_code().to_string());
|
||||
|
||||
let snippet = Snippet {
|
||||
title: None,
|
||||
@@ -356,7 +359,7 @@ mod tests {
|
||||
|
||||
use crate::message::tests::{
|
||||
capture_emitter_notebook_output, capture_emitter_output, create_messages,
|
||||
create_notebook_messages,
|
||||
create_notebook_messages, create_syntax_error_messages,
|
||||
};
|
||||
use crate::message::TextEmitter;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
@@ -401,4 +404,12 @@ mod tests {
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = TextEmitter::default().with_show_source(true);
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1063,7 +1063,7 @@ mod tests {
|
||||
|
||||
use crate::generate_noqa_edits;
|
||||
use crate::noqa::{add_noqa_inner, Directive, NoqaMapping, ParsedFileExemption};
|
||||
use crate::rules::pycodestyle::rules::AmbiguousVariableName;
|
||||
use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon};
|
||||
use crate::rules::pyflakes::rules::UnusedVariable;
|
||||
use crate::rules::pyupgrade::rules::PrintfStringFormatting;
|
||||
|
||||
@@ -1380,4 +1380,36 @@ print(
|
||||
))]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_error() {
|
||||
let path = Path::new("/tmp/foo.txt");
|
||||
let source = "\
|
||||
foo;
|
||||
bar =
|
||||
";
|
||||
let diagnostics = [Diagnostic::new(
|
||||
UselessSemicolon,
|
||||
TextRange::new(4.into(), 5.into()),
|
||||
)];
|
||||
let noqa_line_for = NoqaMapping::default();
|
||||
let comment_ranges = CommentRanges::default();
|
||||
let edits = generate_noqa_edits(
|
||||
path,
|
||||
&diagnostics,
|
||||
&Locator::new(source),
|
||||
&comment_ranges,
|
||||
&[],
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
assert_eq!(
|
||||
edits,
|
||||
vec![Some(Edit::replacement(
|
||||
" # noqa: E703\n".to_string(),
|
||||
4.into(),
|
||||
5.into()
|
||||
))]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +64,6 @@ pub enum Linter {
|
||||
/// [flake8-async](https://pypi.org/project/flake8-async/)
|
||||
#[prefix = "ASYNC"]
|
||||
Flake8Async,
|
||||
/// [flake8-trio](https://pypi.org/project/flake8-trio/)
|
||||
#[prefix = "TRIO"]
|
||||
Flake8Trio,
|
||||
/// [flake8-bandit](https://pypi.org/project/flake8-bandit/)
|
||||
#[prefix = "S"]
|
||||
Flake8Bandit,
|
||||
|
||||
@@ -103,6 +103,18 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||
("TRY200", "B904"),
|
||||
("PGH001", "S307"),
|
||||
("PGH002", "G010"),
|
||||
// flake8-trio and flake8-async merged with name flake8-async
|
||||
("TRIO", "ASYNC1"),
|
||||
("TRIO1", "ASYNC1"),
|
||||
("TRIO10", "ASYNC10"),
|
||||
("TRIO100", "ASYNC100"),
|
||||
("TRIO105", "ASYNC105"),
|
||||
("TRIO109", "ASYNC109"),
|
||||
("TRIO11", "ASYNC11"),
|
||||
("TRIO110", "ASYNC110"),
|
||||
("TRIO115", "ASYNC115"),
|
||||
// Removed in v0.5
|
||||
("PLR1701", "SIM101"),
|
||||
// Test redirect by exact code
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
("RUF940", "RUF950"),
|
||||
|
||||
@@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use crate::codes::RuleCodePrefix;
|
||||
use crate::codes::RuleIter;
|
||||
use crate::codes::{RuleCodePrefix, RuleGroup};
|
||||
use crate::registry::{Linter, Rule, RuleNamespace};
|
||||
use crate::rule_redirects::get_redirect;
|
||||
use crate::settings::types::PreviewMode;
|
||||
@@ -15,9 +15,6 @@ use crate::settings::types::PreviewMode;
|
||||
pub enum RuleSelector {
|
||||
/// Select all rules (includes rules in preview if enabled)
|
||||
All,
|
||||
/// Legacy category to select all rules in the "nursery" which predated preview mode
|
||||
#[deprecated(note = "The nursery was replaced with 'preview mode' which has no selector")]
|
||||
Nursery,
|
||||
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
|
||||
/// via a single selector.
|
||||
C,
|
||||
@@ -65,8 +62,6 @@ impl FromStr for RuleSelector {
|
||||
// **Changes should be reflected in `parse_no_redirect` as well**
|
||||
match s {
|
||||
"ALL" => Ok(Self::All),
|
||||
#[allow(deprecated)]
|
||||
"NURSERY" => Ok(Self::Nursery),
|
||||
"C" => Ok(Self::C),
|
||||
"T" => Ok(Self::T),
|
||||
_ => {
|
||||
@@ -130,8 +125,6 @@ impl RuleSelector {
|
||||
pub fn prefix_and_code(&self) -> (&'static str, &'static str) {
|
||||
match self {
|
||||
RuleSelector::All => ("", "ALL"),
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => ("", "NURSERY"),
|
||||
RuleSelector::C => ("", "C"),
|
||||
RuleSelector::T => ("", "T"),
|
||||
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
|
||||
@@ -191,10 +184,6 @@ impl RuleSelector {
|
||||
match self {
|
||||
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
|
||||
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => {
|
||||
RuleSelectorIter::Nursery(Rule::iter().filter(Rule::is_nursery))
|
||||
}
|
||||
RuleSelector::C => RuleSelectorIter::Chain(
|
||||
Linter::Flake8Comprehensions
|
||||
.rules()
|
||||
@@ -216,19 +205,23 @@ impl RuleSelector {
|
||||
pub fn rules<'a>(&'a self, preview: &PreviewOptions) -> impl Iterator<Item = Rule> + 'a {
|
||||
let preview_enabled = preview.mode.is_enabled();
|
||||
let preview_require_explicit = preview.require_explicit;
|
||||
#[allow(deprecated)]
|
||||
|
||||
self.all_rules().filter(move |rule| {
|
||||
// Always include stable rules
|
||||
rule.is_stable()
|
||||
// Backwards compatibility allows selection of nursery rules by exact code or dedicated group
|
||||
|| ((self.is_exact() || matches!(self, RuleSelector::Nursery { .. })) && rule.is_nursery())
|
||||
// Enabling preview includes all preview or nursery rules unless explicit selection
|
||||
// is turned on
|
||||
|| ((rule.is_preview() || rule.is_nursery()) && preview_enabled && (self.is_exact() || !preview_require_explicit))
|
||||
// Deprecated rules are excluded in preview mode unless explicitly selected
|
||||
|| (rule.is_deprecated() && (!preview_enabled || self.is_exact()))
|
||||
// Removed rules are included if explicitly selected but will error downstream
|
||||
|| (rule.is_removed() && self.is_exact())
|
||||
match rule.group() {
|
||||
// Always include stable rules
|
||||
RuleGroup::Stable => true,
|
||||
// Enabling preview includes all preview rules unless explicit selection is turned on
|
||||
RuleGroup::Preview => {
|
||||
preview_enabled && (self.is_exact() || !preview_require_explicit)
|
||||
}
|
||||
// Deprecated rules are excluded in preview mode and with 'All' option unless explicitly selected
|
||||
RuleGroup::Deprecated => {
|
||||
(!preview_enabled || self.is_exact())
|
||||
&& !matches!(self, RuleSelector::All { .. })
|
||||
}
|
||||
// Removed rules are included if explicitly selected but will error downstream
|
||||
RuleGroup::Removed => self.is_exact(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -240,7 +233,6 @@ impl RuleSelector {
|
||||
|
||||
pub enum RuleSelectorIter {
|
||||
All(RuleIter),
|
||||
Nursery(std::iter::Filter<RuleIter, fn(&Rule) -> bool>),
|
||||
Chain(std::iter::Chain<std::vec::IntoIter<Rule>, std::vec::IntoIter<Rule>>),
|
||||
Vec(std::vec::IntoIter<Rule>),
|
||||
}
|
||||
@@ -251,7 +243,6 @@ impl Iterator for RuleSelectorIter {
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
RuleSelectorIter::All(iter) => iter.next(),
|
||||
RuleSelectorIter::Nursery(iter) => iter.next(),
|
||||
RuleSelectorIter::Chain(iter) => iter.next(),
|
||||
RuleSelectorIter::Vec(iter) => iter.next(),
|
||||
}
|
||||
@@ -288,7 +279,7 @@ mod schema {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(
|
||||
[
|
||||
// Include the non-standard "ALL" and "NURSERY" selectors.
|
||||
// Include the non-standard "ALL" selectors.
|
||||
"ALL".to_string(),
|
||||
// Include the legacy "C" and "T" selectors.
|
||||
"C".to_string(),
|
||||
@@ -345,8 +336,6 @@ impl RuleSelector {
|
||||
pub fn specificity(&self) -> Specificity {
|
||||
match self {
|
||||
RuleSelector::All => Specificity::All,
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => Specificity::All,
|
||||
RuleSelector::T => Specificity::LinterGroup,
|
||||
RuleSelector::C => Specificity::LinterGroup,
|
||||
RuleSelector::Linter(..) => Specificity::Linter,
|
||||
@@ -369,8 +358,6 @@ impl RuleSelector {
|
||||
// **Changes should be reflected in `from_str` as well**
|
||||
match s {
|
||||
"ALL" => Ok(Self::All),
|
||||
#[allow(deprecated)]
|
||||
"NURSERY" => Ok(Self::Nursery),
|
||||
"C" => Ok(Self::C),
|
||||
"T" => Ok(Self::T),
|
||||
_ => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Rules from [flake8-async](https://pypi.org/project/flake8-async/).
|
||||
mod helpers;
|
||||
pub(crate) mod rules;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -13,10 +14,18 @@ mod tests {
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
|
||||
#[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC100.py"))]
|
||||
#[test_case(Rule::OpenSleepOrSubprocessInAsyncFunction, Path::new("ASYNC101.py"))]
|
||||
#[test_case(Rule::BlockingOsCallInAsyncFunction, Path::new("ASYNC102.py"))]
|
||||
#[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("ASYNC100.py"))]
|
||||
#[test_case(Rule::TrioSyncCall, Path::new("ASYNC105.py"))]
|
||||
#[test_case(Rule::TrioAsyncFunctionWithTimeout, Path::new("ASYNC109.py"))]
|
||||
#[test_case(Rule::TrioUnneededSleep, Path::new("ASYNC110.py"))]
|
||||
#[test_case(Rule::TrioZeroSleepCall, Path::new("ASYNC115.py"))]
|
||||
#[test_case(Rule::SleepForeverCall, Path::new("ASYNC116.py"))]
|
||||
#[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))]
|
||||
#[test_case(Rule::CreateSubprocessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::RunProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::WaitForProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::BlockingOpenCallInAsyncFunction, Path::new("ASYNC230.py"))]
|
||||
#[test_case(Rule::BlockingSleepInAsyncFunction, Path::new("ASYNC251.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -40,7 +40,7 @@ impl Violation for TrioAsyncFunctionWithTimeout {
|
||||
}
|
||||
}
|
||||
|
||||
/// TRIO109
|
||||
/// ASYNC109
|
||||
pub(crate) fn async_function_with_timeout(
|
||||
checker: &mut Checker,
|
||||
function_def: &ast::StmtFunctionDef,
|
||||
@@ -45,6 +45,7 @@ fn is_blocking_http_call(qualified_name: &QualifiedName) -> bool {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["urllib", "request", "urlopen"]
|
||||
| ["urllib3", "request"]
|
||||
| [
|
||||
"httpx" | "requests",
|
||||
"get"
|
||||
@@ -60,7 +61,7 @@ fn is_blocking_http_call(qualified_name: &QualifiedName) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// ASYNC100
|
||||
/// ASYNC210
|
||||
pub(crate) fn blocking_http_call(checker: &mut Checker, call: &ExprCall) {
|
||||
if checker.semantic().in_async_context() {
|
||||
if checker
|
||||
|
||||
@@ -7,8 +7,7 @@ use ruff_text_size::Ranged;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that async functions do not contain calls to `open`, `time.sleep`,
|
||||
/// or `subprocess` methods.
|
||||
/// Checks that async functions do not open files with blocking methods like `open`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Blocking an async function via a blocking call will block the entire
|
||||
@@ -21,61 +20,53 @@ use crate::checkers::ast::Checker;
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// async def foo():
|
||||
/// time.sleep(1000)
|
||||
/// with open("bar.txt") as f:
|
||||
/// contents = f.read()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import anyio
|
||||
///
|
||||
///
|
||||
/// async def foo():
|
||||
/// await asyncio.sleep(1000)
|
||||
/// async with await anyio.open_file("bar.txt") as f:
|
||||
/// contents = await f.read()
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct OpenSleepOrSubprocessInAsyncFunction;
|
||||
pub struct BlockingOpenCallInAsyncFunction;
|
||||
|
||||
impl Violation for OpenSleepOrSubprocessInAsyncFunction {
|
||||
impl Violation for BlockingOpenCallInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Async functions should not call `open`, `time.sleep`, or `subprocess` methods")
|
||||
format!("Async functions should not open files with blocking methods like `open`")
|
||||
}
|
||||
}
|
||||
|
||||
/// ASYNC101
|
||||
pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
/// ASYNC230
|
||||
pub(crate) fn blocking_open_call(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if !checker.semantic().in_async_context() {
|
||||
return;
|
||||
}
|
||||
|
||||
if is_open_sleep_or_subprocess_call(&call.func, checker.semantic())
|
||||
if is_open_call(&call.func, checker.semantic())
|
||||
|| is_open_call_from_pathlib(call.func.as_ref(), checker.semantic())
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
OpenSleepOrSubprocessInAsyncFunction,
|
||||
BlockingOpenCallInAsyncFunction,
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the expression resolves to a blocking call, like `time.sleep` or
|
||||
/// `subprocess.run`.
|
||||
fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||
/// Returns `true` if the expression resolves to a blocking open call, like `open` or `Path().open()`.
|
||||
fn is_open_call(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["" | "builtins", "open"]
|
||||
| ["time", "sleep"]
|
||||
| [
|
||||
"subprocess",
|
||||
"run"
|
||||
| "Popen"
|
||||
| "call"
|
||||
| "check_call"
|
||||
| "check_output"
|
||||
| "getoutput"
|
||||
| "getstatusoutput"
|
||||
]
|
||||
| ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"]
|
||||
["" | "io", "open"] | ["io", "open_code"]
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that async functions do not contain calls to blocking synchronous
|
||||
/// process calls via the `os` module.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Blocking an async function via a blocking call will block the entire
|
||||
/// event loop, preventing it from executing other tasks while waiting for the
|
||||
/// call to complete, negating the benefits of asynchronous programming.
|
||||
///
|
||||
/// Instead of making a blocking call, use an equivalent asynchronous library
|
||||
/// or function.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// async def foo():
|
||||
/// os.popen()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo():
|
||||
/// os.popen()
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct BlockingOsCallInAsyncFunction;
|
||||
|
||||
impl Violation for BlockingOsCallInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Async functions should not call synchronous `os` methods")
|
||||
}
|
||||
}
|
||||
|
||||
/// ASYNC102
|
||||
pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) {
|
||||
if checker.semantic().seen_module(Modules::OS) {
|
||||
if checker.semantic().in_async_context() {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(call.func.as_ref())
|
||||
.as_ref()
|
||||
.is_some_and(is_unsafe_os_method)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BlockingOsCallInAsyncFunction,
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_unsafe_os_method(qualified_name: &QualifiedName) -> bool {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
[
|
||||
"os",
|
||||
"popen"
|
||||
| "posix_spawn"
|
||||
| "posix_spawnp"
|
||||
| "spawnl"
|
||||
| "spawnle"
|
||||
| "spawnlp"
|
||||
| "spawnlpe"
|
||||
| "spawnv"
|
||||
| "spawnve"
|
||||
| "spawnvp"
|
||||
| "spawnvpe"
|
||||
| "system"
|
||||
]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::analyze::typing::find_assigned_value;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that async functions do not create subprocesses with blocking methods.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Blocking an async function via a blocking call will block the entire
|
||||
/// event loop, preventing it from executing other tasks while waiting for the
|
||||
/// call to complete, negating the benefits of asynchronous programming.
|
||||
///
|
||||
/// Instead of making a blocking call, use an equivalent asynchronous library
|
||||
/// or function.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// async def foo():
|
||||
/// os.popen(cmd)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// async def foo():
|
||||
/// asyncio.create_subprocess_shell(cmd)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct CreateSubprocessInAsyncFunction;
|
||||
|
||||
impl Violation for CreateSubprocessInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Async functions should not create subprocesses with blocking methods")
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that async functions do not run processes with blocking methods.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Blocking an async function via a blocking call will block the entire
|
||||
/// event loop, preventing it from executing other tasks while waiting for the
|
||||
/// call to complete, negating the benefits of asynchronous programming.
|
||||
///
|
||||
/// Instead of making a blocking call, use an equivalent asynchronous library
|
||||
/// or function.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// async def foo():
|
||||
/// subprocess.run(cmd)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// async def foo():
|
||||
/// asyncio.create_subprocess_shell(cmd)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct RunProcessInAsyncFunction;
|
||||
|
||||
impl Violation for RunProcessInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Async functions should not run processes with blocking methods")
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that async functions do not wait on processes with blocking methods.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Blocking an async function via a blocking call will block the entire
|
||||
/// event loop, preventing it from executing other tasks while waiting for the
|
||||
/// call to complete, negating the benefits of asynchronous programming.
|
||||
///
|
||||
/// Instead of making a blocking call, use an equivalent asynchronous library
|
||||
/// or function.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// async def foo():
|
||||
/// os.waitpid(0)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def wait_for_process():
|
||||
/// os.waitpid(0)
|
||||
///
|
||||
///
|
||||
/// async def foo():
|
||||
/// await asyncio.loop.run_in_executor(None, wait_for_process)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct WaitForProcessInAsyncFunction;
|
||||
|
||||
impl Violation for WaitForProcessInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Async functions should not wait on processes with blocking methods")
|
||||
}
|
||||
}
|
||||
|
||||
/// ASYNC220, ASYNC221, ASYNC222
|
||||
pub(crate) fn blocking_process_invocation(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if !checker.semantic().in_async_context() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(diagnostic_kind) =
|
||||
checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(call.func.as_ref())
|
||||
.and_then(|qualified_name| match qualified_name.segments() {
|
||||
["subprocess", "Popen"] | ["os", "popen"] => {
|
||||
Some(CreateSubprocessInAsyncFunction.into())
|
||||
}
|
||||
["os", "system" | "posix_spawn" | "posix_spawnp"]
|
||||
| ["subprocess", "run" | "call" | "check_call" | "check_output" | "getoutput"
|
||||
| "getstatusoutput"] => Some(RunProcessInAsyncFunction.into()),
|
||||
["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"] => {
|
||||
Some(WaitForProcessInAsyncFunction.into())
|
||||
}
|
||||
["os", "spawnl" | "spawnle" | "spawnlp" | "spawnlpe" | "spawnv" | "spawnve"
|
||||
| "spawnvp" | "spawnvpe"] => {
|
||||
if is_p_wait(call, checker.semantic()) {
|
||||
Some(RunProcessInAsyncFunction.into())
|
||||
} else {
|
||||
Some(CreateSubprocessInAsyncFunction.into())
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let diagnostic = Diagnostic::new::<DiagnosticKind>(diagnostic_kind, call.func.range());
|
||||
if checker.enabled(diagnostic.kind.rule()) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_p_wait(call: &ast::ExprCall, semantic: &SemanticModel) -> bool {
|
||||
let Some(arg) = call.arguments.find_argument("mode", 0) else {
|
||||
return true;
|
||||
};
|
||||
|
||||
if let Some(qualified_name) = semantic.resolve_qualified_name(arg) {
|
||||
return matches!(qualified_name.segments(), ["os", "P_WAIT"]);
|
||||
} else if let Expr::Name(ast::ExprName { id, .. }) = arg {
|
||||
let Some(value) = find_assigned_value(id, semantic) else {
|
||||
return false;
|
||||
};
|
||||
if let Some(qualified_name) = semantic.resolve_qualified_name(value) {
|
||||
return matches!(qualified_name.segments(), ["os", "P_WAIT"]);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that async functions do not call `time.sleep`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Blocking an async function via a `time.sleep` call will block the entire
|
||||
/// event loop, preventing it from executing other tasks while waiting for the
|
||||
/// `time.sleep`, negating the benefits of asynchronous programming.
|
||||
///
|
||||
/// Instead of `time.sleep`, use `asyncio.sleep`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// async def fetch():
|
||||
/// time.sleep(1)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// async def fetch():
|
||||
/// await asyncio.sleep(1)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct BlockingSleepInAsyncFunction;
|
||||
|
||||
impl Violation for BlockingSleepInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Async functions should not call `time.sleep`")
|
||||
}
|
||||
}
|
||||
|
||||
fn is_blocking_sleep(qualified_name: &QualifiedName) -> bool {
|
||||
matches!(qualified_name.segments(), ["time", "sleep"])
|
||||
}
|
||||
|
||||
/// ASYNC251
|
||||
pub(crate) fn blocking_sleep(checker: &mut Checker, call: &ExprCall) {
|
||||
if checker.semantic().in_async_context() {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(call.func.as_ref())
|
||||
.as_ref()
|
||||
.is_some_and(is_blocking_sleep)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BlockingSleepInAsyncFunction,
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,21 @@
|
||||
pub(crate) use async_function_with_timeout::*;
|
||||
pub(crate) use blocking_http_call::*;
|
||||
pub(crate) use blocking_os_call::*;
|
||||
pub(crate) use open_sleep_or_subprocess_call::*;
|
||||
pub(crate) use blocking_open_call::*;
|
||||
pub(crate) use blocking_process_invocation::*;
|
||||
pub(crate) use blocking_sleep::*;
|
||||
pub(crate) use sleep_forever_call::*;
|
||||
pub(crate) use sync_call::*;
|
||||
pub(crate) use timeout_without_await::*;
|
||||
pub(crate) use unneeded_sleep::*;
|
||||
pub(crate) use zero_sleep_call::*;
|
||||
|
||||
mod async_function_with_timeout;
|
||||
mod blocking_http_call;
|
||||
mod blocking_os_call;
|
||||
mod open_sleep_or_subprocess_call;
|
||||
mod blocking_open_call;
|
||||
mod blocking_process_invocation;
|
||||
mod blocking_sleep;
|
||||
mod sleep_forever_call;
|
||||
mod sync_call;
|
||||
mod timeout_without_await;
|
||||
mod unneeded_sleep;
|
||||
mod zero_sleep_call;
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::pad;
|
||||
use crate::rules::flake8_trio::method_name::MethodName;
|
||||
use crate::rules::flake8_async::helpers::MethodName;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for calls to trio functions that are not immediately awaited.
|
||||
@@ -50,7 +50,7 @@ impl Violation for TrioSyncCall {
|
||||
}
|
||||
}
|
||||
|
||||
/// TRIO105
|
||||
/// ASYNC105
|
||||
pub(crate) fn sync_call(checker: &mut Checker, call: &ExprCall) {
|
||||
if !checker.semantic().seen_module(Modules::TRIO) {
|
||||
return;
|
||||
@@ -6,7 +6,7 @@ use ruff_python_ast::{StmtWith, WithItem};
|
||||
use ruff_python_semantic::Modules;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_trio::method_name::MethodName;
|
||||
use crate::rules::flake8_async::helpers::MethodName;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for trio functions that should contain await but don't.
|
||||
@@ -44,7 +44,7 @@ impl Violation for TrioTimeoutWithoutAwait {
|
||||
}
|
||||
}
|
||||
|
||||
/// TRIO100
|
||||
/// ASYNC100
|
||||
pub(crate) fn timeout_without_await(
|
||||
checker: &mut Checker,
|
||||
with_stmt: &StmtWith,
|
||||
@@ -41,7 +41,7 @@ impl Violation for TrioUnneededSleep {
|
||||
}
|
||||
}
|
||||
|
||||
/// TRIO110
|
||||
/// ASYNC110
|
||||
pub(crate) fn unneeded_sleep(checker: &mut Checker, while_stmt: &ast::StmtWhile) {
|
||||
if !checker.semantic().seen_module(Modules::TRIO) {
|
||||
return;
|
||||
@@ -45,7 +45,7 @@ impl AlwaysFixableViolation for TrioZeroSleepCall {
|
||||
}
|
||||
}
|
||||
|
||||
/// TRIO115
|
||||
/// ASYNC115
|
||||
pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) {
|
||||
if !checker.semantic().seen_module(Modules::TRIO) {
|
||||
return;
|
||||
@@ -1,39 +1,20 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
||||
---
|
||||
ASYNC100.py:7:5: ASYNC100 Async functions should not call blocking HTTP methods
|
||||
ASYNC100.py:5:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
||||
|
|
||||
6 | async def foo():
|
||||
7 | urllib.request.urlopen("http://example.com/foo/bar").read()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ ASYNC100
|
||||
4 | async def func():
|
||||
5 | with trio.fail_after():
|
||||
| _____^
|
||||
6 | | ...
|
||||
| |___________^ ASYNC100
|
||||
|
|
||||
|
||||
ASYNC100.py:11:5: ASYNC100 Async functions should not call blocking HTTP methods
|
||||
ASYNC100.py:15:5: ASYNC100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
||||
|
|
||||
10 | async def foo():
|
||||
11 | requests.get()
|
||||
| ^^^^^^^^^^^^ ASYNC100
|
||||
14 | async def func():
|
||||
15 | with trio.move_on_after():
|
||||
| _____^
|
||||
16 | | ...
|
||||
| |___________^ ASYNC100
|
||||
|
|
||||
|
||||
ASYNC100.py:15:5: ASYNC100 Async functions should not call blocking HTTP methods
|
||||
|
|
||||
14 | async def foo():
|
||||
15 | httpx.get()
|
||||
| ^^^^^^^^^ ASYNC100
|
||||
|
|
||||
|
||||
ASYNC100.py:19:5: ASYNC100 Async functions should not call blocking HTTP methods
|
||||
|
|
||||
18 | async def foo():
|
||||
19 | requests.post()
|
||||
| ^^^^^^^^^^^^^ ASYNC100
|
||||
|
|
||||
|
||||
ASYNC100.py:23:5: ASYNC100 Async functions should not call blocking HTTP methods
|
||||
|
|
||||
22 | async def foo():
|
||||
23 | httpx.post()
|
||||
| ^^^^^^^^^^ ASYNC100
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
||||
---
|
||||
ASYNC101.py:10:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
9 | async def func():
|
||||
10 | open("foo")
|
||||
| ^^^^ ASYNC101
|
||||
|
|
||||
|
||||
ASYNC101.py:14:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
13 | async def func():
|
||||
14 | time.sleep(1)
|
||||
| ^^^^^^^^^^ ASYNC101
|
||||
|
|
||||
|
||||
ASYNC101.py:18:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
17 | async def func():
|
||||
18 | subprocess.run("foo")
|
||||
| ^^^^^^^^^^^^^^ ASYNC101
|
||||
|
|
||||
|
||||
ASYNC101.py:22:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
21 | async def func():
|
||||
22 | subprocess.call("foo")
|
||||
| ^^^^^^^^^^^^^^^ ASYNC101
|
||||
|
|
||||
|
||||
ASYNC101.py:30:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
29 | async def func():
|
||||
30 | os.wait4(10)
|
||||
| ^^^^^^^^ ASYNC101
|
||||
|
|
||||
|
||||
ASYNC101.py:34:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
33 | async def func():
|
||||
34 | os.wait(12)
|
||||
| ^^^^^^^ ASYNC101
|
||||
|
|
||||
|
||||
ASYNC101.py:41:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
40 | async def func():
|
||||
41 | Path("foo").open() # ASYNC101
|
||||
| ^^^^^^^^^^^^^^^^ ASYNC101
|
||||
|
|
||||
|
||||
ASYNC101.py:46:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
44 | async def func():
|
||||
45 | p = Path("foo")
|
||||
46 | p.open() # ASYNC101
|
||||
| ^^^^^^ ASYNC101
|
||||
|
|
||||
|
||||
ASYNC101.py:50:10: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
49 | async def func():
|
||||
50 | with Path("foo").open() as f: # ASYNC101
|
||||
| ^^^^^^^^^^^^^^^^ ASYNC101
|
||||
51 | pass
|
||||
|
|
||||
|
||||
ASYNC101.py:58:9: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
57 | async def bar():
|
||||
58 | p.open() # ASYNC101
|
||||
| ^^^^^^ ASYNC101
|
||||
|
|
||||
|
||||
ASYNC101.py:64:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
||||
|
|
||||
62 | (p1, p2) = (Path("foo"), Path("bar"))
|
||||
63 |
|
||||
64 | p1.open() # ASYNC101
|
||||
| ^^^^^^^ ASYNC101
|
||||
|
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
||||
---
|
||||
ASYNC102.py:5:5: ASYNC102 Async functions should not call synchronous `os` methods
|
||||
|
|
||||
4 | async def foo():
|
||||
5 | os.popen()
|
||||
| ^^^^^^^^ ASYNC102
|
||||
|
|
||||
|
||||
ASYNC102.py:9:5: ASYNC102 Async functions should not call synchronous `os` methods
|
||||
|
|
||||
8 | async def foo():
|
||||
9 | os.spawnl()
|
||||
| ^^^^^^^^^ ASYNC102
|
||||
|
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
|
||||
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
||||
---
|
||||
TRIO105.py:30:5: TRIO105 [*] Call to `trio.aclose_forcefully` is not immediately awaited
|
||||
ASYNC105.py:30:5: ASYNC105 [*] Call to `trio.aclose_forcefully` is not immediately awaited
|
||||
|
|
||||
29 | # TRIO105
|
||||
29 | # ASYNC105
|
||||
30 | trio.aclose_forcefully(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
31 | trio.open_file(foo)
|
||||
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
|
|
||||
@@ -14,19 +14,19 @@ TRIO105.py:30:5: TRIO105 [*] Call to `trio.aclose_forcefully` is not immediately
|
||||
ℹ Unsafe fix
|
||||
27 27 | await trio.lowlevel.wait_writable(foo)
|
||||
28 28 |
|
||||
29 29 | # TRIO105
|
||||
29 29 | # ASYNC105
|
||||
30 |- trio.aclose_forcefully(foo)
|
||||
30 |+ await trio.aclose_forcefully(foo)
|
||||
31 31 | trio.open_file(foo)
|
||||
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
|
||||
TRIO105.py:31:5: TRIO105 [*] Call to `trio.open_file` is not immediately awaited
|
||||
ASYNC105.py:31:5: ASYNC105 [*] Call to `trio.open_file` is not immediately awaited
|
||||
|
|
||||
29 | # TRIO105
|
||||
29 | # ASYNC105
|
||||
30 | trio.aclose_forcefully(foo)
|
||||
31 | trio.open_file(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
|
|
||||
@@ -34,7 +34,7 @@ TRIO105.py:31:5: TRIO105 [*] Call to `trio.open_file` is not immediately awaited
|
||||
|
||||
ℹ Unsafe fix
|
||||
28 28 |
|
||||
29 29 | # TRIO105
|
||||
29 29 | # ASYNC105
|
||||
30 30 | trio.aclose_forcefully(foo)
|
||||
31 |- trio.open_file(foo)
|
||||
31 |+ await trio.open_file(foo)
|
||||
@@ -42,19 +42,19 @@ TRIO105.py:31:5: TRIO105 [*] Call to `trio.open_file` is not immediately awaited
|
||||
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 34 | trio.open_tcp_listeners(foo)
|
||||
|
||||
TRIO105.py:32:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_listeners` is not immediately awaited
|
||||
ASYNC105.py:32:5: ASYNC105 [*] Call to `trio.open_ssl_over_tcp_listeners` is not immediately awaited
|
||||
|
|
||||
30 | trio.aclose_forcefully(foo)
|
||||
31 | trio.open_file(foo)
|
||||
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 | trio.open_tcp_listeners(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Unsafe fix
|
||||
29 29 | # TRIO105
|
||||
29 29 | # ASYNC105
|
||||
30 30 | trio.aclose_forcefully(foo)
|
||||
31 31 | trio.open_file(foo)
|
||||
32 |- trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
@@ -63,12 +63,12 @@ TRIO105.py:32:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_listeners` is not i
|
||||
34 34 | trio.open_tcp_listeners(foo)
|
||||
35 35 | trio.open_tcp_stream(foo, foo)
|
||||
|
||||
TRIO105.py:33:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_stream` is not immediately awaited
|
||||
ASYNC105.py:33:5: ASYNC105 [*] Call to `trio.open_ssl_over_tcp_stream` is not immediately awaited
|
||||
|
|
||||
31 | trio.open_file(foo)
|
||||
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
34 | trio.open_tcp_listeners(foo)
|
||||
35 | trio.open_tcp_stream(foo, foo)
|
||||
|
|
||||
@@ -84,12 +84,12 @@ TRIO105.py:33:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_stream` is not imme
|
||||
35 35 | trio.open_tcp_stream(foo, foo)
|
||||
36 36 | trio.open_unix_socket(foo)
|
||||
|
||||
TRIO105.py:34:5: TRIO105 [*] Call to `trio.open_tcp_listeners` is not immediately awaited
|
||||
ASYNC105.py:34:5: ASYNC105 [*] Call to `trio.open_tcp_listeners` is not immediately awaited
|
||||
|
|
||||
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 | trio.open_tcp_listeners(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
35 | trio.open_tcp_stream(foo, foo)
|
||||
36 | trio.open_unix_socket(foo)
|
||||
|
|
||||
@@ -105,12 +105,12 @@ TRIO105.py:34:5: TRIO105 [*] Call to `trio.open_tcp_listeners` is not immediatel
|
||||
36 36 | trio.open_unix_socket(foo)
|
||||
37 37 | trio.run_process(foo)
|
||||
|
||||
TRIO105.py:35:5: TRIO105 [*] Call to `trio.open_tcp_stream` is not immediately awaited
|
||||
ASYNC105.py:35:5: ASYNC105 [*] Call to `trio.open_tcp_stream` is not immediately awaited
|
||||
|
|
||||
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 | trio.open_tcp_listeners(foo)
|
||||
35 | trio.open_tcp_stream(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
36 | trio.open_unix_socket(foo)
|
||||
37 | trio.run_process(foo)
|
||||
|
|
||||
@@ -126,12 +126,12 @@ TRIO105.py:35:5: TRIO105 [*] Call to `trio.open_tcp_stream` is not immediately a
|
||||
37 37 | trio.run_process(foo)
|
||||
38 38 | trio.serve_listeners(foo, foo)
|
||||
|
||||
TRIO105.py:36:5: TRIO105 [*] Call to `trio.open_unix_socket` is not immediately awaited
|
||||
ASYNC105.py:36:5: ASYNC105 [*] Call to `trio.open_unix_socket` is not immediately awaited
|
||||
|
|
||||
34 | trio.open_tcp_listeners(foo)
|
||||
35 | trio.open_tcp_stream(foo, foo)
|
||||
36 | trio.open_unix_socket(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
37 | trio.run_process(foo)
|
||||
38 | trio.serve_listeners(foo, foo)
|
||||
|
|
||||
@@ -147,12 +147,12 @@ TRIO105.py:36:5: TRIO105 [*] Call to `trio.open_unix_socket` is not immediately
|
||||
38 38 | trio.serve_listeners(foo, foo)
|
||||
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
|
||||
TRIO105.py:37:5: TRIO105 [*] Call to `trio.run_process` is not immediately awaited
|
||||
ASYNC105.py:37:5: ASYNC105 [*] Call to `trio.run_process` is not immediately awaited
|
||||
|
|
||||
35 | trio.open_tcp_stream(foo, foo)
|
||||
36 | trio.open_unix_socket(foo)
|
||||
37 | trio.run_process(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
38 | trio.serve_listeners(foo, foo)
|
||||
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
|
|
||||
@@ -168,12 +168,12 @@ TRIO105.py:37:5: TRIO105 [*] Call to `trio.run_process` is not immediately await
|
||||
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 40 | trio.serve_tcp(foo, foo)
|
||||
|
||||
TRIO105.py:38:5: TRIO105 [*] Call to `trio.serve_listeners` is not immediately awaited
|
||||
ASYNC105.py:38:5: ASYNC105 [*] Call to `trio.serve_listeners` is not immediately awaited
|
||||
|
|
||||
36 | trio.open_unix_socket(foo)
|
||||
37 | trio.run_process(foo)
|
||||
38 | trio.serve_listeners(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 | trio.serve_tcp(foo, foo)
|
||||
|
|
||||
@@ -189,12 +189,12 @@ TRIO105.py:38:5: TRIO105 [*] Call to `trio.serve_listeners` is not immediately a
|
||||
40 40 | trio.serve_tcp(foo, foo)
|
||||
41 41 | trio.sleep(foo)
|
||||
|
||||
TRIO105.py:39:5: TRIO105 [*] Call to `trio.serve_ssl_over_tcp` is not immediately awaited
|
||||
ASYNC105.py:39:5: ASYNC105 [*] Call to `trio.serve_ssl_over_tcp` is not immediately awaited
|
||||
|
|
||||
37 | trio.run_process(foo)
|
||||
38 | trio.serve_listeners(foo, foo)
|
||||
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
40 | trio.serve_tcp(foo, foo)
|
||||
41 | trio.sleep(foo)
|
||||
|
|
||||
@@ -210,12 +210,12 @@ TRIO105.py:39:5: TRIO105 [*] Call to `trio.serve_ssl_over_tcp` is not immediatel
|
||||
41 41 | trio.sleep(foo)
|
||||
42 42 | trio.sleep_forever()
|
||||
|
||||
TRIO105.py:40:5: TRIO105 [*] Call to `trio.serve_tcp` is not immediately awaited
|
||||
ASYNC105.py:40:5: ASYNC105 [*] Call to `trio.serve_tcp` is not immediately awaited
|
||||
|
|
||||
38 | trio.serve_listeners(foo, foo)
|
||||
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 | trio.serve_tcp(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
41 | trio.sleep(foo)
|
||||
42 | trio.sleep_forever()
|
||||
|
|
||||
@@ -231,12 +231,12 @@ TRIO105.py:40:5: TRIO105 [*] Call to `trio.serve_tcp` is not immediately awaited
|
||||
42 42 | trio.sleep_forever()
|
||||
43 43 | trio.sleep_until(foo)
|
||||
|
||||
TRIO105.py:41:5: TRIO105 [*] Call to `trio.sleep` is not immediately awaited
|
||||
ASYNC105.py:41:5: ASYNC105 [*] Call to `trio.sleep` is not immediately awaited
|
||||
|
|
||||
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 | trio.serve_tcp(foo, foo)
|
||||
41 | trio.sleep(foo)
|
||||
| ^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^ ASYNC105
|
||||
42 | trio.sleep_forever()
|
||||
43 | trio.sleep_until(foo)
|
||||
|
|
||||
@@ -252,12 +252,12 @@ TRIO105.py:41:5: TRIO105 [*] Call to `trio.sleep` is not immediately awaited
|
||||
43 43 | trio.sleep_until(foo)
|
||||
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
|
||||
TRIO105.py:42:5: TRIO105 [*] Call to `trio.sleep_forever` is not immediately awaited
|
||||
ASYNC105.py:42:5: ASYNC105 [*] Call to `trio.sleep_forever` is not immediately awaited
|
||||
|
|
||||
40 | trio.serve_tcp(foo, foo)
|
||||
41 | trio.sleep(foo)
|
||||
42 | trio.sleep_forever()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
43 | trio.sleep_until(foo)
|
||||
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
|
|
||||
@@ -273,12 +273,12 @@ TRIO105.py:42:5: TRIO105 [*] Call to `trio.sleep_forever` is not immediately awa
|
||||
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
45 45 | trio.lowlevel.checkpoint()
|
||||
|
||||
TRIO105.py:44:5: TRIO105 [*] Call to `trio.lowlevel.cancel_shielded_checkpoint` is not immediately awaited
|
||||
ASYNC105.py:44:5: ASYNC105 [*] Call to `trio.lowlevel.cancel_shielded_checkpoint` is not immediately awaited
|
||||
|
|
||||
42 | trio.sleep_forever()
|
||||
43 | trio.sleep_until(foo)
|
||||
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
45 | trio.lowlevel.checkpoint()
|
||||
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
|
|
||||
@@ -294,12 +294,12 @@ TRIO105.py:44:5: TRIO105 [*] Call to `trio.lowlevel.cancel_shielded_checkpoint`
|
||||
46 46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 47 | trio.lowlevel.open_process()
|
||||
|
||||
TRIO105.py:45:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint` is not immediately awaited
|
||||
ASYNC105.py:45:5: ASYNC105 [*] Call to `trio.lowlevel.checkpoint` is not immediately awaited
|
||||
|
|
||||
43 | trio.sleep_until(foo)
|
||||
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
45 | trio.lowlevel.checkpoint()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 | trio.lowlevel.open_process()
|
||||
|
|
||||
@@ -315,12 +315,12 @@ TRIO105.py:45:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint` is not immediate
|
||||
47 47 | trio.lowlevel.open_process()
|
||||
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
|
||||
TRIO105.py:46:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint_if_cancelled` is not immediately awaited
|
||||
ASYNC105.py:46:5: ASYNC105 [*] Call to `trio.lowlevel.checkpoint_if_cancelled` is not immediately awaited
|
||||
|
|
||||
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
45 | trio.lowlevel.checkpoint()
|
||||
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
47 | trio.lowlevel.open_process()
|
||||
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
|
|
||||
@@ -336,12 +336,12 @@ TRIO105.py:46:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint_if_cancelled` is
|
||||
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
|
||||
TRIO105.py:47:5: TRIO105 [*] Call to `trio.lowlevel.open_process` is not immediately awaited
|
||||
ASYNC105.py:47:5: ASYNC105 [*] Call to `trio.lowlevel.open_process` is not immediately awaited
|
||||
|
|
||||
45 | trio.lowlevel.checkpoint()
|
||||
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 | trio.lowlevel.open_process()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
|
|
||||
@@ -357,12 +357,12 @@ TRIO105.py:47:5: TRIO105 [*] Call to `trio.lowlevel.open_process` is not immedia
|
||||
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
|
||||
TRIO105.py:48:5: TRIO105 [*] Call to `trio.lowlevel.permanently_detach_coroutine_object` is not immediately awaited
|
||||
ASYNC105.py:48:5: ASYNC105 [*] Call to `trio.lowlevel.permanently_detach_coroutine_object` is not immediately awaited
|
||||
|
|
||||
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 | trio.lowlevel.open_process()
|
||||
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
|
|
||||
@@ -378,12 +378,12 @@ TRIO105.py:48:5: TRIO105 [*] Call to `trio.lowlevel.permanently_detach_coroutine
|
||||
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 51 | trio.lowlevel.wait_readable(foo)
|
||||
|
||||
TRIO105.py:49:5: TRIO105 [*] Call to `trio.lowlevel.reattach_detached_coroutine_object` is not immediately awaited
|
||||
ASYNC105.py:49:5: ASYNC105 [*] Call to `trio.lowlevel.reattach_detached_coroutine_object` is not immediately awaited
|
||||
|
|
||||
47 | trio.lowlevel.open_process()
|
||||
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 | trio.lowlevel.wait_readable(foo)
|
||||
|
|
||||
@@ -399,12 +399,12 @@ TRIO105.py:49:5: TRIO105 [*] Call to `trio.lowlevel.reattach_detached_coroutine_
|
||||
51 51 | trio.lowlevel.wait_readable(foo)
|
||||
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
|
||||
TRIO105.py:50:5: TRIO105 [*] Call to `trio.lowlevel.temporarily_detach_coroutine_object` is not immediately awaited
|
||||
ASYNC105.py:50:5: ASYNC105 [*] Call to `trio.lowlevel.temporarily_detach_coroutine_object` is not immediately awaited
|
||||
|
|
||||
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
51 | trio.lowlevel.wait_readable(foo)
|
||||
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
|
|
||||
@@ -420,12 +420,12 @@ TRIO105.py:50:5: TRIO105 [*] Call to `trio.lowlevel.temporarily_detach_coroutine
|
||||
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
53 53 | trio.lowlevel.wait_writable(foo)
|
||||
|
||||
TRIO105.py:51:5: TRIO105 [*] Call to `trio.lowlevel.wait_readable` is not immediately awaited
|
||||
ASYNC105.py:51:5: ASYNC105 [*] Call to `trio.lowlevel.wait_readable` is not immediately awaited
|
||||
|
|
||||
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 | trio.lowlevel.wait_readable(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
53 | trio.lowlevel.wait_writable(foo)
|
||||
|
|
||||
@@ -441,12 +441,12 @@ TRIO105.py:51:5: TRIO105 [*] Call to `trio.lowlevel.wait_readable` is not immedi
|
||||
53 53 | trio.lowlevel.wait_writable(foo)
|
||||
54 54 |
|
||||
|
||||
TRIO105.py:52:5: TRIO105 [*] Call to `trio.lowlevel.wait_task_rescheduled` is not immediately awaited
|
||||
ASYNC105.py:52:5: ASYNC105 [*] Call to `trio.lowlevel.wait_task_rescheduled` is not immediately awaited
|
||||
|
|
||||
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 | trio.lowlevel.wait_readable(foo)
|
||||
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
53 | trio.lowlevel.wait_writable(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
@@ -461,12 +461,12 @@ TRIO105.py:52:5: TRIO105 [*] Call to `trio.lowlevel.wait_task_rescheduled` is no
|
||||
54 54 |
|
||||
55 55 | async with await trio.open_file(foo): # Ok
|
||||
|
||||
TRIO105.py:53:5: TRIO105 [*] Call to `trio.lowlevel.wait_writable` is not immediately awaited
|
||||
ASYNC105.py:53:5: ASYNC105 [*] Call to `trio.lowlevel.wait_writable` is not immediately awaited
|
||||
|
|
||||
51 | trio.lowlevel.wait_readable(foo)
|
||||
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
53 | trio.lowlevel.wait_writable(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
54 |
|
||||
55 | async with await trio.open_file(foo): # Ok
|
||||
|
|
||||
@@ -482,12 +482,12 @@ TRIO105.py:53:5: TRIO105 [*] Call to `trio.lowlevel.wait_writable` is not immedi
|
||||
55 55 | async with await trio.open_file(foo): # Ok
|
||||
56 56 | pass
|
||||
|
||||
TRIO105.py:58:16: TRIO105 [*] Call to `trio.open_file` is not immediately awaited
|
||||
ASYNC105.py:58:16: ASYNC105 [*] Call to `trio.open_file` is not immediately awaited
|
||||
|
|
||||
56 | pass
|
||||
57 |
|
||||
58 | async with trio.open_file(foo): # TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
58 | async with trio.open_file(foo): # ASYNC105
|
||||
| ^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
59 | pass
|
||||
|
|
||||
= help: Add `await`
|
||||
@@ -496,19 +496,17 @@ TRIO105.py:58:16: TRIO105 [*] Call to `trio.open_file` is not immediately awaite
|
||||
55 55 | async with await trio.open_file(foo): # Ok
|
||||
56 56 | pass
|
||||
57 57 |
|
||||
58 |- async with trio.open_file(foo): # TRIO105
|
||||
58 |+ async with await trio.open_file(foo): # TRIO105
|
||||
58 |- async with trio.open_file(foo): # ASYNC105
|
||||
58 |+ async with await trio.open_file(foo): # ASYNC105
|
||||
59 59 | pass
|
||||
60 60 |
|
||||
61 61 |
|
||||
|
||||
TRIO105.py:64:5: TRIO105 Call to `trio.open_file` is not immediately awaited
|
||||
ASYNC105.py:64:5: ASYNC105 Call to `trio.open_file` is not immediately awaited
|
||||
|
|
||||
62 | def func() -> None:
|
||||
63 | # TRIO105 (without fix)
|
||||
63 | # ASYNC105 (without fix)
|
||||
64 | trio.open_file(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^ ASYNC105
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user