Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d83f99dbf | ||
|
|
5123b38758 | ||
|
|
e9a4c8ba13 | ||
|
|
927d716edd | ||
|
|
df6a48fced | ||
|
|
91a8277ac0 | ||
|
|
5797884262 | ||
|
|
5aa8455258 | ||
|
|
8fd713739b | ||
|
|
5de1fcd653 | ||
|
|
032f4f3f12 | ||
|
|
621db96e7f | ||
|
|
05867ef260 | ||
|
|
0cd8b75f06 | ||
|
|
5f07e1d6b5 | ||
|
|
1ce4585c88 | ||
|
|
f3f010cdf5 | ||
|
|
7e5e03fb15 |
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo build --release
|
||||
- run: cargo build --all --release
|
||||
|
||||
cargo_fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo fmt --check
|
||||
- run: cargo fmt --all --check
|
||||
|
||||
cargo_clippy:
|
||||
name: "cargo clippy"
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo clippy -- -D warnings
|
||||
- run: cargo clippy --all -- -D warnings
|
||||
|
||||
cargo_test:
|
||||
name: "cargo test"
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo test
|
||||
- run: cargo test --all
|
||||
|
||||
maturin_build:
|
||||
name: "maturin build"
|
||||
|
||||
298
.github/workflows/flake8-to-ruff.yaml
vendored
Normal file
298
.github/workflows/flake8-to-ruff.yaml
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
name: "[flake8-to-ruff] Release"
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PACKAGE_NAME: flake8-to-ruff
|
||||
CRATE_NAME: flake8_to_ruff
|
||||
PYTHON_VERSION: "3.7" # to build abi3 wheels
|
||||
|
||||
jobs:
|
||||
macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
- name: Build wheels - x86_64
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: x86_64
|
||||
args: --release --out dist --sdist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- name: Install built wheel - x86_64
|
||||
run: |
|
||||
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
macos-universal:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
- name: Build wheels - universal2
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
args: --release --universal2 --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- name: Install built wheel - universal2
|
||||
run: |
|
||||
pip install dist/${{ env.CRATE_NAME }}-*universal2.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: ${{ matrix.target }}
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- name: Install built wheel
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, i686]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- name: Install built wheel
|
||||
if: matrix.target == 'x86_64'
|
||||
run: |
|
||||
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
linux-cross:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [aarch64, armv7, s390x, ppc64le, ppc64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --no-default-features --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- uses: uraimo/run-on-arch-action@v2.0.5
|
||||
if: matrix.target != 'ppc64'
|
||||
name: Install built wheel
|
||||
with:
|
||||
arch: ${{ matrix.target }}
|
||||
distro: ubuntu20.04
|
||||
githubToken: ${{ github.token }}
|
||||
install: |
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends python3 python3-pip
|
||||
pip3 install -U pip
|
||||
run: |
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
musllinux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-unknown-linux-musl
|
||||
- i686-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: musllinux_1_2
|
||||
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- name: Install built wheel
|
||||
if: matrix.target == 'x86_64-unknown-linux-musl'
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: alpine:latest
|
||||
options: -v ${{ github.workspace }}:/io -w /io
|
||||
run: |
|
||||
apk add py3-pip
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links /io/dist/ --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
musllinux-cross:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- target: aarch64-unknown-linux-musl
|
||||
arch: aarch64
|
||||
- target: armv7-unknown-linux-musleabihf
|
||||
arch: armv7
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: musllinux_1_2
|
||||
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
maturin-version: "v0.13.0"
|
||||
- uses: uraimo/run-on-arch-action@master
|
||||
name: Install built wheel
|
||||
with:
|
||||
arch: ${{ matrix.platform.arch }}
|
||||
distro: alpine_latest
|
||||
githubToken: ${{ github.token }}
|
||||
install: |
|
||||
apk add py3-pip
|
||||
run: |
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
pypy:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
target: [x86_64, aarch64]
|
||||
python-version:
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
target: aarch64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: pypy${{ matrix.python-version }}
|
||||
- name: Build wheels
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
maturin-version: "v0.13.0"
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --release --out dist -i pypy${{ matrix.python-version }} -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
|
||||
- name: Install built wheel
|
||||
if: matrix.target == 'x86_64'
|
||||
run: |
|
||||
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- macos-universal
|
||||
- macos-x86_64
|
||||
- windows
|
||||
- linux
|
||||
- linux-cross
|
||||
- musllinux
|
||||
- musllinux-cross
|
||||
- pypy
|
||||
steps:
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
- uses: actions/setup-python@v4
|
||||
- name: Publish to PyPi
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.FLAKE8_TO_RUFF_TOKEN }}
|
||||
run: |
|
||||
pip install --upgrade twine
|
||||
twine upload --skip-existing *
|
||||
@@ -1,8 +1,6 @@
|
||||
name: Release
|
||||
name: "[ruff] Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
create:
|
||||
tags:
|
||||
- v*
|
||||
@@ -297,7 +295,7 @@ jobs:
|
||||
- name: Publish to PyPi
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
||||
TWINE_PASSWORD: ${{ secrets.RUFF_TOKEN }}
|
||||
run: |
|
||||
pip install --upgrade twine
|
||||
twine upload --skip-existing *
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.93
|
||||
rev: v0.0.94
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
23
Cargo.lock
generated
23
Cargo.lock
generated
@@ -571,6 +571,12 @@ dependencies = [
|
||||
"cache-padded",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "configparser"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5458d9d1a587efaf5091602c59d299696a3877a439c8f6d461a2d3cce11df87a"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.2"
|
||||
@@ -912,6 +918,21 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.94-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.15",
|
||||
"configparser",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"ruff",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.24"
|
||||
@@ -2190,7 +2211,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.93"
|
||||
version = "0.0.94"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/flake8_to_ruff",
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.93"
|
||||
version = "0.0.94"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -89,7 +89,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.93
|
||||
rev: v0.0.94
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
2964
crates/flake8_to_ruff/Cargo.lock
generated
Normal file
2964
crates/flake8_to_ruff/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
crates/flake8_to_ruff/Cargo.toml
Normal file
22
crates/flake8_to_ruff/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.94-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "flake8_to_ruff"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.60" }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
configparser = { version = "3.0.2" }
|
||||
once_cell = { version = "1.13.1" }
|
||||
regex = { version = "1.6.0" }
|
||||
ruff = { path = "../..", default-features = false }
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
toml = { version = "0.5.9" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
[features]
|
||||
47
crates/flake8_to_ruff/README.md
Normal file
47
crates/flake8_to_ruff/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# flake8-to-ruff
|
||||
|
||||
Convert existing Flake8 configuration files (`setup.cfg`, `tox.ini`, or `.flake8`) for use with
|
||||
[Ruff](https://github.com/charliermarsh/ruff).
|
||||
|
||||
Generates a Ruff-compatible `pyproject.toml` section.
|
||||
|
||||
## Installation and Usage
|
||||
|
||||
### Installation
|
||||
|
||||
Available as [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) on PyPI:
|
||||
|
||||
```shell
|
||||
pip install flake8-to-ruff
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
To run Ruff, try any of the following:
|
||||
|
||||
```shell
|
||||
flake8-to-ruff path/to/setup.cfg
|
||||
flake8-to-ruff path/to/tox.ini
|
||||
flake8-to-ruff path/to/.flake8
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
|
||||
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
|
||||
configuration options that don't exist in Flake8.)
|
||||
2. Ruff will omit any error codes that are unimplemented or unsupported by Ruff, including error
|
||||
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
|
||||
for the complete list of supported plugins.)
|
||||
3. `flake8-to-ruff` does not auto-detect your Flake8 plugins, so any reliance on Flake8 plugins that
|
||||
implicitly enable third-party checks will be ignored. Instead, add those error codes to your
|
||||
`select` or `extend-select` fields, so that `flake8-to-ruff` can pick them up.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome and hugely appreciated. To get started, check out the
|
||||
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).
|
||||
33
crates/flake8_to_ruff/pyproject.toml
Normal file
33
crates/flake8_to_ruff/pyproject.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[project]
|
||||
name = "flake8-to-ruff"
|
||||
keywords = ["automation", "flake8", "pycodestyle", "pyflakes", "pylint", "clippy"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Software Development :: Quality Assurance",
|
||||
]
|
||||
author = "Charlie Marsh"
|
||||
author_email = "charlie.r.marsh@gmail.com"
|
||||
description = "Convert existing Flake8 configuration to Ruff."
|
||||
requires-python = ">=3.7"
|
||||
|
||||
[project.urls]
|
||||
repository = "https://github.com/charliermarsh/ruff#subdirectory=crates/flake8_to_ruff"
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=0.13,<0.14"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "bin"
|
||||
strip = true
|
||||
59
crates/flake8_to_ruff/src/converter.rs
Normal file
59
crates/flake8_to_ruff/src/converter.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
|
||||
use crate::parser;
|
||||
|
||||
pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Result<Pyproject> {
|
||||
// Extract the Flake8 section.
|
||||
let flake8 = config
|
||||
.get("flake8")
|
||||
.expect("Unable to find flake8 section in INI file.");
|
||||
|
||||
// Parse each supported option.
|
||||
let mut options: Options = Default::default();
|
||||
for (key, value) in flake8 {
|
||||
if let Some(value) = value {
|
||||
match key.as_str() {
|
||||
"line-length" | "line_length" => match value.clone().parse::<usize>() {
|
||||
Ok(line_length) => options.line_length = Some(line_length),
|
||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||
},
|
||||
"select" => {
|
||||
options.select = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"extend-select" | "extend_select" => {
|
||||
options.extend_select = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"ignore" => {
|
||||
options.ignore = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"extend-ignore" | "extend_ignore" => {
|
||||
options.extend_ignore = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"exclude" => {
|
||||
options.exclude = Some(parser::parse_strings(value.as_ref()));
|
||||
}
|
||||
"extend-exclude" | "extend_exclude" => {
|
||||
options.extend_exclude = Some(parser::parse_strings(value.as_ref()));
|
||||
}
|
||||
"per-file-ignores" | "per_file_ignores" => {
|
||||
match parser::parse_files_to_codes_mapping(value.as_ref()) {
|
||||
Ok(per_file_ignores) => {
|
||||
options.per_file_ignores =
|
||||
Some(parser::collect_per_file_ignores(per_file_ignores))
|
||||
}
|
||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||
}
|
||||
}
|
||||
_ => eprintln!("Skipping unsupported property: {key}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the pyproject.toml.
|
||||
Ok(Pyproject::new(options))
|
||||
}
|
||||
4
crates/flake8_to_ruff/src/lib.rs
Normal file
4
crates/flake8_to_ruff/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
|
||||
|
||||
pub mod converter;
|
||||
mod parser;
|
||||
35
crates/flake8_to_ruff/src/main.rs
Normal file
35
crates/flake8_to_ruff/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
//! Utility to generate Ruff's pyproject.toml section from a Flake8 INI file.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use configparser::ini::Ini;
|
||||
|
||||
use flake8_to_ruff::converter;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
about = "Convert existing Flake8 configuration to Ruff.",
|
||||
long_about = None
|
||||
)]
|
||||
struct Cli {
|
||||
/// Path to the Flake8 configuration file (e.g., 'setup.cfg', 'tox.ini', or '.flake8').
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Read the INI file.
|
||||
let mut ini = Ini::new_cs();
|
||||
ini.set_multiline(true);
|
||||
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
|
||||
|
||||
// Create the pyproject.toml.
|
||||
let pyproject = converter::convert(config)?;
|
||||
println!("{}", toml::to_string_pretty(&pyproject)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
374
crates/flake8_to_ruff/src/parser.rs
Normal file
374
crates/flake8_to_ruff/src/parser.rs
Normal file
@@ -0,0 +1,374 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::settings::types::PatternPrefixPair;
|
||||
|
||||
static COMMA_SEPARATED_LIST_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
|
||||
|
||||
/// Parse a comma-separated list of `CheckCodePrefix` values (e.g., "F401,E501").
|
||||
pub fn parse_prefix_codes(value: &str) -> Vec<CheckCodePrefix> {
|
||||
let mut codes: Vec<CheckCodePrefix> = vec![];
|
||||
for code in COMMA_SEPARATED_LIST_RE.split(value) {
|
||||
let code = code.trim();
|
||||
if code.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(code) = CheckCodePrefix::from_str(code) {
|
||||
codes.push(code);
|
||||
} else {
|
||||
eprintln!("Unsupported prefix code: {code}");
|
||||
}
|
||||
}
|
||||
codes
|
||||
}
|
||||
|
||||
/// Parse a comma-separated list of strings (e.g., "__init__.py,__main__.py").
|
||||
pub fn parse_strings(value: &str) -> Vec<String> {
|
||||
COMMA_SEPARATED_LIST_RE
|
||||
.split(value)
|
||||
.map(|part| part.trim())
|
||||
.filter(|part| !part.is_empty())
|
||||
.map(String::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Token {
|
||||
token_name: TokenType,
|
||||
src: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TokenType {
|
||||
Code,
|
||||
File,
|
||||
Colon,
|
||||
Comma,
|
||||
Ws,
|
||||
Eof,
|
||||
}
|
||||
|
||||
struct State {
|
||||
seen_sep: bool,
|
||||
seen_colon: bool,
|
||||
filenames: Vec<String>,
|
||||
codes: Vec<String>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
seen_sep: true,
|
||||
seen_colon: false,
|
||||
filenames: vec![],
|
||||
codes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the list of `StrCheckCodePair` pairs for the current state.
|
||||
fn parse(&self) -> Vec<PatternPrefixPair> {
|
||||
let mut codes: Vec<PatternPrefixPair> = vec![];
|
||||
for code in &self.codes {
|
||||
match CheckCodePrefix::from_str(code) {
|
||||
Ok(code) => {
|
||||
for filename in &self.filenames {
|
||||
codes.push(PatternPrefixPair {
|
||||
pattern: filename.clone(),
|
||||
prefix: code.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(_) => eprintln!("Skipping unrecognized prefix: {}", code),
|
||||
}
|
||||
}
|
||||
codes
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokenize the raw 'files-to-codes' mapping.
|
||||
fn tokenize_files_to_codes_mapping(value: &str) -> Vec<Token> {
|
||||
let mut tokens = vec![];
|
||||
let mut i = 0;
|
||||
while i < value.len() {
|
||||
for (token_re, token_name) in [
|
||||
(
|
||||
Regex::new(r"([A-Z]+[0-9]*)(?:$|\s|,)").unwrap(),
|
||||
TokenType::Code,
|
||||
),
|
||||
(Regex::new(r"([^\s:,]+)").unwrap(), TokenType::File),
|
||||
(Regex::new(r"(\s*:\s*)").unwrap(), TokenType::Colon),
|
||||
(Regex::new(r"(\s*,\s*)").unwrap(), TokenType::Comma),
|
||||
(Regex::new(r"(\s+)").unwrap(), TokenType::Ws),
|
||||
] {
|
||||
if let Some(cap) = token_re.captures(&value[i..]) {
|
||||
let mat = cap.get(1).unwrap();
|
||||
if mat.start() == 0 {
|
||||
tokens.push(Token {
|
||||
token_name,
|
||||
src: mat.as_str().to_string().trim().to_string(),
|
||||
});
|
||||
i += mat.end();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens.push(Token {
|
||||
token_name: TokenType::Eof,
|
||||
src: "".to_string(),
|
||||
});
|
||||
tokens
|
||||
}
|
||||
|
||||
/// Parse a 'files-to-codes' mapping, mimicking Flake8's internal logic.
|
||||
///
|
||||
/// See: https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45
|
||||
pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair>> {
|
||||
if value.trim().is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
let mut codes: Vec<PatternPrefixPair> = vec![];
|
||||
let mut state = State::new();
|
||||
for token in tokenize_files_to_codes_mapping(value) {
|
||||
if matches!(token.token_name, TokenType::Comma | TokenType::Ws) {
|
||||
state.seen_sep = true;
|
||||
} else if !state.seen_colon {
|
||||
if matches!(token.token_name, TokenType::Colon) {
|
||||
state.seen_colon = true;
|
||||
state.seen_sep = true;
|
||||
} else if state.seen_sep && matches!(token.token_name, TokenType::File) {
|
||||
state.filenames.push(token.src);
|
||||
state.seen_sep = false;
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Unexpected token: {:?}", token.token_name));
|
||||
}
|
||||
} else {
|
||||
if matches!(token.token_name, TokenType::Eof) {
|
||||
codes.extend(state.parse());
|
||||
state = State::new();
|
||||
} else if state.seen_sep && matches!(token.token_name, TokenType::Code) {
|
||||
state.codes.push(token.src);
|
||||
state.seen_sep = false;
|
||||
} else if state.seen_sep && matches!(token.token_name, TokenType::File) {
|
||||
codes.extend(state.parse());
|
||||
state = State::new();
|
||||
state.filenames.push(token.src);
|
||||
state.seen_sep = false;
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Unexpected token: {:?}", token.token_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(codes)
|
||||
}
|
||||
|
||||
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
|
||||
pub fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
) -> BTreeMap<String, Vec<CheckCodePrefix>> {
|
||||
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(pair.prefix);
|
||||
}
|
||||
per_file_ignores
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::settings::types::PatternPrefixPair;
|
||||
|
||||
use crate::parser::{parse_files_to_codes_mapping, parse_prefix_codes, parse_strings};
|
||||
|
||||
#[test]
|
||||
fn it_parses_prefix_codes() {
|
||||
let actual = parse_prefix_codes("");
|
||||
let expected: Vec<CheckCodePrefix> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes(" ");
|
||||
let expected: Vec<CheckCodePrefix> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401");
|
||||
let expected = vec![CheckCodePrefix::F401];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401,");
|
||||
let expected = vec![CheckCodePrefix::F401];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401,E501");
|
||||
let expected = vec![CheckCodePrefix::F401, CheckCodePrefix::E501];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401, E501");
|
||||
let expected = vec![CheckCodePrefix::F401, CheckCodePrefix::E501];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_strings() {
|
||||
let actual = parse_strings("");
|
||||
let expected: Vec<String> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_strings(" ");
|
||||
let expected: Vec<String> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_strings("__init__.py");
|
||||
let expected = vec!["__init__.py".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_strings("__init__.py,");
|
||||
let expected = vec!["__init__.py".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_strings("__init__.py,__main__.py");
|
||||
let expected = vec!["__init__.py".to_string(), "__main__.py".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_strings("__init__.py, __main__.py");
|
||||
let expected = vec!["__init__.py".to_string(), "__main__.py".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parse_files_to_codes_mapping() -> Result<()> {
|
||||
let actual = parse_files_to_codes_mapping("")?;
|
||||
let expected: Vec<PatternPrefixPair> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_files_to_codes_mapping(" ")?;
|
||||
let expected: Vec<PatternPrefixPair> = vec![];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Ex) locust
|
||||
let actual = parse_files_to_codes_mapping(
|
||||
"per-file-ignores =
|
||||
locust/test/*: F841
|
||||
examples/*: F841
|
||||
*.pyi: E302,E704"
|
||||
.strip_prefix("per-file-ignores =")
|
||||
.unwrap(),
|
||||
)?;
|
||||
let expected: Vec<PatternPrefixPair> = vec![
|
||||
PatternPrefixPair {
|
||||
pattern: "locust/test/*".to_string(),
|
||||
prefix: CheckCodePrefix::F841,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "examples/*".to_string(),
|
||||
prefix: CheckCodePrefix::F841,
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Ex) celery
|
||||
let actual = parse_files_to_codes_mapping(
|
||||
"per-file-ignores =
|
||||
t/*,setup.py,examples/*,docs/*,extra/*:
|
||||
D,"
|
||||
.strip_prefix("per-file-ignores =")
|
||||
.unwrap(),
|
||||
)?;
|
||||
let expected: Vec<PatternPrefixPair> = vec![
|
||||
PatternPrefixPair {
|
||||
pattern: "t/*".to_string(),
|
||||
prefix: CheckCodePrefix::D,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "setup.py".to_string(),
|
||||
prefix: CheckCodePrefix::D,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "examples/*".to_string(),
|
||||
prefix: CheckCodePrefix::D,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "docs/*".to_string(),
|
||||
prefix: CheckCodePrefix::D,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "extra/*".to_string(),
|
||||
prefix: CheckCodePrefix::D,
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Ex) scrapy
|
||||
let actual = parse_files_to_codes_mapping(
|
||||
"per-file-ignores =
|
||||
scrapy/__init__.py:E402
|
||||
scrapy/core/downloader/handlers/http.py:F401
|
||||
scrapy/http/__init__.py:F401
|
||||
scrapy/linkextractors/__init__.py:E402,F401
|
||||
scrapy/selector/__init__.py:F401
|
||||
scrapy/spiders/__init__.py:E402,F401
|
||||
scrapy/utils/url.py:F403,F405
|
||||
tests/test_loader.py:E741"
|
||||
.strip_prefix("per-file-ignores =")
|
||||
.unwrap(),
|
||||
)?;
|
||||
let expected: Vec<PatternPrefixPair> = vec![
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::E402,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/core/downloader/handlers/http.py".to_string(),
|
||||
prefix: CheckCodePrefix::F401,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/http/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::F401,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/linkextractors/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::E402,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/linkextractors/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::F401,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/selector/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::F401,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/spiders/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::E402,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/spiders/__init__.py".to_string(),
|
||||
prefix: CheckCodePrefix::F401,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/utils/url.py".to_string(),
|
||||
prefix: CheckCodePrefix::F403,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/utils/url.py".to_string(),
|
||||
prefix: CheckCodePrefix::F405,
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "tests/test_loader.py".to_string(),
|
||||
prefix: CheckCodePrefix::E741,
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ fn main() {
|
||||
|
||||
println!("//! File automatically generated by examples/generate_check_code_prefix.rs.");
|
||||
println!();
|
||||
println!("use serde::{{Deserialize, Serialize}};");
|
||||
println!("use serde::{{Serialize, Deserialize}};");
|
||||
println!("use strum_macros::EnumString;");
|
||||
println!();
|
||||
println!("use crate::checks::CheckCode;");
|
||||
|
||||
30
resources/test/fixtures/F821_1.py
vendored
Normal file
30
resources/test/fixtures/F821_1.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Test: typing module imports."""
|
||||
from foo import cast
|
||||
|
||||
# OK
|
||||
x = cast("Model")
|
||||
|
||||
from typing import cast
|
||||
|
||||
|
||||
# F821 Undefined name `Model`
|
||||
x = cast("Model", x)
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
# F821 Undefined name `Model`
|
||||
x = typing.cast("Model", x)
|
||||
|
||||
|
||||
from typing import Pattern
|
||||
|
||||
# F821 Undefined name `Model`
|
||||
x = Pattern["Model"]
|
||||
|
||||
|
||||
from typing.re import Match
|
||||
|
||||
# F821 Undefined name `Model`
|
||||
x = Match["Model"]
|
||||
38
resources/test/fixtures/pyproject.toml
vendored
38
resources/test/fixtures/pyproject.toml
vendored
@@ -1,13 +1,11 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
extend-exclude = [
|
||||
"excluded.py",
|
||||
"migrations",
|
||||
"directory/also_excluded.py",
|
||||
]
|
||||
per-file-ignores = [
|
||||
"__init__.py:F401",
|
||||
"excluded.py",
|
||||
"migrations",
|
||||
"directory/also_excluded.py",
|
||||
]
|
||||
per-file-ignores = { "__init__.py" = ["F401"] }
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
@@ -17,22 +15,22 @@ avoid-escape = true
|
||||
|
||||
[tool.ruff.pep8-naming]
|
||||
ignore-names = [
|
||||
"setUp",
|
||||
"tearDown",
|
||||
"setUpClass",
|
||||
"tearDownClass",
|
||||
"setUpModule",
|
||||
"tearDownModule",
|
||||
"asyncSetUp",
|
||||
"asyncTearDown",
|
||||
"setUpTestData",
|
||||
"failureException",
|
||||
"longMessage",
|
||||
"maxDiff",
|
||||
"setUp",
|
||||
"tearDown",
|
||||
"setUpClass",
|
||||
"tearDownClass",
|
||||
"setUpModule",
|
||||
"tearDownModule",
|
||||
"asyncSetUp",
|
||||
"asyncTearDown",
|
||||
"setUpTestData",
|
||||
"failureException",
|
||||
"longMessage",
|
||||
"maxDiff",
|
||||
]
|
||||
classmethod-decorators = [
|
||||
"classmethod",
|
||||
"classmethod",
|
||||
]
|
||||
staticmethod-decorators = [
|
||||
"staticmethod",
|
||||
"staticmethod",
|
||||
]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, StmtKind};
|
||||
|
||||
use crate::python::typing;
|
||||
|
||||
fn compose_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
@@ -30,6 +30,7 @@ pub fn compose_call_path(expr: &Expr) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a name or attribute reference to `${target}`.
|
||||
pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, .. } => target == attr,
|
||||
@@ -38,32 +39,27 @@ pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SubscriptKind {
|
||||
AnnotatedSubscript,
|
||||
PEP593AnnotatedSubscript,
|
||||
}
|
||||
|
||||
pub fn match_annotated_subscript(expr: &Expr) -> Option<SubscriptKind> {
|
||||
/// Return `true` if the `Expr` is a reference to `${module}.${target}`.
|
||||
///
|
||||
/// Useful for, e.g., ensuring that a `Union` reference represents `typing.Union`.
|
||||
pub fn match_name_or_attr_from_module(
|
||||
expr: &Expr,
|
||||
target: &str,
|
||||
module: &str,
|
||||
imports: Option<&BTreeSet<&str>>,
|
||||
) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, .. } => {
|
||||
if typing::is_annotated_subscript(attr) {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
} else if typing::is_pep593_annotated_subscript(attr) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ExprKind::Attribute { value, attr, .. } => match &value.node {
|
||||
ExprKind::Name { id, .. } => id == module && target == attr,
|
||||
_ => false,
|
||||
},
|
||||
ExprKind::Name { id, .. } => {
|
||||
if typing::is_annotated_subscript(id) {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
} else if typing::is_pep593_annotated_subscript(id) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
target == id
|
||||
&& imports
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
_ => None,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use rustpython_parser::ast::{
|
||||
};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::{extract_handler_names, match_name_or_attr, SubscriptKind};
|
||||
use crate::ast::helpers::{extract_handler_names, match_name_or_attr_from_module};
|
||||
use crate::ast::operations::extract_all_names;
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{
|
||||
@@ -25,6 +25,8 @@ use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::python::future::ALL_FEATURE_NAMES;
|
||||
use crate::python::typing;
|
||||
use crate::python::typing::SubscriptKind;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::Settings;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
@@ -34,7 +36,15 @@ use crate::{
|
||||
pycodestyle, pydocstyle, pyflakes, pyupgrade,
|
||||
};
|
||||
|
||||
pub const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
const TRACK_FROM_IMPORTS: [&str; 6] = [
|
||||
"collections",
|
||||
"collections.abc",
|
||||
"contextlib",
|
||||
"re",
|
||||
"typing",
|
||||
"typing.re",
|
||||
];
|
||||
|
||||
pub struct Checker<'a> {
|
||||
// Input data.
|
||||
@@ -70,6 +80,7 @@ pub struct Checker<'a> {
|
||||
futures_allowed: bool,
|
||||
annotations_future_enabled: bool,
|
||||
except_handlers: Vec<Vec<String>>,
|
||||
from_imports: BTreeMap<&'a str, BTreeSet<&'a str>>,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -101,13 +112,14 @@ impl<'a> Checker<'a> {
|
||||
modifier: Modifier::Module,
|
||||
visibility: module_visibility(path),
|
||||
},
|
||||
in_f_string: None,
|
||||
in_f_string: Default::default(),
|
||||
in_annotation: Default::default(),
|
||||
in_literal: Default::default(),
|
||||
seen_import_boundary: Default::default(),
|
||||
futures_allowed: true,
|
||||
annotations_future_enabled: Default::default(),
|
||||
except_handlers: Default::default(),
|
||||
from_imports: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +127,11 @@ impl<'a> Checker<'a> {
|
||||
pub fn patch(&self) -> bool {
|
||||
self.autofix.patch()
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
|
||||
pub fn match_typing_module(&self, expr: &Expr, target: &str) -> bool {
|
||||
match_name_or_attr_from_module(expr, target, "typing", self.from_imports.get("typing"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for Checker<'a>
|
||||
@@ -492,6 +509,24 @@ where
|
||||
module,
|
||||
level,
|
||||
} => {
|
||||
// Track `import from` statements, to ensure that we can correctly attribute
|
||||
// references like `from typing import Union`.
|
||||
if level.map(|level| level == 0).unwrap_or(true) {
|
||||
if let Some(module) = module {
|
||||
if TRACK_FROM_IMPORTS.contains(&module.as_str()) {
|
||||
self.from_imports
|
||||
.entry(module)
|
||||
.or_insert_with(BTreeSet::new)
|
||||
.extend(
|
||||
names
|
||||
.iter()
|
||||
.filter(|alias| alias.node.asname.is_none())
|
||||
.map(|alias| alias.node.name.as_str()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
if self.seen_import_boundary && stmt.location.column() == 0 {
|
||||
self.checks.push(Check::new(
|
||||
@@ -861,7 +896,7 @@ where
|
||||
pyupgrade::plugins::use_pep604_annotation(self, expr, value, slice);
|
||||
}
|
||||
|
||||
if match_name_or_attr(value, "Literal") {
|
||||
if self.match_typing_module(value, "Literal") {
|
||||
self.in_literal = true;
|
||||
}
|
||||
}
|
||||
@@ -886,6 +921,7 @@ where
|
||||
// Ex) List[...]
|
||||
if self.settings.enabled.contains(&CheckCode::U006)
|
||||
&& self.settings.target_version >= PythonVersion::Py39
|
||||
&& typing::is_pep585_builtin(expr, self.from_imports.get("typing"))
|
||||
{
|
||||
pyupgrade::plugins::use_pep585_annotation(self, expr, id);
|
||||
}
|
||||
@@ -908,16 +944,13 @@ where
|
||||
}
|
||||
ExprContext::Del => self.handle_node_delete(expr),
|
||||
},
|
||||
ExprKind::Attribute { value, attr, .. } => {
|
||||
ExprKind::Attribute { attr, .. } => {
|
||||
// Ex) typing.List[...]
|
||||
if self.settings.enabled.contains(&CheckCode::U006)
|
||||
&& self.settings.target_version >= PythonVersion::Py39
|
||||
&& typing::is_pep585_builtin(expr, self.from_imports.get("typing"))
|
||||
{
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "typing" {
|
||||
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
|
||||
}
|
||||
}
|
||||
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
|
||||
}
|
||||
}
|
||||
ExprKind::Call {
|
||||
@@ -1276,12 +1309,12 @@ where
|
||||
args,
|
||||
keywords,
|
||||
} => {
|
||||
if match_name_or_attr(func, "ForwardRef") {
|
||||
if self.match_typing_module(func, "ForwardRef") {
|
||||
self.visit_expr(func);
|
||||
for expr in args {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
} else if match_name_or_attr(func, "cast") {
|
||||
} else if self.match_typing_module(func, "cast") {
|
||||
self.visit_expr(func);
|
||||
if !args.is_empty() {
|
||||
self.visit_annotation(&args[0]);
|
||||
@@ -1289,12 +1322,12 @@ where
|
||||
for expr in args.iter().skip(1) {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
} else if match_name_or_attr(func, "NewType") {
|
||||
} else if self.match_typing_module(func, "NewType") {
|
||||
self.visit_expr(func);
|
||||
for expr in args.iter().skip(1) {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
} else if match_name_or_attr(func, "TypeVar") {
|
||||
} else if self.match_typing_module(func, "TypeVar") {
|
||||
self.visit_expr(func);
|
||||
for expr in args.iter().skip(1) {
|
||||
self.visit_annotation(expr);
|
||||
@@ -1311,7 +1344,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if match_name_or_attr(func, "NamedTuple") {
|
||||
} else if self.match_typing_module(func, "NamedTuple") {
|
||||
self.visit_expr(func);
|
||||
|
||||
// Ex) NamedTuple("a", [("a", int)])
|
||||
@@ -1343,7 +1376,7 @@ where
|
||||
let KeywordData { value, .. } = &keyword.node;
|
||||
self.visit_annotation(value);
|
||||
}
|
||||
} else if match_name_or_attr(func, "TypedDict") {
|
||||
} else if self.match_typing_module(func, "TypedDict") {
|
||||
self.visit_expr(func);
|
||||
|
||||
// Ex) TypedDict("a", {"a": int})
|
||||
@@ -1370,7 +1403,7 @@ where
|
||||
}
|
||||
}
|
||||
ExprKind::Subscript { value, slice, ctx } => {
|
||||
match helpers::match_annotated_subscript(value) {
|
||||
match typing::match_annotated_subscript(value, &self.from_imports) {
|
||||
Some(subscript) => match subscript {
|
||||
// Ex) Optional[int]
|
||||
SubscriptKind::AnnotatedSubscript => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! File automatically generated by examples/generate_check_code_prefix.rs.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::EnumString;
|
||||
use strum_macros::{AsRefStr, EnumString};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
|
||||
#[derive(EnumString, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
#[derive(AsRefStr, EnumString, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub enum CheckCodePrefix {
|
||||
A,
|
||||
A0,
|
||||
|
||||
19
src/cli.rs
19
src/cli.rs
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -8,8 +9,8 @@ use regex::Regex;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::types::PatternPrefixPair;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::types::StrCheckCodePair;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "ruff: An extremely fast Python linter.")]
|
||||
@@ -61,7 +62,7 @@ pub struct Cli {
|
||||
pub extend_exclude: Vec<String>,
|
||||
/// List of mappings from file pattern to code to exclude
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub per_file_ignores: Vec<StrCheckCodePair>,
|
||||
pub per_file_ignores: Vec<PatternPrefixPair>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
|
||||
pub format: SerializationFormat,
|
||||
@@ -143,3 +144,17 @@ pub fn warn_on(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
|
||||
pub fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
) -> BTreeMap<String, Vec<CheckCodePrefix>> {
|
||||
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(pair.prefix);
|
||||
}
|
||||
per_file_ignores
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub enum Quote {
|
||||
Single,
|
||||
Double,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub inline_quotes: Option<Quote>,
|
||||
|
||||
@@ -21,7 +21,7 @@ pub mod check_ast;
|
||||
mod check_lines;
|
||||
mod check_tokens;
|
||||
pub mod checks;
|
||||
mod checks_gen;
|
||||
pub mod checks_gen;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
mod cst;
|
||||
|
||||
@@ -377,7 +377,8 @@ mod tests {
|
||||
#[test_case(CheckCode::F706, Path::new("F706.py"); "F706")]
|
||||
#[test_case(CheckCode::F707, Path::new("F707.py"); "F707")]
|
||||
#[test_case(CheckCode::F722, Path::new("F722.py"); "F722")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821.py"); "F821")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_0.py"); "F821_0")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_1.py"); "F821_1")]
|
||||
#[test_case(CheckCode::F822, Path::new("F822.py"); "F822")]
|
||||
#[test_case(CheckCode::F823, Path::new("F823.py"); "F823")]
|
||||
#[test_case(CheckCode::F831, Path::new("F831.py"); "F831")]
|
||||
|
||||
13
src/main.rs
13
src/main.rs
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{self, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
@@ -17,7 +18,8 @@ use walkdir::DirEntry;
|
||||
use ruff::cache;
|
||||
use ruff::checks::CheckCode;
|
||||
use ruff::checks::CheckKind;
|
||||
use ruff::cli::{warn_on, Cli, Warnable};
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::cli::{collect_per_file_ignores, warn_on, Cli, Warnable};
|
||||
use ruff::fs::iter_python_files;
|
||||
use ruff::linter::add_noqa_to_path;
|
||||
use ruff::linter::autoformat_path;
|
||||
@@ -27,7 +29,7 @@ use ruff::message::Message;
|
||||
use ruff::printer::{Printer, SerializationFormat};
|
||||
use ruff::settings::configuration::Configuration;
|
||||
use ruff::settings::pyproject;
|
||||
use ruff::settings::types::{FilePattern, PerFileIgnore};
|
||||
use ruff::settings::types::FilePattern;
|
||||
use ruff::settings::user::UserConfiguration;
|
||||
use ruff::settings::Settings;
|
||||
use ruff::tell_user;
|
||||
@@ -255,11 +257,8 @@ fn inner_main() -> Result<ExitCode> {
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect();
|
||||
let per_file_ignores: Vec<PerFileIgnore> = cli
|
||||
.per_file_ignores
|
||||
.into_iter()
|
||||
.map(|pair| PerFileIgnore::new(pair, &project_root))
|
||||
.collect();
|
||||
let per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> =
|
||||
collect_per_file_ignores(cli.per_file_ignores);
|
||||
|
||||
let mut configuration = Configuration::from_pyproject(&pyproject, &project_root)?;
|
||||
if !exclude.is_empty() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Settings for the `pep8-naming` plugin.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const IGNORE_NAMES: [&str; 12] = [
|
||||
"setUp",
|
||||
@@ -21,7 +21,7 @@ const CLASSMETHOD_DECORATORS: [&str; 1] = ["classmethod"];
|
||||
|
||||
const STATICMETHOD_DECORATORS: [&str; 1] = ["staticmethod"];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub ignore_names: Option<Vec<String>>,
|
||||
|
||||
@@ -1,101 +1,207 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
"AbstractAsyncContextManager",
|
||||
"AbstractContextManager",
|
||||
"AbstractSet",
|
||||
// "Annotated",
|
||||
"AsyncContextManager",
|
||||
"AsyncGenerator",
|
||||
"AsyncIterable",
|
||||
"AsyncIterator",
|
||||
"Awaitable",
|
||||
"BinaryIO",
|
||||
"BsdDbShelf",
|
||||
"ByteString",
|
||||
"Callable",
|
||||
"ChainMap",
|
||||
"ClassVar",
|
||||
"Collection",
|
||||
"Concatenate",
|
||||
"Container",
|
||||
"ContextManager",
|
||||
"Coroutine",
|
||||
"Counter",
|
||||
"Counter",
|
||||
"DbfilenameShelf",
|
||||
"DefaultDict",
|
||||
"Deque",
|
||||
"Dict",
|
||||
"Field",
|
||||
"Final",
|
||||
"FrozenSet",
|
||||
"Generator",
|
||||
"Iterator",
|
||||
"Generic",
|
||||
"IO",
|
||||
"ItemsView",
|
||||
"Iterable",
|
||||
"Iterator",
|
||||
"KeysView",
|
||||
"LifoQueue",
|
||||
"List",
|
||||
"Mapping",
|
||||
"MappingProxyType",
|
||||
"MappingView",
|
||||
"Match",
|
||||
"MutableMapping",
|
||||
"MutableSequence",
|
||||
"MutableSet",
|
||||
"Optional",
|
||||
"OrderedDict",
|
||||
"PathLike",
|
||||
"Pattern",
|
||||
"PriorityQueue",
|
||||
"Protocol",
|
||||
"Queue",
|
||||
"Reversible",
|
||||
"Sequence",
|
||||
"Set",
|
||||
"Shelf",
|
||||
"SimpleQueue",
|
||||
"TextIO",
|
||||
"Tuple",
|
||||
"Type",
|
||||
"TypeGuard",
|
||||
"Union",
|
||||
"ValuesView",
|
||||
"WeakKeyDictionary",
|
||||
"WeakMethod",
|
||||
"WeakSet",
|
||||
"WeakValueDictionary",
|
||||
"cached_property",
|
||||
"defaultdict",
|
||||
"deque",
|
||||
"dict",
|
||||
"frozenset",
|
||||
"list",
|
||||
"partialmethod",
|
||||
"set",
|
||||
"tuple",
|
||||
"type",
|
||||
])
|
||||
});
|
||||
// See: https://docs.python.org/3/library/typing.html
|
||||
static IMPORTED_SUBSCRIPTS: Lazy<BTreeMap<&'static str, BTreeSet<&'static str>>> =
|
||||
Lazy::new(|| {
|
||||
let mut import_map = BTreeMap::new();
|
||||
for (name, module) in [
|
||||
// `collections`
|
||||
("ChainMap", "collections"),
|
||||
("Counter", "collections"),
|
||||
("OrderedDict", "collections"),
|
||||
("defaultdict", "collections"),
|
||||
("deque", "collections"),
|
||||
// `collections.abc`
|
||||
("AsyncGenerator", "collections.abc"),
|
||||
("AsyncIterable", "collections.abc"),
|
||||
("AsyncIterator", "collections.abc"),
|
||||
("Awaitable", "collections.abc"),
|
||||
("ByteString", "collections.abc"),
|
||||
("Callable", "collections.abc"),
|
||||
("Collection", "collections.abc"),
|
||||
("Container", "collections.abc"),
|
||||
("Coroutine", "collections.abc"),
|
||||
("Generator", "collections.abc"),
|
||||
("ItemsView", "collections.abc"),
|
||||
("Iterable", "collections.abc"),
|
||||
("Iterator", "collections.abc"),
|
||||
("KeysView", "collections.abc"),
|
||||
("Mapping", "collections.abc"),
|
||||
("MappingView", "collections.abc"),
|
||||
("MutableMapping", "collections.abc"),
|
||||
("MutableSequence", "collections.abc"),
|
||||
("MutableSet", "collections.abc"),
|
||||
("Reversible", "collections.abc"),
|
||||
("Sequence", "collections.abc"),
|
||||
("Set", "collections.abc"),
|
||||
("ValuesView", "collections.abc"),
|
||||
// `contextlib`
|
||||
("AbstractAsyncContextManager", "contextlib"),
|
||||
("AbstractContextManager", "contextlib"),
|
||||
// `re`
|
||||
("Match", "re"),
|
||||
("Pattern", "re"),
|
||||
// `typing`
|
||||
("AbstractSet", "typing"),
|
||||
("Annotated", "typing"),
|
||||
("AsyncContextManager", "typing"),
|
||||
("AsyncContextManager", "typing"),
|
||||
("AsyncGenerator", "typing"),
|
||||
("AsyncIterable", "typing"),
|
||||
("AsyncIterator", "typing"),
|
||||
("Awaitable", "typing"),
|
||||
("BinaryIO", "typing"),
|
||||
("ByteString", "typing"),
|
||||
("Callable", "typing"),
|
||||
("ChainMap", "typing"),
|
||||
("ClassVar", "typing"),
|
||||
("Collection", "typing"),
|
||||
("Concatenate", "typing"),
|
||||
("Container", "typing"),
|
||||
("ContextManager", "typing"),
|
||||
("ContextManager", "typing"),
|
||||
("Coroutine", "typing"),
|
||||
("Counter", "typing"),
|
||||
("DefaultDict", "typing"),
|
||||
("Deque", "typing"),
|
||||
("Dict", "typing"),
|
||||
("Final", "typing"),
|
||||
("FrozenSet", "typing"),
|
||||
("Generator", "typing"),
|
||||
("Generic", "typing"),
|
||||
("IO", "typing"),
|
||||
("ItemsView", "typing"),
|
||||
("Iterable", "typing"),
|
||||
("Iterator", "typing"),
|
||||
("KeysView", "typing"),
|
||||
("List", "typing"),
|
||||
("Mapping", "typing"),
|
||||
("Match", "typing"),
|
||||
("MutableMapping", "typing"),
|
||||
("MutableSequence", "typing"),
|
||||
("MutableSet", "typing"),
|
||||
("Optional", "typing"),
|
||||
("OrderedDict", "typing"),
|
||||
("Pattern", "typing"),
|
||||
("Reversible", "typing"),
|
||||
("Sequence", "typing"),
|
||||
("Set", "typing"),
|
||||
("TextIO", "typing"),
|
||||
("Tuple", "typing"),
|
||||
("Type", "typing"),
|
||||
("Type", "typing"),
|
||||
("TypeGuard", "typing"),
|
||||
("Union", "typing"),
|
||||
("Unpack", "typing"),
|
||||
("ValuesView", "typing"),
|
||||
// `typing.io`
|
||||
("BinaryIO", "typing.io"),
|
||||
("IO", "typing.io"),
|
||||
("TextIO", "typing.io"),
|
||||
// `typing.re`
|
||||
("Match", "typing.re"),
|
||||
("Pattern", "typing.re"),
|
||||
// `weakref`
|
||||
("WeakKeyDictionary", "weakref"),
|
||||
("WeakSet", "weakref"),
|
||||
("WeakValueDictionary", "weakref"),
|
||||
] {
|
||||
import_map
|
||||
.entry(name)
|
||||
.or_insert_with(BTreeSet::new)
|
||||
.insert(module);
|
||||
}
|
||||
import_map
|
||||
});
|
||||
|
||||
pub fn is_annotated_subscript(name: &str) -> bool {
|
||||
ANNOTATED_SUBSCRIPTS.contains(name)
|
||||
}
|
||||
// These are all assumed to come from the `typing` module.
|
||||
// See: https://peps.python.org/pep-0585/
|
||||
static PEP_585_BUILTINS_ELIGIBLE: Lazy<BTreeSet<&'static str>> =
|
||||
Lazy::new(|| BTreeSet::from(["Dict", "FrozenSet", "List", "Set", "Tuple", "Type"]));
|
||||
|
||||
pub fn is_pep593_annotated_subscript(name: &str) -> bool {
|
||||
// These are all assumed to come from the `typing` module.
|
||||
// See: https://peps.python.org/pep-0585/
|
||||
static PEP_585_BUILTINS: Lazy<BTreeSet<&'static str>> =
|
||||
Lazy::new(|| BTreeSet::from(["dict", "frozenset", "list", "set", "tuple", "type"]));
|
||||
|
||||
fn is_pep593_annotated_subscript(name: &str) -> bool {
|
||||
name == "Annotated"
|
||||
}
|
||||
|
||||
static PEP_585_BUILTINS: Lazy<BTreeSet<&'static str>> =
|
||||
Lazy::new(|| BTreeSet::from(["Dict", "FrozenSet", "List", "Set", "Tuple", "Type"]));
|
||||
|
||||
pub fn is_pep585_builtin(name: &str) -> bool {
|
||||
PEP_585_BUILTINS.contains(name)
|
||||
pub enum SubscriptKind {
|
||||
AnnotatedSubscript,
|
||||
PEP593AnnotatedSubscript,
|
||||
}
|
||||
|
||||
pub fn match_annotated_subscript(
|
||||
expr: &Expr,
|
||||
imports: &BTreeMap<&str, BTreeSet<&str>>,
|
||||
) -> Option<SubscriptKind> {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, value, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
// If `id` is `typing` and `attr` is `Union`, verify that `typing.Union` is an
|
||||
// annotated subscript.
|
||||
if IMPORTED_SUBSCRIPTS
|
||||
.get(&attr.as_str())
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return if is_pep593_annotated_subscript(attr) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
// Built-ins (no import necessary).
|
||||
if PEP_585_BUILTINS.contains(&id.as_str()) {
|
||||
return Some(SubscriptKind::AnnotatedSubscript);
|
||||
}
|
||||
|
||||
// Verify that, e.g., `Union` is a reference to `typing.Union`.
|
||||
if let Some(modules) = IMPORTED_SUBSCRIPTS.get(&id.as_str()) {
|
||||
for module in modules {
|
||||
if imports
|
||||
.get(module)
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return if is_pep593_annotated_subscript(id) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns `true` if `Expr` represents a reference to a typing object with a PEP 585 built-in.
|
||||
pub fn is_pep585_builtin(expr: &Expr, typing_imports: Option<&BTreeSet<&str>>) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, value, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
id == "typing" && PEP_585_BUILTINS_ELIGIBLE.contains(&attr.as_str())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
typing_imports
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
&& PEP_585_BUILTINS_ELIGIBLE.contains(&id.as_str())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,18 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::python::typing;
|
||||
|
||||
pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr, id: &str) {
|
||||
// TODO(charlie): Verify that the builtin is imported from the `typing` module.
|
||||
if typing::is_pep585_builtin(id) {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UsePEP585Annotation(id.to_string()),
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch() {
|
||||
check.amend(Fix::replacement(
|
||||
id.to_lowercase(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
let mut check = Check::new(
|
||||
CheckKind::UsePEP585Annotation(id.to_string()),
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch() {
|
||||
check.amend(Fix::replacement(
|
||||
id.to_lowercase(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Operator};
|
||||
|
||||
use crate::ast::helpers::match_name_or_attr;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
@@ -43,7 +42,7 @@ fn union(elts: &[Expr]) -> Expr {
|
||||
}
|
||||
|
||||
pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, slice: &Expr) {
|
||||
if match_name_or_attr(value, "Optional") {
|
||||
if checker.match_typing_module(value, "Optional") {
|
||||
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
|
||||
if checker.patch() {
|
||||
let mut generator = SourceGenerator::new();
|
||||
@@ -58,7 +57,7 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
} else if match_name_or_attr(value, "Union") {
|
||||
} else if checker.match_typing_module(value, "Union") {
|
||||
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
|
||||
if checker.patch() {
|
||||
match &slice.node {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! User-provided program settings, taking into account pyproject.toml and command-line options.
|
||||
//! Structure mirrors the user-facing representation of the various parameters.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
@@ -9,7 +10,7 @@ use regex::Regex;
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::pyproject::load_options;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::settings::types::{FilePattern, PythonVersion};
|
||||
use crate::{flake8_quotes, pep8_naming};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -21,7 +22,7 @@ pub struct Configuration {
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
pub target_version: PythonVersion,
|
||||
// Plugins
|
||||
@@ -80,21 +81,18 @@ impl Configuration {
|
||||
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
|
||||
extend_exclude: options
|
||||
.extend_exclude
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.collect(),
|
||||
extend_ignore: options.extend_ignore,
|
||||
extend_ignore: options.extend_ignore.unwrap_or_default(),
|
||||
select: options
|
||||
.select
|
||||
.unwrap_or_else(|| vec![CheckCodePrefix::E, CheckCodePrefix::F]),
|
||||
extend_select: options.extend_select,
|
||||
ignore: options.ignore,
|
||||
extend_select: options.extend_select.unwrap_or_default(),
|
||||
ignore: options.ignore.unwrap_or_default(),
|
||||
line_length: options.line_length.unwrap_or(88),
|
||||
per_file_ignores: options
|
||||
.per_file_ignores
|
||||
.into_iter()
|
||||
.map(|pair| PerFileIgnore::new(pair, project_root))
|
||||
.collect(),
|
||||
per_file_ignores: options.per_file_ignores.unwrap_or_default(),
|
||||
// Plugins
|
||||
flake8_quotes: options
|
||||
.flake8_quotes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Effective program settings, taking into account pyproject.toml and command-line options.
|
||||
//! Structure is optimized for internal usage, as opposed to external visibility or parsing.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use regex::Regex;
|
||||
@@ -47,7 +47,7 @@ impl Settings {
|
||||
flake8_quotes: config.flake8_quotes,
|
||||
line_length: config.line_length,
|
||||
pep8_naming: config.pep8_naming,
|
||||
per_file_ignores: config.per_file_ignores,
|
||||
per_file_ignores: resolve_per_file_ignores(&config.per_file_ignores),
|
||||
target_version: config.target_version,
|
||||
}
|
||||
}
|
||||
@@ -56,10 +56,10 @@ impl Settings {
|
||||
Self {
|
||||
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
|
||||
enabled: BTreeSet::from([check_code]),
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
exclude: Default::default(),
|
||||
extend_exclude: Default::default(),
|
||||
line_length: 88,
|
||||
per_file_ignores: vec![],
|
||||
per_file_ignores: Default::default(),
|
||||
target_version: PythonVersion::Py310,
|
||||
flake8_quotes: Default::default(),
|
||||
pep8_naming: Default::default(),
|
||||
@@ -70,10 +70,10 @@ impl Settings {
|
||||
Self {
|
||||
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
|
||||
enabled: BTreeSet::from_iter(check_codes),
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
exclude: Default::default(),
|
||||
extend_exclude: Default::default(),
|
||||
line_length: 88,
|
||||
per_file_ignores: vec![],
|
||||
per_file_ignores: Default::default(),
|
||||
target_version: PythonVersion::Py310,
|
||||
flake8_quotes: Default::default(),
|
||||
pep8_naming: Default::default(),
|
||||
@@ -136,6 +136,15 @@ fn resolve_codes(
|
||||
codes
|
||||
}
|
||||
|
||||
fn resolve_per_file_ignores(
|
||||
per_file_ignores: &BTreeMap<String, Vec<CheckCodePrefix>>,
|
||||
) -> Vec<PerFileIgnore> {
|
||||
per_file_ignores
|
||||
.iter()
|
||||
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, prefixes, &None))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
//! Options that the user can provide via pyproject.toml.
|
||||
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::types::{PythonVersion, StrCheckCodePair};
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::{flake8_quotes, pep8_naming};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub line_length: Option<usize>,
|
||||
pub exclude: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub extend_exclude: Vec<String>,
|
||||
pub extend_exclude: Option<Vec<String>>,
|
||||
pub select: Option<Vec<CheckCodePrefix>>,
|
||||
#[serde(default)]
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
#[serde(default)]
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
#[serde(default)]
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
#[serde(default)]
|
||||
pub per_file_ignores: Vec<StrCheckCodePair>,
|
||||
pub extend_select: Option<Vec<CheckCodePrefix>>,
|
||||
pub ignore: Option<Vec<CheckCodePrefix>>,
|
||||
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
|
||||
pub per_file_ignores: Option<BTreeMap<String, Vec<CheckCodePrefix>>>,
|
||||
pub dummy_variable_rgx: Option<String>,
|
||||
pub target_version: Option<PythonVersion>,
|
||||
// Plugins
|
||||
|
||||
@@ -6,21 +6,31 @@ use anyhow::Result;
|
||||
use common_path::common_path_all;
|
||||
use log::debug;
|
||||
use path_absolutize::Absolutize;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::fs;
|
||||
use crate::settings::options::Options;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct Tools {
|
||||
ruff: Option<Options>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct Pyproject {
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Pyproject {
|
||||
tool: Option<Tools>,
|
||||
}
|
||||
|
||||
impl Pyproject {
|
||||
pub fn new(options: Options) -> Self {
|
||||
Self {
|
||||
tool: Some(Tools {
|
||||
ruff: Some(options),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_pyproject_toml(path: &Path) -> Result<Pyproject> {
|
||||
let contents = fs::read_file(path)?;
|
||||
toml::from_str(&contents).map_err(|e| e.into())
|
||||
@@ -86,6 +96,7 @@ pub fn load_options(pyproject: &Option<PathBuf>) -> Result<Options> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
use std::env::current_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
@@ -97,7 +108,7 @@ mod tests {
|
||||
use crate::settings::pyproject::{
|
||||
find_project_root, find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools,
|
||||
};
|
||||
use crate::settings::types::StrCheckCodePair;
|
||||
use crate::settings::types::PatternPrefixPair;
|
||||
use crate::{flake8_quotes, pep8_naming};
|
||||
|
||||
#[test]
|
||||
@@ -124,12 +135,12 @@ mod tests {
|
||||
ruff: Some(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: vec![],
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
@@ -151,12 +162,12 @@ line-length = 79
|
||||
ruff: Some(Options {
|
||||
line_length: Some(79),
|
||||
exclude: None,
|
||||
extend_exclude: vec![],
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
@@ -178,12 +189,12 @@ exclude = ["foo.py"]
|
||||
ruff: Some(Options {
|
||||
line_length: None,
|
||||
exclude: Some(vec!["foo.py".to_string()]),
|
||||
extend_exclude: vec![],
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
@@ -205,12 +216,12 @@ select = ["E501"]
|
||||
ruff: Some(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: vec![],
|
||||
extend_exclude: None,
|
||||
select: Some(vec![CheckCodePrefix::E501]),
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
@@ -233,12 +244,12 @@ ignore = ["E501"]
|
||||
ruff: Some(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: vec![],
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
extend_select: vec![CheckCodePrefix::M001],
|
||||
ignore: vec![CheckCodePrefix::E501],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
extend_select: Some(vec![CheckCodePrefix::M001]),
|
||||
ignore: Some(vec![CheckCodePrefix::E501]),
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
@@ -300,19 +311,19 @@ other-attribute = 1
|
||||
Options {
|
||||
line_length: Some(88),
|
||||
exclude: None,
|
||||
extend_exclude: vec![
|
||||
extend_exclude: Some(vec![
|
||||
"excluded.py".to_string(),
|
||||
"migrations".to_string(),
|
||||
"directory/also_excluded.py".to_string(),
|
||||
],
|
||||
]),
|
||||
select: None,
|
||||
extend_select: vec![],
|
||||
ignore: vec![],
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![StrCheckCodePair {
|
||||
pattern: "__init__.py".to_string(),
|
||||
code: CheckCodePrefix::F401
|
||||
}],
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: Some(BTreeMap::from([(
|
||||
"__init__.py".to_string(),
|
||||
vec![CheckCodePrefix::F401]
|
||||
),])),
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
@@ -347,21 +358,21 @@ other-attribute = 1
|
||||
|
||||
#[test]
|
||||
fn str_check_code_pair_strings() {
|
||||
let result = StrCheckCodePair::from_str("foo:E501");
|
||||
let result = PatternPrefixPair::from_str("foo:E501");
|
||||
assert!(result.is_ok());
|
||||
let result = StrCheckCodePair::from_str("foo: E501");
|
||||
let result = PatternPrefixPair::from_str("foo: E501");
|
||||
assert!(result.is_ok());
|
||||
let result = StrCheckCodePair::from_str("E501:foo");
|
||||
let result = PatternPrefixPair::from_str("E501:foo");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("E501");
|
||||
let result = PatternPrefixPair::from_str("E501");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("foo");
|
||||
let result = PatternPrefixPair::from_str("foo");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("foo:E501:E402");
|
||||
let result = PatternPrefixPair::from_str("foo:E501:E402");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("**/bar:E501");
|
||||
let result = PatternPrefixPair::from_str("**/bar:E501");
|
||||
assert!(result.is_ok());
|
||||
let result = StrCheckCodePair::from_str("bar:E502");
|
||||
let result = PatternPrefixPair::from_str("bar:E502");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,24 +75,28 @@ pub struct PerFileIgnore {
|
||||
}
|
||||
|
||||
impl PerFileIgnore {
|
||||
pub fn new(user_in: StrCheckCodePair, project_root: &Option<PathBuf>) -> Self {
|
||||
let pattern = FilePattern::from_user(user_in.pattern.as_str(), project_root);
|
||||
let codes = BTreeSet::from_iter(user_in.code.codes());
|
||||
pub fn new(
|
||||
pattern: &str,
|
||||
prefixes: &[CheckCodePrefix],
|
||||
project_root: &Option<PathBuf>,
|
||||
) -> Self {
|
||||
let pattern = FilePattern::from_user(pattern, project_root);
|
||||
let codes = BTreeSet::from_iter(prefixes.iter().flat_map(|prefix| prefix.codes()));
|
||||
Self { pattern, codes }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StrCheckCodePair {
|
||||
pub struct PatternPrefixPair {
|
||||
pub pattern: String,
|
||||
pub code: CheckCodePrefix,
|
||||
pub prefix: CheckCodePrefix,
|
||||
}
|
||||
|
||||
impl StrCheckCodePair {
|
||||
impl PatternPrefixPair {
|
||||
const EXPECTED_PATTERN: &'static str = "<FilePattern>:<CheckCode> pattern";
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for StrCheckCodePair {
|
||||
impl<'de> Deserialize<'de> for PatternPrefixPair {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
@@ -107,7 +111,7 @@ impl<'de> Deserialize<'de> for StrCheckCodePair {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for StrCheckCodePair {
|
||||
impl FromStr for PatternPrefixPair {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
@@ -118,8 +122,8 @@ impl FromStr for StrCheckCodePair {
|
||||
}
|
||||
(tokens[0].trim(), tokens[1].trim())
|
||||
};
|
||||
let code = CheckCodePrefix::from_str(code_string)?;
|
||||
let pattern = pattern_str.into();
|
||||
Ok(Self { pattern, code })
|
||||
let prefix = CheckCodePrefix::from_str(code_string)?;
|
||||
Ok(Self { pattern, prefix })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
//! Structs to render user-facing settings.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::settings::types::{FilePattern, PythonVersion};
|
||||
use crate::{flake8_quotes, pep8_naming, Configuration};
|
||||
|
||||
/// Struct to render user-facing exclusion patterns.
|
||||
@@ -41,7 +42,7 @@ pub struct UserConfiguration {
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
pub target_version: PythonVersion,
|
||||
// Plugins
|
||||
|
||||
41
src/snapshots/ruff__linter__tests__F821_F821_1.py.snap
Normal file
41
src/snapshots/ruff__linter__tests__F821_F821_1.py.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 11
|
||||
column: 9
|
||||
end_location:
|
||||
row: 11
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 18
|
||||
column: 16
|
||||
end_location:
|
||||
row: 18
|
||||
column: 23
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 24
|
||||
column: 12
|
||||
end_location:
|
||||
row: 24
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 30
|
||||
column: 10
|
||||
end_location:
|
||||
row: 30
|
||||
column: 17
|
||||
fix: ~
|
||||
|
||||
Reference in New Issue
Block a user