Compare commits

...

97 Commits

Author SHA1 Message Date
Charlie Marsh
e9a236f740 Bump version to 0.0.209 2023-01-03 08:27:28 -05:00
Ran Benita
ebb31dc29b Fix PT006 autofix of parametrize name strings like ' first, , second ' (#1591) 2023-01-03 08:12:09 -05:00
Charlie Marsh
b9e92affb1 Avoid silently dropping code generator errors (#1598) 2023-01-03 08:11:18 -05:00
Charlie Marsh
68fbd0f029 Preserve style when generating flake8-simplify messages (#1599) 2023-01-03 08:05:55 -05:00
Charlie Marsh
8d01efb571 Avoid hard unwrap in PT checks (#1597) 2023-01-03 07:39:52 -05:00
Pedram Navid
da5a25b421 Add autofix for SIM300 (#1588) 2023-01-03 07:19:04 -05:00
Charlie Marsh
bfdab4ac94 Add flake8-pytest-style settings to hash (#1595) 2023-01-03 07:12:33 -05:00
jvstme
d1389894a4 Fix several typos in README (#1590) 2023-01-03 07:06:43 -05:00
Charlie Marsh
8b277138de Bump version to 0.0.208 2023-01-02 23:19:03 -05:00
Charlie Marsh
e0fe34c523 Implement and-false and or-true rules (#1586) 2023-01-02 23:10:42 -05:00
Charlie Marsh
995fee5ddd Increment flake8-pie implementation count 2023-01-02 22:47:05 -05:00
Charlie Marsh
99906c16db Prefer GitHub icon on mobile (#1585) 2023-01-02 22:46:23 -05:00
Charlie Marsh
b6cb35414e Swap accent color for playground (#1584) 2023-01-02 22:44:09 -05:00
Harutaka Kawamura
b351221049 Mark FStringMissingPlaceholders as fixable (#1582) 2023-01-02 22:41:40 -05:00
Charlie Marsh
afb6f55b8d Add a link to GitHub from the playground (#1583) 2023-01-02 22:40:38 -05:00
Harutaka Kawamura
03a8ece954 Implement autofix for F541 (#1577) 2023-01-02 22:28:32 -05:00
Charlie Marsh
8aeec35bfb Implement dupe-class-field-definitions (#1581) 2023-01-02 22:26:01 -05:00
Charlie Marsh
93259acb31 Implement unnecessary-pass-statement (#1580) 2023-01-02 22:15:24 -05:00
Charlie Marsh
ca7fe686d5 Add scripts to generate plugin and check boilerplate (#1579) 2023-01-02 22:10:31 -05:00
Charlie Marsh
5dd9e99a4b Add flake8-pie plugin with prefer_list_builtin (#1578) 2023-01-02 21:47:38 -05:00
Charlie Marsh
8f0270acfe Remove registry_gen.rs at root 2023-01-02 21:31:19 -05:00
Charlie Marsh
7ce38840a2 Re-run registry_gen.rs generation 2023-01-02 21:29:53 -05:00
Charlie Marsh
8ab8217ca5 Fix destination for registry_gen.rs 2023-01-02 21:29:08 -05:00
Charlie Marsh
5d3ff69053 Add comment annotations to plugin match 2023-01-02 20:49:30 -05:00
Charlie Marsh
726399b2b3 Move Ruff checks to the end of each list 2023-01-02 20:44:27 -05:00
Charlie Marsh
8329237f19 Warn user when D203 and D211 are enabled (#1576) 2023-01-02 19:54:55 -05:00
Oliver Margetts
cd5882c66d Remove need for vendored format/cformat code (#1573) 2023-01-02 19:37:31 -05:00
Charlie Marsh
0c05488740 Automatically set baseline D codes based on convention (#1574) 2023-01-02 19:08:56 -05:00
Charlie Marsh
1425b21d93 Avoid invalid trailing comma fixes for mock rewrites (#1570) 2023-01-02 18:03:43 -05:00
Charlie Marsh
e5a59f41b0 Remove extend- from docstring configuration examples (#1571) 2023-01-02 17:53:41 -05:00
Charlie Marsh
8647bec3cb Rename checks.rs to registry.rs (#1566) 2023-01-02 17:26:51 -05:00
Charlie Marsh
14042800c2 Remove common-path dependency (#1565) 2023-01-02 17:23:29 -05:00
Charlie Marsh
21986e89fd Always check directly-passed-in files (#1564) 2023-01-02 16:49:44 -05:00
Edgar R. M
c4014ef2d3 Implement flake8-pytest-style (#1506) 2023-01-02 16:34:17 -05:00
Charlie Marsh
9ffd20707f Avoid PEP 604 rewrites for runtime annotations (#1563) 2023-01-02 16:33:52 -05:00
Charlie Marsh
6d5aa344a1 Avoid merging import from statements with inline comments (#1562) 2023-01-02 16:24:41 -05:00
Colin Delahunty
e9be5fc7be Add typo linter (#1553) 2023-01-02 15:57:59 -05:00
Charlie Marsh
f74050e5b1 Bump version to 0.0.207 2023-01-02 14:39:32 -05:00
Martin Fischer
90b2d85c85 Fix __init__.py being private (#1556)
Previously visibility::module_visibility() returned Private
for any module name starting with an underscore, resulting in
__init__.py being categorized as private, which in turn resulted
in D104 (Missing docstring in public package) never being reported
for __init__.py files.
2023-01-02 14:39:23 -05:00
Charlie Marsh
ccf848705d Detect unpacking assignments in eradicate (#1559) 2023-01-02 14:05:58 -05:00
Charlie Marsh
3b535fcc74 Add explicit new-rule recommendation in CONTRIBUTING.md (#1558) 2023-01-02 13:50:11 -05:00
Víctor
06321fd240 Add usage clarification to README (#1557) 2023-01-02 13:40:16 -05:00
Martin Fischer
cdae2f0e67 Fix typing::match_annotated_subscript matching ExprKind::Call (#1554) 2023-01-02 12:13:45 -05:00
Martin Fischer
f52691a90a Print warning when running debug builds without --no-cache (#1549) 2023-01-02 12:12:04 -05:00
Pedram Navid
07e47bef4b Add flake8-simplify SIM300 check for Yoda Conditions (#1539) 2023-01-01 18:37:40 -05:00
Anders Kaseorg
86b61806a5 Correct UP027 message to “generator expression” (#1540) 2023-01-01 18:30:58 -05:00
Charlie Marsh
31ce37dd8e Avoid PD false positives on some non-DataFrame expressions (#1538) 2023-01-01 17:05:57 -05:00
Charlie Marsh
2cf6d05586 Avoid triggering PD errors on method calls (#1537) 2023-01-01 17:00:17 -05:00
Colin Delahunty
65c34c56d6 Implement list-to-tuple comprehension unpacking (#1534) 2023-01-01 16:53:26 -05:00
Charlie Marsh
2315db7d13 Bump version to 0.0.206 2023-01-01 16:39:29 -05:00
Charlie Marsh
f1a183c171 Rewrite mock.mock attribute accesses (#1533) 2023-01-01 13:14:09 -05:00
Harutaka Kawamura
509c6d5ec7 Add visit_format_spec to avoid false positives for F541 in f-string format specifier (#1528) 2023-01-01 13:03:32 -05:00
Maksudul Haque
6695988b59 Do not Change Quotation Style for SIM118 Autofix (#1529) 2023-01-01 12:53:46 -05:00
Harutaka Kawamura
e3867b172d Simplify unused snapshot check (#1525) 2023-01-01 02:43:07 -05:00
Harutaka Kawamura
4b8e30f350 Fix Name node range in NamedExpr node (#1526) 2023-01-01 02:41:49 -05:00
Charlie Marsh
8fd0d8e9d8 Bump pyupgrade implementation count 2022-12-31 21:25:34 -05:00
Colin Delahunty
70895a8f1e Pyupgrade: import mock to from unittest import mock (#1488) 2022-12-31 21:25:06 -05:00
Charlie Marsh
f2c9f94f73 Avoid some false positives for ends-in-period checks (#1521) 2022-12-31 18:38:22 -05:00
Charlie Marsh
605c6069e2 Ignore property assignments in RET504 (#1520) 2022-12-31 18:04:13 -05:00
Charlie Marsh
92c2981b6d Add dark mode variant for benchmark image (#1519) 2022-12-31 17:47:32 -05:00
Colin Delahunty
4ad8db3d61 Pyupgrade: Turn errors into OSError (#1434) 2022-12-31 16:36:05 -05:00
Charlie Marsh
0e8c237167 Bump version to 0.0.205 2022-12-31 13:44:39 -05:00
Harutaka Kawamura
960c5e2006 Use more precise error ranges for names (#1513) 2022-12-31 13:42:39 -05:00
Charlie Marsh
9ba17fbf92 Avoid flagging nested f-strings (#1516) 2022-12-31 13:41:21 -05:00
Charlie Marsh
bfdf972a5d Add code kind to Quick Fix action 2022-12-31 10:26:47 -05:00
Charlie Marsh
0c215365ae Bump version to 0.0.204 2022-12-31 08:20:09 -05:00
Maksudul Haque
815284f890 Check for Unsupported Files and Display Errors and Warnings (#1509) 2022-12-31 08:12:55 -05:00
Charlie Marsh
6880338a9a Restore pyproject.toml 2022-12-31 08:06:08 -05:00
Charlie Marsh
68b749c67d Remove foo directory 2022-12-31 08:05:04 -05:00
Reiner Gerecke
c0fc55b812 Generate source code with detected line ending (#1487) 2022-12-31 08:02:29 -05:00
Reiner Gerecke
ba9cf70917 Adjust test_path helper to detect round-trip autofix issues (#1501) 2022-12-31 08:02:13 -05:00
Harutaka Kawamura
f73dfbbfd3 Fix E722 and F707 ranges (#1508) 2022-12-31 07:58:46 -05:00
Reiner Gerecke
62c273cd22 Include fix commit message when showing violations together with source (#1505) 2022-12-31 07:54:41 -05:00
Harutaka Kawamura
938ad9a39e Fix N818 range (#1503) 2022-12-31 07:43:03 -05:00
Harutaka Kawamura
14248cb8cb Improve PLW0120 range (#1500) 2022-12-31 07:42:49 -05:00
Harutaka Kawamura
3a280039e1 Improve F811 range for function and class definitions (#1499) 2022-12-31 07:42:18 -05:00
Harutaka Kawamura
4e9e58bdc0 Improve T20X ranges (#1502) 2022-12-31 07:41:53 -05:00
Harutaka Kawamura
926b5494ad Remove unused snapshots (#1497) 2022-12-31 07:40:38 -05:00
Reiner Gerecke
6717b48ca5 Fix detection of changed imports in isort plugin (#1504) 2022-12-31 07:37:27 -05:00
Harutaka Kawamura
f7bb5bc858 Remove F831 (#1495) 2022-12-30 23:57:51 -05:00
Harutaka Kawamura
3e23fd1487 Stop overriding locations for expressions within f-strings (#1494) 2022-12-30 23:43:59 -05:00
Charlie Marsh
01c74e0629 Add a "fix message" to every autofix-able check (#1489) 2022-12-30 23:16:03 -05:00
Charlie Marsh
1e3cf87f67 Escape strings when formatting check messages (#1493) 2022-12-30 22:11:01 -05:00
Charlie Marsh
248447e139 Trim CLI help during generation (#1492) 2022-12-30 22:03:58 -05:00
Charlie Marsh
95f139583a Modify pyproject.toml to meet schema compliance 2022-12-30 15:51:35 -05:00
Charlie Marsh
74903f23d6 Bump version to 0.0.203 2022-12-30 15:33:30 -05:00
Charlie Marsh
3ee20a70d3 Remove lingering ruff_options.ts references 2022-12-30 15:33:09 -05:00
Charlie Marsh
4c2fbb7ac0 Remove hidden autoformat command (#1486) 2022-12-30 15:32:05 -05:00
Charlie Marsh
7a66f98590 Move some argument validation into Clap (#1485) 2022-12-30 15:29:41 -05:00
Charlie Marsh
a2bf3916f3 Make clean a standalone command 2022-12-30 15:20:18 -05:00
Reiner Gerecke
c9aa7b9308 Generate the README's --help output automatically via cargo +nightly dev generate-all (#1483) 2022-12-30 15:06:32 -05:00
Charlie Marsh
d880ca6cc6 Add a command to clear the Ruff cache (#1484) 2022-12-30 13:52:16 -05:00
Reiner Gerecke
080f99b908 Detect line endings and use them during code generation (#1482) 2022-12-30 12:59:40 -05:00
Charlie Marsh
a7dc491ff1 Fix clippy 2022-12-30 12:34:43 -05:00
Charlie Marsh
cdc8f8c91a Remove support for ur prefixes (#1481) 2022-12-30 11:21:05 -05:00
Colin Delahunty
138c46e793 Simplified code for unicode fix (#1475) 2022-12-30 11:18:52 -05:00
Charlie Marsh
a86c57a832 Support multi-line noqa directives for 'import from' (#1479) 2022-12-30 11:16:50 -05:00
757 changed files with 14828 additions and 4338 deletions

View File

@@ -38,10 +38,9 @@ jobs:
- run: cargo build --all --release
- run: ./target/release/ruff_dev generate-all
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo +nightly dev generate-all'."
- run: git diff --quiet src/checks_gen.rs || echo "::error file=src/checks_gen.rs::This file is outdated. Run 'cargo +nightly dev generate-all'."
- run: git diff --quiet src/registry_gen.rs || echo "::error file=src/registry_gen.rs::This file is outdated. Run 'cargo +nightly dev generate-all'."
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo +nightly dev generate-all'."
- run: git diff --quiet playground/src/ruff_options.ts || echo "::error file=playground/src/ruff_options.ts::This file is outdated. Run 'cargo +nightly dev generate-all'."
- run: git diff --exit-code -- README.md src/checks_gen.rs ruff.schema.json playground/src/ruff_options.ts
- run: git diff --exit-code -- README.md src/registry_gen.rs ruff.schema.json
cargo-fmt:
name: "cargo fmt"
@@ -117,8 +116,12 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: cargo install cargo-insta
- run: pip install black[d]==22.12.0
- run: cargo test --all
- name: Run tests
run: |
cargo insta test --all --delete-unreferenced-snapshots
git diff --exit-code
- run: cargo test --package ruff --test black_compatibility_test -- --ignored
# TODO(charlie): Re-enable the `wasm-pack` tests.
@@ -178,3 +181,15 @@ jobs:
${{ runner.os }}-build-
${{ runner.os }}-
- run: maturin build -b bin
typos:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v2
- name: Check spelling of file.txt
uses: crate-ci/typos@master
with:
files: .

1
.gitignore vendored
View File

@@ -181,3 +181,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
.vimspector.json

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.202
rev: v0.0.209
hooks:
- id: ruff

View File

@@ -9,6 +9,15 @@ free to submit a PR. For larger changes (e.g., new lint rules, new functionality
options), consider submitting an [Issue](https://github.com/charliermarsh/ruff/issues) outlining
your proposed change.
If you're looking for a place to start, we recommend implementing a new lint rule (see:
[_Adding a new lint rule_](#example-adding-a-new-lint-rule), which will allow you to learn from and
pattern-match against the examples in the existing codebase. Many lint rules are inspired by
existing Python plugins, which can be used as a reference implementation.
As a concrete example: consider taking on one of the rules in [`flake8-simplify`](https://github.com/charliermarsh/ruff/issues/998),
and looking to the originating [Python source](https://github.com/MartinThoma/flake8-simplify) for
guidance.
### Prerequisites
Ruff is written in Rust. You'll need to install the
@@ -65,21 +74,15 @@ understand how other, similar rules are implemented.
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run Ruff locally
with (e.g.) `cargo run resources/test/fixtures/E402.py --no-cache --select E402`. Once you're satisfied with the
output, codify the behavior as a snapshot test by adding a new `testcase` macro to the `mod tests`
section of `src/linter.rs`, like so:
non-violations designed to evaluate and demonstrate the behavior of your lint rule.
```rust
use test_case::test_case;
Run `cargo +nightly dev generate-all` to generate the code for your new fixture. Then run Ruff
locally with (e.g.) `cargo run resources/test/fixtures/E402.py --no-cache --select E402`.
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
...
```
Then, run `cargo test`. Your test will fail, but you'll be prompted to follow-up with
`cargo insta review`. Accept the generated snapshot, then commit the snapshot file alongside the
rest of your changes.
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
`test_case` macro in the relevant `src/[test-suite-name]/mod.rs` file. Then, run `cargo test`. Your
test will fail, but you'll be prompted to follow-up with `cargo insta review`. Accept the generated
snapshot, then commit the snapshot file alongside the rest of your changes.
Finally, regenerate the documentation and generated code with `cargo +nightly dev generate-all`.

39
Cargo.lock generated
View File

@@ -356,9 +356,9 @@ dependencies = [
[[package]]
name = "clearscreen"
version = "1.0.10"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c969a6b6dadff9f3349b1f783f553e2411104763ca4789e1c6ca6a41f46a57b0"
checksum = "41aa24cc5e1d6b3fc49ad4cd540b522fedcbe88bc6f259ff16e20e7010b6f8c7"
dependencies = [
"nix",
"terminfo",
@@ -397,12 +397,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "common-path"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101"
[[package]]
name = "configparser"
version = "3.0.2"
@@ -750,7 +744,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.202-dev.0"
version = "0.0.209-dev.0"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1274,13 +1268,14 @@ checksum = "d906846a98739ed9d73d66e62c2641eef8321f1734b7a1156ab045a0248fb2b3"
[[package]]
name = "nix"
version = "0.24.3"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"libc",
"static_assertions",
]
[[package]]
@@ -1878,7 +1873,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.202"
version = "0.0.209"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1893,7 +1888,6 @@ dependencies = [
"clap_complete_command",
"clearscreen",
"colored",
"common-path",
"console_error_panic_hook",
"console_log",
"criterion",
@@ -1913,6 +1907,7 @@ dependencies = [
"nohash-hasher",
"notify",
"num-bigint",
"num-traits",
"once_cell",
"path-absolutize",
"quick-junit",
@@ -1946,7 +1941,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.202"
version = "0.0.209"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1967,7 +1962,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.202"
version = "0.0.209"
dependencies = [
"proc-macro2",
"quote",
@@ -2010,7 +2005,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
source = "git+https://github.com/RustPython/RustPython.git?rev=4d53c7cb27c0379adf8b51c4d3d0d2174f41d590#4d53c7cb27c0379adf8b51c4d3d0d2174f41d590"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2020,11 +2015,13 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
source = "git+https://github.com/RustPython/RustPython.git?rev=4d53c7cb27c0379adf8b51c4d3d0d2174f41d590#4d53c7cb27c0379adf8b51c4d3d0d2174f41d590"
dependencies = [
"ascii",
"bitflags",
"cfg-if 1.0.0",
"hexf-parse",
"itertools",
"lexical-parse-float",
"libc",
"lock_api",
@@ -2043,7 +2040,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
source = "git+https://github.com/RustPython/RustPython.git?rev=4d53c7cb27c0379adf8b51c4d3d0d2174f41d590#4d53c7cb27c0379adf8b51c4d3d0d2174f41d590"
dependencies = [
"bincode",
"bitflags",
@@ -2060,7 +2057,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=68d26955b3e24198a150315e7959719b03709dee#68d26955b3e24198a150315e7959719b03709dee"
source = "git+https://github.com/RustPython/RustPython.git?rev=4d53c7cb27c0379adf8b51c4d3d0d2174f41d590#4d53c7cb27c0379adf8b51c4d3d0d2174f41d590"
dependencies = [
"ahash",
"anyhow",
@@ -2638,9 +2635,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "update-informer"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f154aee470c0882ea0f3b1cc2a46c5f4d24f282655f7b0cec065614fe24c447f"
checksum = "152ff185ca29f7f487c51ca785b0f1d85970c4581f4cdd12ed499227890200f5"
dependencies = [
"directories",
"semver",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.202"
version = "0.0.209"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -32,7 +32,6 @@ chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
clap = { version = "4.0.1", features = ["derive"] }
clap_complete_command = { version = "0.4.0" }
colored = { version = "2.0.0" }
common-path = { version = "1.0.0" }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
filetime = { version = "0.2.17" }
@@ -46,16 +45,17 @@ natord = { version = "1.0.9" }
nohash-hasher = { version = "0.2.0" }
notify = { version = "5.0.0" }
num-bigint = { version = "0.4.3" }
num-traits = "0.2.15"
once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
quick-junit = { version = "0.3.2" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.202", path = "ruff_macros" }
ruff_macros = { version = "0.0.209", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }
schemars = { version = "0.8.11" }
semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }
@@ -70,9 +70,9 @@ toml = { version = "0.5.9" }
walkdir = { version = "2.3.2" }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
clearscreen = { version = "1.0.10" }
clearscreen = { version = "2.0.0" }
rayon = { version = "1.5.3" }
update-informer = { version = "0.5.0", default-features = false, features = ["pypi"], optional = true }
update-informer = { version = "0.6.0", default-features = false, features = ["pypi"], optional = true }
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
# For (future) wasm-pack support

377
README.md
View File

@@ -8,7 +8,11 @@
An extremely fast Python linter, written in Rust.
<p align="center">
<img alt="Bar chart with benchmark results" src="https://user-images.githubusercontent.com/1309177/187504482-6d9df992-a81d-4e86-9f6a-d958741c8182.svg">
<picture align="center">
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1309177/210156880-a97c2a0d-2c03-4393-8695-36547935a94e.svg">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1309177/210156881-a88fd142-5008-4695-9407-d028cec3eff7.svg">
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/210156881-a88fd142-5008-4695-9407-d028cec3eff7.svg">
</picture>
</p>
<p align="center">
@@ -22,8 +26,9 @@ An extremely fast Python linter, written in Rust.
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
- 🌎 Monorepo-friendly configuration via hierarchical and cascading settings
- 🔌 Native re-implementations of dozens of Flake8 plugins, like [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
- ⌨️ First-party editor integrations for [VS Code](https://github.com/charliermarsh/ruff-vscode) and [more](https://github.com/charliermarsh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](#pyprojecttoml-discovery)
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface.
@@ -98,6 +103,7 @@ of [Conda](https://docs.conda.io/en/latest/):
1. [flake8-implicit-str-concat (ISC)](#flake8-implicit-str-concat-isc)
1. [flake8-import-conventions (ICN)](#flake8-import-conventions-icn)
1. [flake8-print (T20)](#flake8-print-t20)
1. [flake8-pytest-style (PT)](#flake8-pytest-style-pt)
1. [flake8-quotes (Q)](#flake8-quotes-q)
1. [flake8-return (RET)](#flake8-return-ret)
1. [flake8-simplify (SIM)](#flake8-simplify-sim)
@@ -108,6 +114,7 @@ of [Conda](https://docs.conda.io/en/latest/):
1. [pandas-vet (PD)](#pandas-vet-pd)
1. [pygrep-hooks (PGH)](#pygrep-hooks-pgh)
1. [Pylint (PLC, PLE, PLR, PLW)](#pylint-plc-ple-plr-plw)
1. [flake8-pie (PIE)](#flake8-pie-pie)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf)<!-- End auto-generated table of contents. -->
1. [Editor Integrations](#editor-integrations)
1. [FAQ](#faq)
@@ -153,9 +160,9 @@ pacman -S ruff
To run Ruff, try any of the following:
```shell
ruff path/to/code/to/check.py
ruff path/to/code/
ruff path/to/code/*.py
ruff path/to/code/to/check.py # Run Ruff over `check.py`
ruff path/to/code/ # Run Ruff over all files in `/path/to/code` (and any subdirectories)
ruff path/to/code/*.py # Run Ruff over all `.py` files in `/path/to/code`
```
You can run Ruff in `--watch` mode to automatically re-run on-change:
@@ -169,7 +176,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.202'
rev: 'v0.0.209'
hooks:
- id: ruff
# Respect `exclude` and `extend-exclude` settings.
@@ -270,7 +277,7 @@ alternative docstring formats. Enabling `ALL` without further configuration may
behavior, especially for the `pydocstyle` plugin.
As an alternative to `pyproject.toml`, Ruff will also respect a `ruff.toml` file, which implements
an equivalent schema (though the `[tool.ruff]` hierarchy can be omitted). For example, the above
an equivalent schema (though the `[tool.ruff]` hierarchy can be omitted). For example, the
`pyproject.toml` described above would be represented via the following `ruff.toml`:
```toml
@@ -300,6 +307,7 @@ ruff path/to/code/ --select F401 --select F403
See `ruff --help` for more:
<!-- Begin auto-generated cli help. -->
```shell
Ruff: An extremely fast Python linter.
@@ -349,6 +357,10 @@ Options:
List of mappings from file pattern to code to exclude
--format <FORMAT>
Output serialization format for error messages [possible values: text, json, junit, grouped, github, gitlab]
--stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin
--cache-dir <CACHE_DIR>
Path to the cache directory
--show-source
Show violations with source code
--respect-gitignore
@@ -357,12 +369,6 @@ Options:
Enforce exclusions, even for paths passed to Ruff directly on the command-line
--update-check
Enable or disable automatic update checks
--show-files
See the files Ruff will be run against with the current settings
--show-settings
See the settings Ruff will use to check a given Python file
--add-noqa
Enable automatic additions of `noqa` directives to failing lines
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
Regular expression matching the name of dummy variables
--target-version <TARGET_VERSION>
@@ -371,17 +377,22 @@ Options:
Set the line-length for length-associated checks and automatic formatting
--max-complexity <MAX_COMPLEXITY>
Maximum McCabe complexity allowed for a given function
--stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin
--add-noqa
Enable automatic additions of `noqa` directives to failing lines
--clean
Clear any caches in the current directory or any subdirectories
--explain <EXPLAIN>
Explain a rule
--cache-dir <CACHE_DIR>
Path to the cache directory
--show-files
See the files Ruff will be run against with the current settings
--show-settings
See the settings Ruff will use to check a given Python file
-h, --help
Print help information
-V, --version
Print version information
```
<!-- End auto-generated cli help. -->
### `pyproject.toml` discovery
@@ -393,7 +404,7 @@ directory hierarchy is used for every individual file, with all paths in the `py
There are a few exceptions to these rules:
1. In locating the "closest" `pyproject.toml` file for a given path, Ruff ignore any
1. In locating the "closest" `pyproject.toml` file for a given path, Ruff ignores any
`pyproject.toml` files that lack a `[tool.ruff]` section.
2. If a configuration file is passed directly via `--config`, those settings are used for across
files. Any relative paths in that configuration file (like `exclude` globs or `src` paths) are
@@ -438,7 +449,7 @@ For example, `ruff /path/to/excluded/file.py` will always check `file.py`.
### Ignoring errors
To omit a lint check entirely, add it to the "ignore" list via [`ignore`](#ignore) or
[`extend-ignore`](#extend-ignore), either on the command-line or in your `project.toml` file.
[`extend-ignore`](#extend-ignore), either on the command-line or in your `pyproject.toml` file.
To ignore an error inline, Ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
To ignore an individual error, add `# noqa: {code}` to the end of the line, like so:
@@ -535,13 +546,13 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
| F523 | StringDotFormatExtraPositionalArguments | '...'.format(...) has unused arguments at position(s): ... | |
| F524 | StringDotFormatMissingArguments | '...'.format(...) is missing argument(s) for placeholder(s): ... | |
| F525 | StringDotFormatMixingAutomatic | '...'.format(...) mixes automatic and manual numbering | |
| F541 | FStringMissingPlaceholders | f-string without any placeholders | |
| F541 | FStringMissingPlaceholders | f-string without any placeholders | 🛠 |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | |
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | |
| F622 | TwoStarredExpressions | Two starred expressions in assignment | |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | |
| F632 | IsLiteral | Use `==` and `!=` to compare constant literals | 🛠 |
| F632 | IsLiteral | Use `==` to compare constant literals | 🛠 |
| F633 | InvalidPrintSyntax | Use of `>>` is invalid with `print` function | |
| F634 | IfTuple | If test is a tuple, which is always `True` | |
| F701 | BreakOutsideLoop | `break` outside loop | |
@@ -554,7 +565,6 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
| F821 | UndefinedName | Undefined name `...` | |
| F822 | UndefinedExport | Undefined name `...` in `__all__` | |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment | |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | |
| F842 | UnusedAnnotation | Local variable `...` is annotated but never used | |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | 🛠 |
@@ -574,7 +584,7 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
| E714 | NotIsTest | Test for object identity should be `is not` | 🛠 |
| E721 | TypeComparison | Do not compare types, use `isinstance()` | |
| E722 | DoNotUseBareExcept | Do not use bare `except` | |
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def | 🛠 |
| E731 | DoNotAssignLambda | Do not assign a `lambda` expression, use a `def` | 🛠 |
| E741 | AmbiguousVariableName | Ambiguous variable name: `...` | |
| E742 | AmbiguousClassName | Ambiguous class name: `...` | |
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | |
@@ -659,7 +669,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| ---- | ---- | ------- | --- |
| UP001 | UselessMetaclassType | `__metaclass__ = type` is implied | 🛠 |
| UP003 | TypeOfPrimitive | Use `str` instead of `type(...)` | 🛠 |
| UP004 | UselessObjectInheritance | Class `...` inherits from object | 🛠 |
| UP004 | UselessObjectInheritance | Class `...` inherits from `object` | 🛠 |
| UP005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` | 🛠 |
| UP006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 |
| UP007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
@@ -673,13 +683,16 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 |
| UP016 | RemoveSixCompat | Unnecessary `six` compatibility usage | 🛠 |
| UP017 | DatetimeTimezoneUTC | Use `datetime.UTC` alias | 🛠 |
| UP018 | NativeLiterals | Unnecessary call to `str` and `bytes` | 🛠 |
| UP018 | NativeLiterals | Unnecessary call to `str` | 🛠 |
| UP019 | TypingTextStrAlias | `typing.Text` is deprecated, use `str` | 🛠 |
| UP020 | OpenAlias | Use builtin `open` | 🛠 |
| UP021 | ReplaceUniversalNewlines | `universal_newlines` is deprecated, use `text` | 🛠 |
| UP022 | ReplaceStdoutStderr | Sending stdout and stderr to pipe is deprecated, use `capture_output` | 🛠 |
| UP023 | RewriteCElementTree | `cElementTree` is deprecated, use `ElementTree` | 🛠 |
| UP024 | OSErrorAlias | Replace aliased errors with `OSError` | 🛠 |
| UP025 | RewriteUnicodeLiteral | Remove unicode literals from strings | 🛠 |
| UP026 | RewriteMockImport | `mock` is deprecated, use `unittest.mock` | 🛠 |
| UP027 | RewriteListComprehension | Replace unpacked list comprehension with a generator expression | 🛠 |
### pep8-naming (N)
@@ -880,6 +893,38 @@ For more, see [flake8-print](https://pypi.org/project/flake8-print/5.0.0/) on Py
| T201 | PrintFound | `print` found | 🛠 |
| T203 | PPrintFound | `pprint` found | 🛠 |
### flake8-pytest-style (PT)
For more, see [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/1.6.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PT001 | IncorrectFixtureParenthesesStyle | Use `@pytest.fixture()` over `@pytest.fixture` | 🛠 |
| PT002 | FixturePositionalArgs | Configuration for fixture `...` specified via positional args, use kwargs | |
| PT003 | ExtraneousScopeFunction | `scope='function'` is implied in `@pytest.fixture()` | |
| PT004 | MissingFixtureNameUnderscore | Fixture `...` does not return anything, add leading underscore | |
| PT005 | IncorrectFixtureNameUnderscore | Fixture `...` returns a value, remove leading underscore | |
| PT006 | ParametrizeNamesWrongType | Wrong name(s) type in `@pytest.mark.parametrize`, expected `tuple` | 🛠 |
| PT007 | ParametrizeValuesWrongType | Wrong values type in `@pytest.mark.parametrize` expected `list` of `tuple` | |
| PT008 | PatchWithLambda | Use `return_value=` instead of patching with lambda | |
| PT009 | UnittestAssertion | Use a regular assert instead of unittest-style '...' | |
| PT010 | RaisesWithoutException | set the expected exception in `pytest.raises()` | |
| PT011 | RaisesTooBroad | `pytest.raises(...)` is too broad, set the `match` parameter or use a more specific exception | |
| PT012 | RaisesWithMultipleStatements | `pytest.raises()` block should contain a single simple statement | |
| PT013 | IncorrectPytestImport | Found incorrect import of pytest, use simple `import pytest` instead | |
| PT015 | AssertAlwaysFalse | Assertion always fails, replace with `pytest.fail()` | |
| PT016 | FailWithoutMessage | No message passed to `pytest.fail()` | |
| PT017 | AssertInExcept | Found assertion on exception ... in except block, use pytest.raises() instead | |
| PT018 | CompositeAssertion | Assertion should be broken down into multiple parts | |
| PT019 | FixtureParamWithoutValue | Fixture ... without value is injected as parameter, use @pytest.mark.usefixtures instead | |
| PT020 | DeprecatedYieldFixture | `@pytest.yield_fixture` is deprecated, use `@pytest.fixture` | |
| PT021 | FixtureFinalizerCallback | Use `yield` instead of `request.addfinalizer` | |
| PT022 | UselessYieldFixture | No teardown in fixture ..., use `return` instead of `yield` | |
| PT023 | IncorrectMarkParenthesesStyle | Use `@pytest.mark....` over `@pytest.mark....()` | 🛠 |
| PT024 | UnnecessaryAsyncioMarkOnFixture | `pytest.mark.asyncio` is unnecessary for fixtures | |
| PT025 | ErroneousUseFixturesOnFixture | `pytest.mark.usefixtures` has no effect on fixtures | |
| PT026 | UseFixturesWithoutParameters | Useless `pytest.mark.usefixtures` without parameters | 🛠 |
### flake8-quotes (Q)
For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/3.3.1/) on PyPI.
@@ -913,6 +958,9 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/0.19.3/
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| SIM118 | KeyInDict | Use `key in dict` instead of `key in dict.keys()` | 🛠 |
| SIM222 | OrTrue | Use `True` instead of `... or True` | 🛠 |
| SIM223 | AndFalse | Use `False` instead of `... and False` | 🛠 |
| SIM300 | YodaConditions | Use `left == right` instead of `right == left (Yoda-conditions)` | 🛠 |
### flake8-tidy-imports (TID)
@@ -1008,13 +1056,23 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| PLW0120 | UselessElseOnLoop | Else clause on loop without a break statement, remove the else and de-indent all the code inside it | |
| PLW0602 | GlobalVariableNotAssigned | Using global for `...` but no assignment is done | |
### flake8-pie (PIE)
For more, see [flake8-pie](https://pypi.org/project/flake8-pie/0.16.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PIE790 | NoUnnecessaryPass | Unnecessary `pass` statement | 🛠 |
| PIE794 | DupeClassFieldDefinitions | Class field `...` is defined multiple times | 🛠 |
| PIE807 | PreferListBuiltin | Prefer `list()` over useless lambda | 🛠 |
### Ruff-specific rules (RUF)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | |
| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF004 | KeywordArgumentBeforeStarArgument | Keyword argument `...` must come after starred arguments | |
| RUF100 | UnusedNOQA | Unused blanket `noqa` directive | 🛠 |
@@ -1287,10 +1345,11 @@ natively, including:
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
- [`flake8-implicit-str-concat`](https://pypi.org/project/flake8-implicit-str-concat/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-pie`](https://pypi.org/project/flake8-pie/) (3/7)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-return`](https://pypi.org/project/flake8-return/)
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (1/37)
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (4/37)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`isort`](https://pypi.org/project/isort/)
@@ -1298,7 +1357,7 @@ natively, including:
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (20/33)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (21/33)
- [`yesqa`](https://github.com/asottile/yesqa)
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
@@ -1344,10 +1403,11 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
- [`flake8-implicit-str-concat`](https://pypi.org/project/flake8-implicit-str-concat/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-pie`](https://pypi.org/project/flake8-pie/) (3/7)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-return`](https://pypi.org/project/flake8-return/)
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (1/37)
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (4/37)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`mccabe`](https://pypi.org/project/mccabe/)
@@ -1357,9 +1417,9 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
Ruff can also replace [`isort`](https://pypi.org/project/isort/),
[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/),
[`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10), and a subset of the rules
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (20/33).
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (21/33).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, feel free to file an Issue.
### Do I need to install Rust to use Ruff?
@@ -1422,59 +1482,49 @@ Found 3 error(s).
### Does Ruff support NumPy- or Google-style docstrings?
Yes! To enable a specific docstring convention, start by enabling all `pydocstyle` error codes, and
then selectively disabling based on your [preferred convention](https://www.pydocstyle.org/en/latest/error_codes.html#default-conventions).
For example, if you're coming from `flake8-docstrings`, the following configuration is equivalent to
`--docstring-convention=numpy`:
Yes! To enable specific docstring convention, add the following to your `pyproject.toml`:
```toml
[tool.ruff]
extend-select = ["D"]
extend-ignore = [
"D107",
"D203",
"D212",
"D213",
"D402",
"D413",
"D415",
"D416",
"D417",
]
[tool.ruff.pydocstyle]
convention = "google" # Accepts: "google", "numpy", or "pep257".
```
Similarly, the following is equivalent to `--docstring-convention=google`:
For example, if you're coming from `flake8-docstrings`, and your originating configuration uses
`--docstring-convention=numpy`, you'd instead set `convention = "numpy"` in your `pyproject.toml`,
as above.
Note that setting a `convention` is equivalent to adding that convention's specific set of codes to
your `select`. For example, `convention = "numpy"` is equivalent to:
```toml
[tool.ruff]
extend-select = ["D"]
extend-ignore = [
"D203",
# Enable all `D` errors except `D107`, `D203`, `D212`, `D213`, `D402`, `D413`, `D415`, `D416`,
# and `D417`.
select = [
"D100",
"D101",
"D102",
"D103",
"D104",
"D105",
"D106",
"D200",
"D201",
"D202",
"D204",
"D213",
"D215",
"D400",
"D404",
"D406",
"D407",
"D408",
"D409",
"D413",
]
```
Similarly, the following is equivalent to `--docstring-convention=pep8`:
```toml
[tool.ruff]
extend-select = ["D"]
extend-ignore = [
"D203",
"D212",
"D213",
"D205",
"D206",
"D207",
"D208",
"D209",
"D210",
"D211",
"D214",
"D215",
"D300",
"D301",
"D400",
"D403",
"D404",
"D405",
"D406",
@@ -1483,24 +1533,16 @@ extend-ignore = [
"D409",
"D410",
"D411",
"D413",
"D415",
"D416",
"D417",
"D412",
"D414",
"D418",
"D419",
]
```
Note that Ruff _also_ supports a [`convention`](#convention) setting:
### How can I tell what settings Ruff is using to check my code?
```toml
[tool.ruff.pydocstyle]
convention = "google"
```
However, this setting is purely used to implement robust detection of Google and NumPy-style
sections, and thus avoid the [false negatives](https://github.com/PyCQA/pydocstyle/issues/459) seen
in `pydocstyle`; it does not affect which errors are enabled, which is driven by the `select` and
`ignore` settings, as described above.
Run `ruff /path/to/code.py --show-settings` to view the resolved settings for a given file.
## Development
@@ -1669,7 +1711,6 @@ Summary
<!-- Sections automatically generated by `cargo dev generate-options`. -->
<!-- Begin auto-generated options sections. -->
#### [`allowed-confusables`](#allowed-confusables)
A list of allowed "confusable" Unicode characters to ignore when
@@ -2398,6 +2439,157 @@ will be added to the `aliases` mapping.
---
### `flake8-pytest-style`
#### [`fixture-parentheses`](#fixture-parentheses)
Boolean flag specifying whether `@pytest.fixture()` without parameters
should have parentheses. If the option is set to `true` (the
default), `@pytest.fixture()` is valid and `@pytest.fixture` is an
error. If set to `false`, `@pytest.fixture` is valid and
`@pytest.fixture()` is an error.
**Default value**: `true`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-pytest-style]
fixture-parentheses = true
```
---
#### [`mark-parentheses`](#mark-parentheses)
Boolean flag specifying whether `@pytest.mark.foo()` without parameters
should have parentheses. If the option is set to `true` (the
default), `@pytest.mark.foo()` is valid and `@pytest.mark.foo` is an
error. If set to `false`, `@pytest.fixture` is valid and
`@pytest.mark.foo()` is an error.
**Default value**: `true`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-pytest-style]
mark-parentheses = true
```
---
#### [`parametrize-names-type`](#parametrize-names-type)
Expected type for multiple argument names in `@pytest.mark.parametrize`.
The following values are supported:
* `csv` — a comma-separated list, e.g.
`@pytest.mark.parametrize('name1,name2', ...)`
* `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'),
...)`
* `list` — e.g. `@pytest.mark.parametrize(['name1', 'name2'], ...)`
**Default value**: `tuple`
**Type**: `ParametrizeNameType`
**Example usage**:
```toml
[tool.ruff.flake8-pytest-style]
parametrize-names-type = "list"
```
---
#### [`parametrize-values-row-type`](#parametrize-values-row-type)
Expected type for each row of values in `@pytest.mark.parametrize` in
case of multiple parameters. The following values are supported:
* `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'),
[(1, 2), (3, 4)])`
* `list` — e.g. `@pytest.mark.parametrize(('name1', 'name2'), [[1, 2],
[3, 4]])`
**Default value**: `tuple`
**Type**: `ParametrizeValuesRowType`
**Example usage**:
```toml
[tool.ruff.flake8-pytest-style]
parametrize-values-row-type = "list"
```
---
#### [`parametrize-values-type`](#parametrize-values-type)
Expected type for the list of values rows in `@pytest.mark.parametrize`.
The following values are supported:
* `tuple` — e.g. `@pytest.mark.parametrize('name', (1, 2, 3))`
* `list` (default) — e.g. `@pytest.mark.parametrize('name', [1, 2, 3])`
**Default value**: `list`
**Type**: `ParametrizeValuesType`
**Example usage**:
```toml
[tool.ruff.flake8-pytest-style]
parametrize-values-type = "tuple"
```
---
#### [`raises-extend-require-match-for`](#raises-extend-require-match-for)
List of additional exception names that require a match= parameter in a
`pytest.raises()` call. This extends the default list of exceptions
that require a match= parameter.
This option is useful if you want to extend the default list of
exceptions that require a match= parameter without having to specify
the entire list.
Note that this option does not remove any exceptions from the default
list.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.flake8-pytest-style]
raises-extend-require-match-for = ["requests.RequestException"]
```
---
#### [`raises-require-match-for`](#raises-require-match-for)
List of exception names that require a match= parameter in a
`pytest.raises()` call.
**Default value**: `["BaseException", "Exception", "ValueError", "OSError", "IOError", "EnvironmentError", "socket.error"]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.flake8-pytest-style]
raises-require-match-for = ["requests.RequestException"]
```
---
### `flake8-quotes`
#### [`avoid-escape`](#avoid-escape)
@@ -2781,9 +2973,8 @@ staticmethod-decorators = ["staticmethod", "stcmthd"]
#### [`convention`](#convention)
Whether to use Google-style or Numpy-style conventions when detecting
docstring sections. By default, conventions will be inferred from
the available sections.
Whether to use Google-style or NumPy-style conventions or the PEP257
defaults when analyzing docstring sections.
**Default value**: `None`

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.202"
version = "0.0.209"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.202"
version = "0.0.209"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.202-dev.0"
version = "0.0.209-dev.0"
edition = "2021"
[lib]

View File

@@ -0,0 +1,43 @@
[flake8]
# Exclude the grpc generated code
exclude = ./manim/grpc/gen/*
max-complexity = 15
max-line-length = 88
statistics = True
# Prevents some flake8-rst-docstrings errors
rst-roles = attr,class,func,meth,mod,obj,ref,doc,exc
rst-directives = manim, SEEALSO, seealso
docstring-convention=numpy
select = A,A00,B,B9,C4,C90,D,E,F,F,PT,RST,SIM,W
# General Compatibility
extend-ignore = E203, W503, D202, D212, D213, D404
# Misc
F401, F403, F405, F841, E501, E731, E402, F811, F821,
# Plug-in: flake8-builtins
A001, A002, A003,
# Plug-in: flake8-bugbear
B006, B007, B008, B009, B010, B903, B950,
# Plug-in: flake8-simplify
SIM105, SIM106, SIM119,
# Plug-in: flake8-comprehensions
C901
# Plug-in: flake8-pytest-style
PT001, PT004, PT006, PT011, PT018, PT022, PT023,
# Plug-in: flake8-docstrings
D100, D101, D102, D103, D104, D105, D106, D107,
D200, D202, D204, D205, D209,
D301,
D400, D401, D402, D403, D405, D406, D407, D409, D411, D412, D414,
# Plug-in: flake8-rst-docstrings
RST201, RST203, RST210, RST212, RST213, RST215,
RST301, RST303,

View File

@@ -1,15 +1,18 @@
use std::collections::{BTreeSet, HashMap};
use anyhow::Result;
use ruff::checks_gen::CheckCodePrefix;
use ruff::flake8_pytest_style::types::{
ParametrizeNameType, ParametrizeValuesRowType, ParametrizeValuesType,
};
use ruff::flake8_quotes::settings::Quote;
use ruff::flake8_tidy_imports::settings::Strictness;
use ruff::pydocstyle::settings::Convention;
use ruff::registry_gen::CheckCodePrefix;
use ruff::settings::options::Options;
use ruff::settings::pyproject::Pyproject;
use ruff::{
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_quotes, flake8_tidy_imports, mccabe,
pep8_naming, pydocstyle,
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_pytest_style, flake8_quotes,
flake8_tidy_imports, mccabe, pep8_naming, pydocstyle,
};
use crate::black::Black;
@@ -49,6 +52,19 @@ pub fn convert(
}
}
// Infer plugins, if not provided.
let plugins = plugins.unwrap_or_else(|| {
let from_options = plugin::infer_plugins_from_options(flake8);
if !from_options.is_empty() {
eprintln!("Inferred plugins from settings: {from_options:#?}");
}
let from_codes = plugin::infer_plugins_from_codes(&referenced_codes);
if !from_codes.is_empty() {
eprintln!("Inferred plugins from referenced check codes: {from_codes:#?}");
}
from_options.into_iter().chain(from_codes).collect()
});
// Check if the user has specified a `select`. If not, we'll add our own
// default `select`, and populate it based on user plugins.
let mut select = flake8
@@ -58,22 +74,7 @@ pub fn convert(
.as_ref()
.map(|value| BTreeSet::from_iter(parser::parse_prefix_codes(value)))
})
.unwrap_or_else(|| {
plugin::resolve_select(
flake8,
&plugins.unwrap_or_else(|| {
let from_options = plugin::infer_plugins_from_options(flake8);
if !from_options.is_empty() {
eprintln!("Inferred plugins from settings: {from_options:#?}");
}
let from_codes = plugin::infer_plugins_from_codes(&referenced_codes);
if !from_codes.is_empty() {
eprintln!("Inferred plugins from referenced check codes: {from_codes:#?}");
}
from_options.into_iter().chain(from_codes).collect()
}),
)
});
.unwrap_or_else(|| plugin::resolve_select(flake8, &plugins));
let mut ignore = flake8
.get("ignore")
.and_then(|value| {
@@ -88,6 +89,7 @@ pub fn convert(
let mut flake8_annotations = flake8_annotations::settings::Options::default();
let mut flake8_bugbear = flake8_bugbear::settings::Options::default();
let mut flake8_errmsg = flake8_errmsg::settings::Options::default();
let mut flake8_pytest_style = flake8_pytest_style::settings::Options::default();
let mut flake8_quotes = flake8_quotes::settings::Options::default();
let mut flake8_tidy_imports = flake8_tidy_imports::settings::Options::default();
let mut mccabe = mccabe::settings::Options::default();
@@ -205,7 +207,8 @@ pub fn convert(
"docstring-convention" => match value.trim() {
"google" => pydocstyle.convention = Some(Convention::Google),
"numpy" => pydocstyle.convention = Some(Convention::Numpy),
"pep257" | "all" => pydocstyle.convention = None,
"pep257" => pydocstyle.convention = Some(Convention::Pep257),
"all" => pydocstyle.convention = None,
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
// mccabe
@@ -222,12 +225,79 @@ pub fn convert(
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
// flake8-pytest-style
"pytest-fixture-no-parentheses" | "pytest_fixture_no_parentheses " => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_pytest_style.fixture_parentheses = Some(!bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
"pytest-parametrize-names-type" | "pytest_parametrize_names_type" => {
match value.trim() {
"csv" => {
flake8_pytest_style.parametrize_names_type =
Some(ParametrizeNameType::CSV);
}
"tuple" => {
flake8_pytest_style.parametrize_names_type =
Some(ParametrizeNameType::Tuple);
}
"list" => {
flake8_pytest_style.parametrize_names_type =
Some(ParametrizeNameType::List);
}
_ => eprintln!("Unexpected '{key}' value: {value}"),
}
}
"pytest-parametrize-values-type" | "pytest_parametrize_values_type" => {
match value.trim() {
"tuple" => {
flake8_pytest_style.parametrize_values_type =
Some(ParametrizeValuesType::Tuple);
}
"list" => {
flake8_pytest_style.parametrize_values_type =
Some(ParametrizeValuesType::List);
}
_ => eprintln!("Unexpected '{key}' value: {value}"),
}
}
"pytest-parametrize-values-row-type" | "pytest_parametrize_values_row_type" => {
match value.trim() {
"tuple" => {
flake8_pytest_style.parametrize_values_row_type =
Some(ParametrizeValuesRowType::Tuple);
}
"list" => {
flake8_pytest_style.parametrize_values_row_type =
Some(ParametrizeValuesRowType::List);
}
_ => eprintln!("Unexpected '{key}' value: {value}"),
}
}
"pytest-raises-require-match-for" | "pytest_raises_require_match_for" => {
flake8_pytest_style.raises_require_match_for =
Some(parser::parse_strings(value.as_ref()));
}
"pytest-mark-no-parentheses" | "pytest_mark_no_parentheses" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_pytest_style.mark_parentheses = Some(!bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
// Unknown
_ => eprintln!("Skipping unsupported property: {key}"),
}
}
}
// Set default `convention`.
if plugins.contains(&Plugin::Flake8Docstrings) {
if pydocstyle.convention.is_none() {
pydocstyle.convention = Some(Convention::Pep257);
}
}
// Deduplicate and sort.
options.select = Some(Vec::from_iter(select));
options.ignore = Some(Vec::from_iter(ignore));
@@ -240,6 +310,9 @@ pub fn convert(
if flake8_errmsg != flake8_errmsg::settings::Options::default() {
options.flake8_errmsg = Some(flake8_errmsg);
}
if flake8_pytest_style != flake8_pytest_style::settings::Options::default() {
options.flake8_pytest_style = Some(flake8_pytest_style);
}
if flake8_quotes != flake8_quotes::settings::Options::default() {
options.flake8_quotes = Some(flake8_quotes);
}
@@ -278,8 +351,8 @@ mod tests {
use std::collections::HashMap;
use anyhow::Result;
use ruff::checks_gen::CheckCodePrefix;
use ruff::pydocstyle::settings::Convention;
use ruff::registry_gen::CheckCodePrefix;
use ruff::settings::options::Options;
use ruff::settings::pyproject::Pyproject;
use ruff::{flake8_quotes, pydocstyle};
@@ -328,6 +401,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
@@ -387,6 +461,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
@@ -446,6 +521,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
@@ -505,6 +581,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
@@ -564,6 +641,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: Some(flake8_quotes::settings::Options {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
@@ -619,42 +697,6 @@ mod tests {
required_version: None,
respect_gitignore: None,
select: Some(vec![
CheckCodePrefix::D100,
CheckCodePrefix::D101,
CheckCodePrefix::D102,
CheckCodePrefix::D103,
CheckCodePrefix::D104,
CheckCodePrefix::D105,
CheckCodePrefix::D106,
CheckCodePrefix::D200,
CheckCodePrefix::D201,
CheckCodePrefix::D202,
CheckCodePrefix::D204,
CheckCodePrefix::D205,
CheckCodePrefix::D206,
CheckCodePrefix::D207,
CheckCodePrefix::D208,
CheckCodePrefix::D209,
CheckCodePrefix::D210,
CheckCodePrefix::D211,
CheckCodePrefix::D214,
CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
CheckCodePrefix::D400,
CheckCodePrefix::D403,
CheckCodePrefix::D404,
CheckCodePrefix::D405,
CheckCodePrefix::D406,
CheckCodePrefix::D407,
CheckCodePrefix::D408,
CheckCodePrefix::D409,
CheckCodePrefix::D410,
CheckCodePrefix::D411,
CheckCodePrefix::D412,
CheckCodePrefix::D414,
CheckCodePrefix::D418,
CheckCodePrefix::D419,
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
@@ -667,6 +709,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
@@ -729,6 +772,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: Some(flake8_quotes::settings::Options {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,

View File

@@ -3,8 +3,8 @@ use std::str::FromStr;
use anyhow::{bail, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use ruff::checks::PREFIX_REDIRECTS;
use ruff::checks_gen::CheckCodePrefix;
use ruff::registry::PREFIX_REDIRECTS;
use ruff::registry_gen::CheckCodePrefix;
use ruff::settings::types::PatternPrefixPair;
use rustc_hash::FxHashMap;
@@ -201,7 +201,7 @@ pub fn collect_per_file_ignores(
#[cfg(test)]
mod tests {
use anyhow::Result;
use ruff::checks_gen::CheckCodePrefix;
use ruff::registry_gen::CheckCodePrefix;
use ruff::settings::types::PatternPrefixPair;
use crate::parser::{parse_files_to_codes_mapping, parse_prefix_codes, parse_strings};

View File

@@ -3,7 +3,7 @@ use std::fmt;
use std::str::FromStr;
use anyhow::anyhow;
use ruff::checks_gen::CheckCodePrefix;
use ruff::registry_gen::CheckCodePrefix;
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Plugin {
@@ -20,6 +20,7 @@ pub enum Plugin {
Flake8ErrMsg,
Flake8ImplicitStrConcat,
Flake8Print,
Flake8PytestStyle,
Flake8Quotes,
Flake8Return,
Flake8Simplify,
@@ -48,6 +49,7 @@ impl FromStr for Plugin {
"flake8-errmsg" => Ok(Plugin::Flake8ErrMsg),
"flake8-implicit-str-concat" => Ok(Plugin::Flake8ImplicitStrConcat),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-pytest-style" => Ok(Plugin::Flake8PytestStyle),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-return" => Ok(Plugin::Flake8Return),
"flake8-simplify" => Ok(Plugin::Flake8Simplify),
@@ -80,6 +82,7 @@ impl fmt::Debug for Plugin {
Plugin::Flake8ErrMsg => "flake8-errmsg",
Plugin::Flake8ImplicitStrConcat => "flake8-implicit-str-concat",
Plugin::Flake8Print => "flake8-print",
Plugin::Flake8PytestStyle => "flake8-pytest-style",
Plugin::Flake8Quotes => "flake8-quotes",
Plugin::Flake8Return => "flake8-return",
Plugin::Flake8Simplify => "flake8-simplify",
@@ -111,6 +114,7 @@ impl Plugin {
Plugin::Flake8ErrMsg => CheckCodePrefix::EM,
Plugin::Flake8ImplicitStrConcat => CheckCodePrefix::ISC,
Plugin::Flake8Print => CheckCodePrefix::T2,
Plugin::Flake8PytestStyle => CheckCodePrefix::PT,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Return => CheckCodePrefix::RET,
Plugin::Flake8Simplify => CheckCodePrefix::SIM,
@@ -145,13 +149,14 @@ impl Plugin {
}
}
}
// Default to PEP8.
DocstringConvention::PEP8.select()
// Default to PEP257.
DocstringConvention::Pep257.select()
}
Plugin::Flake8Eradicate => vec![CheckCodePrefix::ERA],
Plugin::Flake8ErrMsg => vec![CheckCodePrefix::EM],
Plugin::Flake8ImplicitStrConcat => vec![CheckCodePrefix::ISC],
Plugin::Flake8Print => vec![CheckCodePrefix::T2],
Plugin::Flake8PytestStyle => vec![CheckCodePrefix::PT],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Return => vec![CheckCodePrefix::RET],
Plugin::Flake8Simplify => vec![CheckCodePrefix::SIM],
@@ -166,8 +171,8 @@ impl Plugin {
pub enum DocstringConvention {
All,
PEP8,
NumPy,
Pep257,
Numpy,
Google,
}
@@ -177,8 +182,8 @@ impl FromStr for DocstringConvention {
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"all" => Ok(DocstringConvention::All),
"pep8" => Ok(DocstringConvention::PEP8),
"numpy" => Ok(DocstringConvention::NumPy),
"pep257" => Ok(DocstringConvention::Pep257),
"numpy" => Ok(DocstringConvention::Numpy),
"google" => Ok(DocstringConvention::Google),
_ => Err(anyhow!("Unknown docstring convention: {string}")),
}
@@ -189,151 +194,14 @@ impl DocstringConvention {
fn select(&self) -> Vec<CheckCodePrefix> {
match self {
DocstringConvention::All => vec![CheckCodePrefix::D],
DocstringConvention::PEP8 => vec![
// All errors except D203, D212, D213, D214, D215, D404, D405, D406, D407, D408,
// D409, D410, D411, D413, D415, D416 and D417.
CheckCodePrefix::D100,
CheckCodePrefix::D101,
CheckCodePrefix::D102,
CheckCodePrefix::D103,
CheckCodePrefix::D104,
CheckCodePrefix::D105,
CheckCodePrefix::D106,
CheckCodePrefix::D107,
CheckCodePrefix::D200,
CheckCodePrefix::D201,
CheckCodePrefix::D202,
// CheckCodePrefix::D203,
CheckCodePrefix::D204,
CheckCodePrefix::D205,
CheckCodePrefix::D206,
CheckCodePrefix::D207,
CheckCodePrefix::D208,
CheckCodePrefix::D209,
CheckCodePrefix::D210,
CheckCodePrefix::D211,
// CheckCodePrefix::D212,
// CheckCodePrefix::D213,
// CheckCodePrefix::D214,
// CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
CheckCodePrefix::D400,
CheckCodePrefix::D402,
CheckCodePrefix::D403,
// CheckCodePrefix::D404,
// CheckCodePrefix::D405,
// CheckCodePrefix::D406,
// CheckCodePrefix::D407,
// CheckCodePrefix::D408,
// CheckCodePrefix::D409,
// CheckCodePrefix::D410,
// CheckCodePrefix::D411,
CheckCodePrefix::D412,
// CheckCodePrefix::D413,
CheckCodePrefix::D414,
// CheckCodePrefix::D415,
// CheckCodePrefix::D416,
// CheckCodePrefix::D417,
CheckCodePrefix::D418,
CheckCodePrefix::D419,
DocstringConvention::Pep257 => vec![
// Covered by the `convention` setting.
],
DocstringConvention::NumPy => vec![
// All errors except D107, D203, D212, D213, D402, D413, D415, D416, and D417.
CheckCodePrefix::D100,
CheckCodePrefix::D101,
CheckCodePrefix::D102,
CheckCodePrefix::D103,
CheckCodePrefix::D104,
CheckCodePrefix::D105,
CheckCodePrefix::D106,
// CheckCodePrefix::D107,
CheckCodePrefix::D200,
CheckCodePrefix::D201,
CheckCodePrefix::D202,
// CheckCodePrefix::D203,
CheckCodePrefix::D204,
CheckCodePrefix::D205,
CheckCodePrefix::D206,
CheckCodePrefix::D207,
CheckCodePrefix::D208,
CheckCodePrefix::D209,
CheckCodePrefix::D210,
CheckCodePrefix::D211,
// CheckCodePrefix::D212,
// CheckCodePrefix::D213,
CheckCodePrefix::D214,
CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
CheckCodePrefix::D400,
// CheckCodePrefix::D402,
CheckCodePrefix::D403,
CheckCodePrefix::D404,
CheckCodePrefix::D405,
CheckCodePrefix::D406,
CheckCodePrefix::D407,
CheckCodePrefix::D408,
CheckCodePrefix::D409,
CheckCodePrefix::D410,
CheckCodePrefix::D411,
CheckCodePrefix::D412,
// CheckCodePrefix::D413,
CheckCodePrefix::D414,
// CheckCodePrefix::D415,
// CheckCodePrefix::D416,
// CheckCodePrefix::D417,
CheckCodePrefix::D418,
CheckCodePrefix::D419,
DocstringConvention::Numpy => vec![
// Covered by the `convention` setting.
],
DocstringConvention::Google => vec![
// All errors except D203, D204, D213, D215, D400, D401, D404, D406, D407, D408,
// D409 and D413.
CheckCodePrefix::D100,
CheckCodePrefix::D101,
CheckCodePrefix::D102,
CheckCodePrefix::D103,
CheckCodePrefix::D104,
CheckCodePrefix::D105,
CheckCodePrefix::D106,
CheckCodePrefix::D107,
CheckCodePrefix::D200,
CheckCodePrefix::D201,
CheckCodePrefix::D202,
// CheckCodePrefix::D203,
// CheckCodePrefix::D204,
CheckCodePrefix::D205,
CheckCodePrefix::D206,
CheckCodePrefix::D207,
CheckCodePrefix::D208,
CheckCodePrefix::D209,
CheckCodePrefix::D210,
CheckCodePrefix::D211,
CheckCodePrefix::D212,
// CheckCodePrefix::D213,
CheckCodePrefix::D214,
// CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
// CheckCodePrefix::D400,
CheckCodePrefix::D402,
CheckCodePrefix::D403,
// CheckCodePrefix::D404,
CheckCodePrefix::D405,
// CheckCodePrefix::D406,
// CheckCodePrefix::D407,
// CheckCodePrefix::D408,
// CheckCodePrefix::D409,
CheckCodePrefix::D410,
CheckCodePrefix::D411,
CheckCodePrefix::D412,
// CheckCodePrefix::D413,
CheckCodePrefix::D414,
CheckCodePrefix::D415,
CheckCodePrefix::D416,
CheckCodePrefix::D417,
CheckCodePrefix::D418,
CheckCodePrefix::D419,
// Covered by the `convention` setting.
],
}
}
@@ -394,6 +262,25 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
"eradicate-whitelist-extend" | "eradicate_whitelist_extend" => {
plugins.insert(Plugin::Flake8Eradicate);
}
// flake8-pytest-style
"pytest-fixture-no-parentheses" | "pytest_fixture_no_parentheses " => {
plugins.insert(Plugin::Flake8PytestStyle);
}
"pytest-parametrize-names-type" | "pytest_parametrize_names_type" => {
plugins.insert(Plugin::Flake8PytestStyle);
}
"pytest-parametrize-values-type" | "pytest_parametrize_values_type" => {
plugins.insert(Plugin::Flake8PytestStyle);
}
"pytest-parametrize-values-row-type" | "pytest_parametrize_values_row_type" => {
plugins.insert(Plugin::Flake8PytestStyle);
}
"pytest-raises-require-match-for" | "pytest_raises_require_match_for" => {
plugins.insert(Plugin::Flake8PytestStyle);
}
"pytest-mark-no-parentheses" | "pytest_mark_no_parentheses" => {
plugins.insert(Plugin::Flake8PytestStyle);
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
@@ -485,7 +372,7 @@ pub fn resolve_select(
plugins: &[Plugin],
) -> BTreeSet<CheckCodePrefix> {
// Include default Pyflakes and pycodestyle checks.
let mut select = BTreeSet::from([CheckCodePrefix::E, CheckCodePrefix::F, CheckCodePrefix::W]);
let mut select = BTreeSet::from([CheckCodePrefix::F, CheckCodePrefix::E, CheckCodePrefix::W]);
// Add prefix codes for every plugin.
for plugin in plugins {

View File

@@ -11,20 +11,12 @@
<title>Ruff Playground</title>
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🛠️</text></svg>"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22></text></svg>"
/>
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
</head>
<body>
<div id="root"></div>
<div style="display: flex; position: fixed; right: 16px; bottom: 16px">
<a href="https://GitHub.com/charliermarsh/ruff"
><img
src="https://img.shields.io/github/stars/charliermarsh/ruff.svg?style=social&label=GitHub&maxAge=2592000&?logoWidth=100"
alt="GitHub stars"
style="width: 120px"
/></a>
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,4 +1,5 @@
import classNames from "classnames";
import RepoButton from "./RepoButton";
import ThemeButton from "./ThemeButton";
import ShareButton from "./ShareButton";
import { Theme } from "./theme";
@@ -83,14 +84,19 @@ export default function Header({
/>
Settings
</button>
</div>
<div className={"flex items-center min-w-0"}>
{version ? (
<div className={"flex items-center"}>
<div className={"hidden sm:flex items-center"}>
<VersionTag>v{version}</VersionTag>
</div>
) : null}
</div>
<div className={"hidden sm:flex items-center min-w-0"}>
<ShareButton key={edit} onShare={onShare} />
<div className="hidden sm:block mx-6 lg:mx-4 w-px h-6 bg-gray-200 dark:bg-gray-700" />
<RepoButton />
<div className="hidden sm:block mx-6 lg:mx-4 w-px h-6 bg-gray-200 dark:bg-gray-700" />
<div className="hidden sm:block">
<ShareButton key={edit} onShare={onShare} />
</div>
<div className="hidden sm:block mx-6 lg:mx-4 w-px h-6 bg-gray-200 dark:bg-gray-700" />
<ThemeButton theme={theme} onChange={onChangeTheme} />
</div>

View File

@@ -0,0 +1,27 @@
export default function RepoButton() {
return (
<a
className={"hover:text-ayu-accent/70 text-ayu-accent"}
href={"https://github.com/charliermarsh/ruff"}
target={"_blank"}
rel={"noreferrer"}
>
<svg
xmlns={"http://www.w3.org/2000/svg"}
width={"24"}
height={"24"}
viewBox={"0 0 16 16"}
fill={"none"}
>
<path
fillRule={"evenodd"}
clipRule={"evenodd"}
d={
"M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z"
}
fill={"currentColor"}
/>
</svg>
</a>
);
}

View File

@@ -28,7 +28,7 @@ export default function ShareButton({ onShare }: { onShare?: () => void }) {
) : (
<button
type="button"
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 enabled:hover:bg-ayu-accent/80 bg-ayu-accent text-white shadow-sm dark:shadow-highlight/20 disabled:opacity-50"
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 enabled:hover:bg-ayu-accent/70 bg-ayu-accent text-white shadow-sm dark:shadow-highlight/20 disabled:opacity-50"
disabled={!onShare || copied}
onClick={
onShare

View File

@@ -56,7 +56,9 @@ export default function SourceEditor({
.filter((check) => position.startLineNumber === check.location.row)
.filter((check) => check.fix)
.map((check) => ({
title: `Fix ${check.code}`,
title: check.fix
? `${check.code}: ${check.fix.message}` ?? `Fix ${check.code}`
: "Autofix",
id: `fix-${check.code}`,
kind: "quickfix",
edit: check.fix

View File

@@ -7,7 +7,7 @@ module.exports = {
theme: {
extend: {
colors: {
"ayu-accent": "#f07171",
"ayu-accent": "#fa8d3e",
"ayu-background": {
DEFAULT: "#f8f9fa",
dark: "#0b0e14",

View File

@@ -1,5 +1,19 @@
[build-system]
requires = ["maturin>=0.14,<0.15"]
build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.209"
description = "An extremely fast Python linter, written in Rust."
authors = [
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
]
maintainers = [
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
]
requires-python = ">=3.7"
license = { file = "LICENSE" }
keywords = ["automation", "flake8", "pycodestyle", "pyflakes", "pylint", "clippy"]
classifiers = [
"Development Status :: 3 - Alpha",
@@ -17,30 +31,8 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
]
author = "Charlie Marsh"
author_email = "charlie.r.marsh@gmail.com"
description = "An extremely fast Python linter, written in Rust."
requires-python = ">=3.7"
[project.urls]
repository = "https://github.com/charliermarsh/ruff"
[build-system]
requires = ["maturin>=0.14,<0.15"]
build-backend = "maturin"
urls = { repository = "https://github.com/charliermarsh/ruff-lsp" }
[tool.maturin]
bindings = "bin"
strip = true
[tool.ruff]
update-check = true
[tool.ruff.isort]
force-wrap-aliases = true
combine-as-imports = true
force-single-line = true
single-line-exclusions = ["os", "logging.handlers"]
[tool.ruff.pydocstyle]
convention = "google"

View File

@@ -51,3 +51,12 @@ secret == "s3cr3t"
token == "s3cr3t"
secrete == "s3cr3t"
password == safe == "s3cr3t"
if token == "1\n2":
pass
if token == "3\t4":
pass
if token == "5\r6":
pass

View File

@@ -0,0 +1,115 @@
class Foo:
"""buzz"""
pass # PIE790
if foo:
"""foo"""
pass # PIE790
if foo:
pass
else:
"""bar"""
pass # PIE790
while True:
pass
else:
"""bar"""
pass # PIE790
for _ in range(10):
pass
else:
"""bar"""
pass # PIE790
async for _ in range(10):
pass
else:
"""bar"""
pass # PIE790
def foo() -> None:
"""
buzz
"""
pass # PIE790
async def foo():
"""
buzz
"""
pass # PIE790
try:
"""
buzz
"""
pass # PIE790
except ValueError:
pass
try:
bar()
except ValueError:
"""bar"""
pass # PIE790
for _ in range(10):
"""buzz"""
pass # PIE790
async for _ in range(10):
"""buzz"""
pass # PIE790
while cond:
"""buzz"""
pass # PIE790
with bar:
"""buzz"""
pass # PIE790
async with bar:
"""buzz"""
pass # PIE790
class Foo:
# bar
pass
if foo:
# foo
pass
class Error(Exception):
pass
try:
foo()
except NetworkError:
pass
def foo() -> None:
pass

View File

@@ -0,0 +1,33 @@
class Foo(BaseModel):
name = StringField()
# ....
name = StringField() # PIE794
def remove(self) -> None:
...
class Foo(BaseModel):
name: str = StringField()
# ....
name = StringField() # PIE794
def foo(self) -> None:
...
class User(BaseModel):
bar: str = StringField()
foo: bool = BooleanField()
# ...
bar = StringField() # PIE794
class User(BaseModel):
@property
def buzz(self) -> str:
...
@buzz.setter
def buzz(self, value: str | int) -> None:
...

View File

@@ -0,0 +1,20 @@
@dataclass
class Foo:
foo: List[str] = field(default_factory=lambda: []) # PIE807
class FooTable(BaseTable):
bar = fields.ListField(default=lambda: []) # PIE807
class FooTable(BaseTable):
bar = fields.ListField(lambda: []) # PIE807
@dataclass
class Foo:
foo: List[str] = field(default_factory=list)
class FooTable(BaseTable):
bar = fields.ListField(list)

View File

@@ -0,0 +1,78 @@
import pytest
from pytest import fixture
from pytest import fixture as aliased
# `import pytest`
@pytest.fixture
def no_parentheses():
return 42
@pytest.fixture()
def parentheses_no_params():
return 42
@pytest.fixture(scope="module")
def parentheses_with_params():
return 42
@pytest.fixture(
)
def parentheses_no_params_multiline():
return 42
# `from pytest import fixture`
@fixture
def imported_from_no_parentheses():
return 42
@fixture()
def imported_from_parentheses_no_params():
return 42
@fixture(scope="module")
def imported_from_parentheses_with_params():
return 42
@fixture(
)
def imported_from_parentheses_no_params_multiline():
return 42
# `from pytest import fixture as aliased`
@aliased
def aliased_no_parentheses():
return 42
@aliased()
def aliased_parentheses_no_params():
return 42
@aliased(scope="module")
def aliased_parentheses_with_params():
return 42
@aliased(
)
def aliased_parentheses_no_params_multiline():
return 42

View File

@@ -0,0 +1,21 @@
import pytest
@pytest.fixture()
def my_fixture(): # OK no args
return 0
@pytest.fixture(scope="module")
def my_fixture(): # OK only kwargs
return 0
@pytest.fixture("module")
def my_fixture(): # Error only args
return 0
@pytest.fixture("module", autouse=True)
def my_fixture(): # Error mixed
return 0

View File

@@ -0,0 +1,16 @@
import pytest
@pytest.fixture()
def ok_no_scope():
...
@pytest.fixture(scope="module")
def ok_other_scope():
...
@pytest.fixture(scope="function")
def error():
...

View File

@@ -0,0 +1,58 @@
import abc
from abc import abstractmethod
import pytest
@pytest.fixture()
def _patch_something(mocker): # OK simple
mocker.patch("some.thing")
@pytest.fixture()
def _patch_something(mocker): # OK with return
if something:
return
mocker.patch("some.thing")
@pytest.fixture()
def _activate_context(): # OK with yield
with context:
yield
class BaseTest:
@pytest.fixture()
@abc.abstractmethod
def my_fixture(): # OK abstract with import abc
raise NotImplementedError
class BaseTest:
@pytest.fixture()
@abstractmethod
def my_fixture(): # OK abstract with from import
raise NotImplementedError
@pytest.fixture()
def my_fixture(): # OK ignoring yield from
yield from some_generator()
@pytest.fixture()
def my_fixture(): # OK ignoring yield value
yield 1
@pytest.fixture()
def patch_something(mocker): # Error simple
mocker.patch("some.thing")
@pytest.fixture()
def activate_context(): # Error with yield
with context:
yield

View File

@@ -0,0 +1,57 @@
import abc
from abc import abstractmethod
import pytest
@pytest.fixture()
def my_fixture(mocker): # OK with return
return 0
@pytest.fixture()
def activate_context(): # OK with yield
with get_context() as context:
yield context
@pytest.fixture()
def _any_fixture(mocker): # Ok nested function
def nested_function():
return 1
mocker.patch("...", nested_function)
class BaseTest:
@pytest.fixture()
@abc.abstractmethod
def _my_fixture(): # OK abstract with import abc
return NotImplemented
class BaseTest:
@pytest.fixture()
@abstractmethod
def _my_fixture(): # OK abstract with from import
return NotImplemented
@pytest.fixture()
def _my_fixture(mocker): # Error with return
return 0
@pytest.fixture()
def _activate_context(): # Error with yield
with get_context() as context:
yield context
@pytest.fixture()
def _activate_context(): # Error with conditional yield from
if some_condition:
with get_context() as context:
yield context
else:
yield from other_context()

View File

@@ -0,0 +1,36 @@
import pytest
@pytest.mark.parametrize("param", [1, 2, 3])
def test_always_ok(param):
...
@pytest.mark.parametrize("param1,param2", [(1, 2), (3, 4)])
def test_csv(param1, param2):
...
@pytest.mark.parametrize(" param1, , param2 , ", [(1, 2), (3, 4)])
def test_csv_with_whitespace(param1, param2):
...
@pytest.mark.parametrize(("param1", "param2"), [(1, 2), (3, 4)])
def test_tuple(param1, param2):
...
@pytest.mark.parametrize(("param1",), [1, 2, 3])
def test_tuple_one_elem(param1, param2):
...
@pytest.mark.parametrize(["param1", "param2"], [(1, 2), (3, 4)])
def test_list(param1, param2):
...
@pytest.mark.parametrize(["param1"], [1, 2, 3])
def test_list_one_elem(param1, param2):
...

View File

@@ -0,0 +1,55 @@
import pytest
@pytest.mark.parametrize("param", (1, 2))
def test_tuple(param):
...
@pytest.mark.parametrize(
("param1", "param2"),
(
(1, 2),
(3, 4),
),
)
def test_tuple_of_tuples(param1, param2):
...
@pytest.mark.parametrize(
("param1", "param2"),
(
[1, 2],
[3, 4],
),
)
def test_tuple_of_lists(param1, param2):
...
@pytest.mark.parametrize("param", [1, 2])
def test_list(param):
...
@pytest.mark.parametrize(
("param1", "param2"),
[
(1, 2),
(3, 4),
],
)
def test_list_of_tuples(param1, param2):
...
@pytest.mark.parametrize(
("param1", "param2"),
[
[1, 2],
[3, 4],
],
)
def test_list_of_lists(param1, param2):
...

View File

@@ -0,0 +1,48 @@
# OK
mocker.patch("module.name", not_lambda)
module_mocker.patch("module.name", not_lambda)
mocker.patch.object(obj, "attr", not_lambda)
module_mocker.patch.object(obj, "attr", not_lambda)
mocker.patch("module.name", return_value=None)
module_mocker.patch("module.name", return_value=None)
mocker.patch.object(obj, "attr", return_value=None)
module_mocker.patch.object(obj, "attr", return_value=None)
mocker.patch("module.name", lambda x, y: x)
module_mocker.patch("module.name", lambda x, y: x)
mocker.patch.object(obj, "attr", lambda x, y: x)
module_mocker.patch.object(obj, "attr", lambda x, y: x)
mocker.patch("module.name", lambda *args: args)
module_mocker.patch("module.name", lambda *args: args)
mocker.patch.object(obj, "attr", lambda *args: args)
module_mocker.patch.object(obj, "attr", lambda *args: args)
mocker.patch("module.name", lambda **kwargs: kwargs)
module_mocker.patch("module.name", lambda **kwargs: kwargs)
mocker.patch.object(obj, "attr", lambda **kwargs: kwargs)
module_mocker.patch.object(obj, "attr", lambda **kwargs: kwargs)
mocker.patch("module.name", lambda x, /, y: x)
module_mocker.patch("module.name", lambda x, /, y: x)
mocker.patch.object(obj, "attr", lambda x, /, y: x)
module_mocker.patch.object(obj, "attr", lambda x, /, y: x)
# Error
mocker.patch("module.name", lambda: None)
module_mocker.patch("module.name", lambda: None)
mocker.patch.object(obj, "attr", lambda: None)
module_mocker.patch.object(obj, "attr", lambda: None)
mocker.patch("module.name", lambda x, y: None)
module_mocker.patch("module.name", lambda x, y: None)
mocker.patch.object(obj, "attr", lambda x, y: None)
module_mocker.patch.object(obj, "attr", lambda x, y: None)
mocker.patch("module.name", lambda *args, **kwargs: None)
module_mocker.patch("module.name", lambda *args, **kwargs: None)
mocker.patch.object(obj, "attr", lambda *args, **kwargs: None)
module_mocker.patch.object(obj, "attr", lambda *args, **kwargs: None)

View File

@@ -0,0 +1,9 @@
import pytest
def test_xxx():
assert 1 == 1 # OK no parameters
def test_xxx():
self.assertEqual(1, 1) # Error

View File

@@ -0,0 +1,11 @@
import pytest
def test_ok():
with pytest.raises():
pass
def test_error():
with pytest.raises(UnicodeError):
pass

View File

@@ -0,0 +1,32 @@
import socket
import pytest
def test_ok():
with pytest.raises(ValueError, match="Can't divide by 0"):
raise ValueError("Can't divide by 0")
def test_ok_different_error_from_config():
with pytest.raises(ZeroDivisionError):
raise ZeroDivisionError("Can't divide by 0")
def test_error_no_argument_given():
with pytest.raises(ValueError):
raise ValueError("Can't divide 1 by 0")
with pytest.raises(socket.error):
raise ValueError("Can't divide 1 by 0")
def test_error_match_is_empty():
with pytest.raises(ValueError, match=None):
raise ValueError("Can't divide 1 by 0")
with pytest.raises(ValueError, match=""):
raise ValueError("Can't divide 1 by 0")
with pytest.raises(ValueError, match=f""):
raise ValueError("Can't divide 1 by 0")

View File

@@ -0,0 +1,64 @@
import pytest
def test_ok():
with pytest.raises(AttributeError):
[].size
async def test_ok_trivial_with():
with pytest.raises(AttributeError):
with context_manager_under_test():
pass
with pytest.raises(AttributeError):
async with context_manager_under_test():
pass
def test_ok_complex_single_call():
with pytest.raises(AttributeError):
my_func(
[].size,
[].size,
)
def test_error_multiple_statements():
with pytest.raises(AttributeError):
len([])
[].size
async def test_error_complex_statement():
with pytest.raises(AttributeError):
if True:
[].size
with pytest.raises(AttributeError):
for i in []:
[].size
with pytest.raises(AttributeError):
async for i in []:
[].size
with pytest.raises(AttributeError):
while True:
[].size
with pytest.raises(AttributeError):
with context_manager_under_test():
[].size
with pytest.raises(AttributeError):
async with context_manager_under_test():
[].size
def test_error_try():
with pytest.raises(AttributeError):
try:
[].size
except:
raise

View File

@@ -0,0 +1,13 @@
# OK
import pytest
import pytest as pytest
from notpytest import fixture
from . import fixture
from .pytest import fixture
# Error
import pytest as other_name
from pytest import fixture
from pytest import fixture as other_name

View File

@@ -0,0 +1,25 @@
import pytest
def test_ok():
assert [0]
def test_error():
assert None
assert False
assert 0
assert 0.0
assert ""
assert f""
assert []
assert ()
assert {}
assert list()
assert set()
assert tuple()
assert dict()
assert frozenset()
assert list([])
assert set(set())
assert tuple("")

View File

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

View File

@@ -0,0 +1,19 @@
import pytest
def test_ok():
try:
something()
except Exception as e:
something_else()
with pytest.raises(ZeroDivisionError) as e:
1 / 0
assert e.value.message
def test_error():
try:
something()
except Exception as e:
assert e.message, "blah blah"

View File

@@ -0,0 +1,18 @@
import pytest
def test_ok():
assert something
assert something or something_else
assert something or something_else and something_third
assert not (something and something_else)
def test_error():
assert something and something_else
assert something and something_else and something_third
assert something and not something_else
assert something and (something_else or something_third)
assert not (something or something_else)
assert not (something or something_else or something_third)
assert not (something or something_else and something_third)

View File

@@ -0,0 +1,14 @@
def test_xxx(fixture): # Ok good param name
pass
def xxx(_param): # Ok not a test
pass
def test_xxx(_fixture): # Error arg
pass
def test_xxx(*, _fixture): # Error kwonly
pass

View File

@@ -0,0 +1,21 @@
import pytest
@pytest.fixture()
def ok_no_parameters():
return 0
@pytest.fixture
def ok_without_parens():
return 0
@pytest.yield_fixture()
def error_without_parens():
return 0
@pytest.yield_fixture
def error_with_parens():
return 0

View File

@@ -0,0 +1,58 @@
import functools
import pytest
@pytest.fixture()
def my_fixture(): # OK return
return 0
@pytest.fixture()
def my_fixture(): # OK yield
resource = acquire_resource()
yield resource
resource.release()
@pytest.fixture()
def my_fixture(): # OK other request
request = get_request()
request.addfinalizer(finalizer)
return request
def create_resource(arg, request): # OK other function
resource = Resource(arg)
request.addfinalizer(resource.release)
return resource
@pytest.fixture()
def resource_factory(request):
return functools.partial(create_resource, request=request)
@pytest.fixture()
def resource_factory(request): # OK other function
def create_resource(arg) -> Resource:
resource = Resource(arg)
request.addfinalizer(resource.release)
return resource
return create_resource
@pytest.fixture()
def my_fixture(request): # Error return
resource = acquire_resource()
request.addfinalizer(resource.release)
return resource
@pytest.fixture()
def my_fixture(request): # Error yield
resource = acquire_resource()
request.addfinalizer(resource.release)
yield resource
resource # prevent PT022

View File

@@ -0,0 +1,17 @@
import pytest
@pytest.fixture()
def ok_complex_logic():
if some_condition:
resource = acquire_resource()
yield resource
resource.release()
return
yield None
@pytest.fixture()
def error():
resource = acquire_resource()
yield resource

View File

@@ -0,0 +1,74 @@
import pytest
@pytest.mark.foo(scope="module")
def ok_with_parameters_regardless_of_config():
pass
# Without parentheses
@pytest.mark.foo
def test_something():
pass
@pytest.mark.foo
class TestClass:
def test_something():
pass
class TestClass:
@pytest.mark.foo
def test_something():
pass
class TestClass:
@pytest.mark.foo
class TestNestedClass:
def test_something():
pass
class TestClass:
class TestNestedClass:
@pytest.mark.foo
def test_something():
pass
# With parentheses
@pytest.mark.foo()
def test_something():
pass
@pytest.mark.foo()
class TestClass:
def test_something():
pass
class TestClass:
@pytest.mark.foo()
def test_something():
pass
class TestClass:
@pytest.mark.foo()
class TestNestedClass:
def test_something():
pass
class TestClass:
class TestNestedClass:
@pytest.mark.foo()
def test_something():
pass

View File

@@ -0,0 +1,35 @@
import pytest
@pytest.mark.asyncio()
async def test_something(): # Ok not fixture
pass
@pytest.mark.asyncio
async def test_something(): # Ok not fixture no parens
pass
@pytest.mark.asyncio()
@pytest.fixture()
async def my_fixture(): # Error before
return 0
@pytest.mark.asyncio
@pytest.fixture()
async def my_fixture(): # Error before no parens
return 0
@pytest.fixture()
@pytest.mark.asyncio()
async def my_fixture(): # Error after
return 0
@pytest.fixture()
@pytest.mark.asyncio
async def my_fixture(): # Error after no parens
return 0

View File

@@ -0,0 +1,18 @@
import pytest
@pytest.mark.usefixtures("a")
def test_something(): # Ok not fixture
pass
@pytest.mark.usefixtures("a")
@pytest.fixture()
def my_fixture(): # Error before
return 0
@pytest.fixture()
@pytest.mark.usefixtures("a")
def my_fixture(): # Error after
return 0

View File

@@ -0,0 +1,26 @@
import pytest
@pytest.mark.usefixtures("a")
def test_ok():
pass
@pytest.mark.foo()
def test_ok_another_mark_with_parens():
pass
@pytest.mark.foo
def test_ok_another_mark_no_parens():
pass
@pytest.mark.usefixtures()
def test_error_with_parens():
pass
@pytest.mark.usefixtures
def test_error_no_parens():
pass

View File

@@ -137,6 +137,14 @@ def x():
return a
# Considered OK, since attribute assignments can have side effects.
class X:
def x(self):
a = self.property
self.property = None
return a
# Test cases for using value for assignment then returning it
# See:https://github.com/afonasev/flake8-return/issues/47
def resolve_from_url(self, url: str) -> dict:
@@ -238,13 +246,16 @@ def close(self):
report(traceback.format_exc())
return any_failed
def global_assignment():
global X
X = 1
return X
def nonlocal_assignment():
X = 1
def inner():
nonlocal X
X = 1

View File

@@ -1,12 +1,14 @@
key in dict.keys() # SIM118
key in obj.keys() # SIM118
foo["bar"] in dict.keys() # SIM118
foo["bar"] in obj.keys() # SIM118
foo() in dict.keys() # SIM118
foo['bar'] in obj.keys() # SIM118
for key in dict.keys(): # SIM118
foo() in obj.keys() # SIM118
for key in obj.keys(): # SIM118
pass
for key in list(dict.keys()):
for key in list(obj.keys()):
if some_property(key):
del dict[key]
del obj[key]

View File

@@ -0,0 +1,14 @@
if a or True: # SIM223
pass
if (a or b) or True: # SIM223
pass
if a or (b or True): # SIM223
pass
if a and True:
pass
if True:
pass

View File

@@ -0,0 +1,14 @@
if a and False: # SIM223
pass
if (a or b) and False: # SIM223
pass
if a or (b and False): # SIM223
pass
if a or False:
pass
if False:
pass

View File

@@ -0,0 +1,12 @@
# Errors
"yoda" == compare # SIM300
'yoda' == compare # SIM300
42 == age # SIM300
# OK
compare == "yoda"
age == 42
x == y
"yoda" == compare == 1
"yoda" == compare == someothervar
"yoda" == "yoda"

View File

@@ -26,3 +26,8 @@ from A import (
from D import a_long_name_to_force_multiple_lines # Comment 12
from D import another_long_name_to_force_multiple_lines # Comment 13
from E import a # Comment 1
from F import a # Comment 1
from F import b

View File

@@ -0,0 +1 @@
from long_module_name import member_one, member_two, member_three, member_four, member_five

View File

@@ -0,0 +1,2 @@
from long_module_name import member_one, member_two, member_three, member_four, member_five

View File

@@ -0,0 +1,2 @@
from long_module_name import member_one, member_two, member_three, member_four, member_five

View File

View File

@@ -35,7 +35,6 @@ def f():
...
def f():
r"Here's a line without a period"
...
@@ -71,3 +70,24 @@ def f():
Here's a line without a period,
but here's the next line with trailing space """
...
def f(rounds: list[int], number: int) -> bool:
"""
:param rounds: list - rounds played.
:param number: int - round number.
:return: bool - was the round played?
"""
return number in rounds
def f(rounds: list[int], number: int) -> bool:
"""
Args:
rounds (list): rounds played.
number (int): round number.
Returns:
bool: was the round played?
"""
return number in rounds

View File

@@ -376,7 +376,7 @@ class TestGoogle: # noqa: D203
Etiam at tellus a tellus faucibus maximus. Curabitur tellus
mauris, semper id vehicula ac, feugiat ut tortor.
verbose (bool):
If True, print out as much infromation as possible.
If True, print out as much information as possible.
If False, print out concise "one-liner" information.
"""

View File

@@ -1,16 +1,66 @@
"""Test: noqa directives."""
from module import (
A, # noqa: F401
B,
# This should ignore both errors.
from typing import ( # noqa: F401
List,
Sequence,
)
from module import (
A, # noqa: F401
B, # noqa: F401
# This should ignore both errors.
from typing import ( # noqa
List,
Sequence,
)
from module import (
A,
B,
# This should ignore both errors.
from typing import (
List, # noqa: F401
Sequence, # noqa: F401
)
# This should ignore both errors.
from typing import (
List, # noqa
Sequence, # noqa
)
# This should ignore the first error.
from typing import (
Mapping, # noqa: F401
Union,
)
# This should ignore both errors.
from typing import ( # noqa
List,
Sequence,
)
# This should ignore the error, but the inner noqa should be marked as unused.
from typing import ( # noqa: F401
Optional, # noqa: F401
)
# This should ignore the error, but the inner noqa should be marked as unused.
from typing import ( # noqa
Optional, # noqa: F401
)
# This should ignore the error, but mark F501 as unused.
from typing import ( # noqa: F401
Dict, # noqa: F501
)
# This should ignore the error, but mark F501 as unused.
from typing import ( # noqa: F501
Tuple, # noqa: F401
)
# This should ignore both errors.
from typing import Any, AnyStr # noqa: F401
# This should ignore both errors.
from typing import AsyncIterable, AsyncGenerator # noqa
# This should mark F501 as unused.
from typing import Awaitable, AwaitableGenerator # noqa: F501

View File

@@ -1,12 +1,35 @@
# OK
a = "abc"
b = f"ghi{'jkl'}"
# Errors
c = f"def"
d = f"def" + "ghi"
e = (
f"def" +
"ghi"
)
f = (
f"a"
F"b"
"c"
rf"d"
fr"e"
)
g = f""
# OK
g = f"ghi{123:{45}}"
# Error
h = "x" "y" f"z"
v = 23.234234
# OK
f"{v:0.2f}"
f"{f'{v:0.2f}'}"
# Errors
f"{v:{f'0.2f'}}"
f"{f''}"

View File

@@ -89,7 +89,8 @@ A = (
f'B'
f'{B}'
)
C = f'{A:{B}}'
C = f'{A:{f"{B}"}}'
from typing import Annotated, Literal

View File

@@ -1,10 +0,0 @@
def foo(x: int, y: int, x: int) -> None:
pass
def bar(x: int, y: int, *, x: int) -> None:
pass
def baz(x: int, y: int, **x: int) -> None:
pass

View File

@@ -0,0 +1,6 @@
from __future__ import annotations
# test case for https://github.com/charliermarsh/ruff/issues/1552
def _():
x = 0
list()[x:]

View File

@@ -18,8 +18,8 @@ def isinstances():
result = isinstance(var[5], int) or True or isinstance(var[5], float) # [consider-merging-isinstance]
infered_isinstance = isinstance
result = infered_isinstance(var[6], int) or infered_isinstance(var[6], float) or infered_isinstance(var[6], list) and False # [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]

View File

@@ -54,3 +54,12 @@ pandas = "pd"
[tool.ruff.flake8-import-conventions.extend-aliases]
"dask.dataframe" = "dd"
[tool.ruff.flake8-pytest-style]
fixture-parentheses = false
parametrize-names-type = "csv"
parametrize-values-type = "tuple"
parametrize-values-row-type = "list"
raises-require-match-for = ["Exception", "TypeError", "KeyError"]
raises-extend-require-match-for = ["requests.RequestException"]
mark-parentheses = false

View File

@@ -45,8 +45,8 @@ f"foo{bar}".encode(encoding)
"unicode text©".encode()
"unicode text©".encode(encoding="UTF8") # "unicode text©".encode()
r"fo\o".encode("utf-8") # br"fo\o"
r"foo\o".encode("utf-8") # br"foo\o"
u"foo".encode("utf-8") # b"foo"
R"fo\o".encode("utf-8") # br"fo\o"
R"foo\o".encode("utf-8") # br"foo\o"
U"foo".encode("utf-8") # b"foo"
print("foo".encode()) # print(b"foo")

View File

@@ -0,0 +1,98 @@
import mmap, select, socket
from mmap import error
# These should be fixed
try:
pass
except EnvironmentError:
pass
try:
pass
except IOError:
pass
try:
pass
except WindowsError:
pass
try:
pass
except mmap.error:
pass
try:
pass
except select.error:
pass
try:
pass
except socket.error:
pass
try:
pass
except error:
pass
# Should NOT be in parentheses when replaced
try:
pass
except (IOError,):
pass
try:
pass
except (mmap.error,):
pass
try:
pass
except (EnvironmentError, IOError, OSError, select.error):
pass
# Should be kept in parentheses (because multiple)
try:
pass
except (IOError, KeyError, OSError):
pass
# First should change, second should not
from .mmap import error
try:
pass
except (IOError, error):
pass
# These should not change
from foo import error
try:
pass
except (OSError, error):
pass
try:
pass
except:
pass
try:
pass
except AssertionError:
pass
try:
pass
except (mmap).error:
pass
try:
pass
except OSError:
pass
try:
pass
except (OSError, KeyError):
pass

View File

@@ -0,0 +1,17 @@
import mmap, socket, select
try:
pass
except (OSError, mmap.error, IOError):
pass
except (OSError, socket.error, KeyError):
pass
try:
pass
except (
OSError,
select.error,
IOError,
):
pass

View File

@@ -0,0 +1,50 @@
# These should not change
raise ValueError
raise ValueError(1)
from .mmap import error
raise error
# Testing the modules
import socket, mmap, select
raise socket.error
raise mmap.error
raise select.error
raise socket.error()
raise mmap.error(1)
raise select.error(1, 2)
raise socket.error(
1,
2,
3,
)
from mmap import error
raise error
from socket import error
raise error(1)
from select import error
raise error(1, 2)
# Testing the names
raise EnvironmentError
raise IOError
raise WindowsError
raise EnvironmentError()
raise IOError(1)
raise WindowsError(1, 2)
raise EnvironmentError(
1,
2,
3,
)
raise WindowsError
raise EnvironmentError(1)
raise IOError(1, 2)

View File

@@ -0,0 +1,88 @@
# These should be changed
if True:
import mock
if True:
import mock, sys
# This goes to from unitest import mock
import mock.mock
# Mock should go on a new line as `from unittest import mock`
import contextlib, mock, sys
# Mock should go on a new line as `from unittest import mock`
import mock, sys
x = "This code should be preserved one line below the mock"
# Mock should go on a new line as `from unittest import mock`
from mock import mock
# Should keep trailing comma
from mock import (
mock,
a,
b,
c,
)
from mock import (
a,
b,
c,
mock,
)
# Should not get a trailing comma
from mock import (
mock,
a,
b,
c
)
from mock import (
a,
b,
c,
mock
)
from mock import mock, a, b, c
from mock import a, b, c, mock
if True:
if False:
from mock import (
mock,
a,
b,
c
)
# These should not change:
import os, io
# Mock should go on a new line as `from unittest import mock`
import mock, mock
# Mock should go on a new line as `from unittest import mock as foo`
import mock as foo
# Mock should go on a new line as `from unittest import mock as foo`
from mock import mock as foo
if True:
# This should yield multiple, aliased imports.
import mock as foo, mock as bar, mock
# This should yield multiple, aliased imports, and preserve `os`.
import mock as foo, mock as bar, mock, os
if True:
# This should yield multiple, aliased imports.
from mock import mock as foo, mock as bar, mock
# This should be unchanged.
x = mock.Mock()
# This should change to `mock.Mock`().
x = mock.mock.Mock()

View File

@@ -0,0 +1,18 @@
# Should change
foo, bar, baz = [fn(x) for x in items]
foo, bar, baz =[fn(x) for x in items]
foo, bar, baz = [fn(x) for x in items]
foo, bar, baz = [[i for i in fn(x)] for x in items]
foo, bar, baz = [
fn(x)
for x in items
]
# Should not change
foo = [fn(x) for x in items]
x, = [await foo for foo in bar]

View File

@@ -0,0 +1,64 @@
# This should ignore both errors.
from typing import ( # noqa: F401
List,
Sequence,
)
# This should ignore both errors.
from typing import ( # noqa
List,
Sequence,
)
# This should ignore both errors.
from typing import (
List, # noqa: F401
Sequence, # noqa: F401
)
# This should ignore both errors.
from typing import (
List, # noqa
Sequence, # noqa
)
# This should ignore the first error.
from typing import (
Mapping, # noqa: F401
Union,
)
# This should ignore both errors.
from typing import ( # noqa
List,
Sequence,
)
# This should ignore the error, but the inner noqa should be marked as unused.
from typing import ( # noqa: F401
Optional, # noqa: F401
)
# This should ignore the error, but the inner noqa should be marked as unused.
from typing import ( # noqa
Optional, # noqa: F401
)
# This should ignore the error, but mark F501 as unused.
from typing import ( # noqa: F401
Dict, # noqa: F501
)
# This should ignore the error, but mark F501 as unused.
from typing import ( # noqa: F501
Tuple, # noqa: F401
)
# This should ignore both errors.
from typing import Any, AnyStr # noqa: F401
# This should ignore both errors.
from typing import AsyncIterable, AsyncGenerator # noqa
# This should mark F501 as unused.
from typing import Awaitable, AwaitableGenerator # noqa: F501

View File

@@ -64,11 +64,11 @@ Found 9 error(s).
9 potentially fixable with the --fix option.
```
Running from a parent directory should this "ignore" the `exclude` (hence, `concepts/file.py` gets
Running from a parent directory should "ignore" the `exclude` (hence, `concepts/file.py` gets
included in the output):
```
∴ (cd resources/test/project/examples && cargo run -- --config=docs/pyproject.toml .)
∴ (cd resources/test/project/examples && cargo run -- --config=docs/ruff.toml .)
docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used
docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
@@ -90,5 +90,7 @@ Unless we `--force-exclude`:
```
∴ cargo run resources/test/project/examples/excluded/ --force-exclude
warning: No Python files found under the given path(s)
∴ cargo run resources/test/project/examples/excluded/script.py --force-exclude
warning: No Python files found under the given path(s)
```

View File

@@ -154,6 +154,17 @@
}
]
},
"flake8-pytest-style": {
"description": "Options for the `flake8-pytest-style` plugin.",
"anyOf": [
{
"$ref": "#/definitions/Flake8PytestStyleOptions"
},
{
"type": "null"
}
]
},
"flake8-quotes": {
"description": "Options for the `flake8-quotes` plugin.",
"anyOf": [
@@ -652,8 +663,6 @@
"F821",
"F822",
"F823",
"F83",
"F831",
"F84",
"F841",
"F842",
@@ -753,6 +762,14 @@
"PGH002",
"PGH003",
"PGH004",
"PIE",
"PIE7",
"PIE79",
"PIE790",
"PIE794",
"PIE8",
"PIE80",
"PIE807",
"PLC",
"PLC0",
"PLC04",
@@ -798,6 +815,36 @@
"PLW06",
"PLW060",
"PLW0602",
"PT",
"PT0",
"PT00",
"PT001",
"PT002",
"PT003",
"PT004",
"PT005",
"PT006",
"PT007",
"PT008",
"PT009",
"PT01",
"PT010",
"PT011",
"PT012",
"PT013",
"PT015",
"PT016",
"PT017",
"PT018",
"PT019",
"PT02",
"PT020",
"PT021",
"PT022",
"PT023",
"PT024",
"PT025",
"PT026",
"Q",
"Q0",
"Q00",
@@ -850,6 +897,13 @@
"SIM1",
"SIM11",
"SIM118",
"SIM2",
"SIM22",
"SIM222",
"SIM223",
"SIM3",
"SIM30",
"SIM300",
"T",
"T1",
"T10",
@@ -911,7 +965,10 @@
"UP021",
"UP022",
"UP023",
"UP024",
"UP025",
"UP026",
"UP027",
"W",
"W2",
"W29",
@@ -953,6 +1010,13 @@
"enum": [
"numpy"
]
},
{
"description": "Use PEP257-style docstrings.",
"type": "string",
"enum": [
"pep257"
]
}
]
},
@@ -1047,6 +1111,79 @@
},
"additionalProperties": false
},
"Flake8PytestStyleOptions": {
"type": "object",
"properties": {
"fixture-parentheses": {
"description": "Boolean flag specifying whether `@pytest.fixture()` without parameters should have parentheses. If the option is set to `true` (the default), `@pytest.fixture()` is valid and `@pytest.fixture` is an error. If set to `false`, `@pytest.fixture` is valid and `@pytest.fixture()` is an error.",
"type": [
"boolean",
"null"
]
},
"mark-parentheses": {
"description": "Boolean flag specifying whether `@pytest.mark.foo()` without parameters should have parentheses. If the option is set to `true` (the default), `@pytest.mark.foo()` is valid and `@pytest.mark.foo` is an error. If set to `false`, `@pytest.fixture` is valid and `@pytest.mark.foo()` is an error.",
"type": [
"boolean",
"null"
]
},
"parametrize-names-type": {
"description": "Expected type for multiple argument names in `@pytest.mark.parametrize`. The following values are supported: * `csv` — a comma-separated list, e.g. `@pytest.mark.parametrize('name1,name2', ...)` * `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'), ...)` * `list` — e.g. `@pytest.mark.parametrize(['name1', 'name2'], ...)`",
"anyOf": [
{
"$ref": "#/definitions/ParametrizeNameType"
},
{
"type": "null"
}
]
},
"parametrize-values-row-type": {
"description": "Expected type for each row of values in `@pytest.mark.parametrize` in case of multiple parameters. The following values are supported: * `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'), [(1, 2), (3, 4)])` * `list` — e.g. `@pytest.mark.parametrize(('name1', 'name2'), [[1, 2], [3, 4]])`",
"anyOf": [
{
"$ref": "#/definitions/ParametrizeValuesRowType"
},
{
"type": "null"
}
]
},
"parametrize-values-type": {
"description": "Expected type for the list of values rows in `@pytest.mark.parametrize`. The following values are supported: * `tuple` — e.g. `@pytest.mark.parametrize('name', (1, 2, 3))` * `list` (default) — e.g. `@pytest.mark.parametrize('name', [1, 2, 3])`",
"anyOf": [
{
"$ref": "#/definitions/ParametrizeValuesType"
},
{
"type": "null"
}
]
},
"raises-extend-require-match-for": {
"description": "List of additional exception names that require a match= parameter in a `pytest.raises()` call. This extends the default list of exceptions that require a match= parameter. This option is useful if you want to extend the default list of exceptions that require a match= parameter without having to specify the entire list. Note that this option does not remove any exceptions from the default list.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"raises-require-match-for": {
"description": "List of exception names that require a match= parameter in a `pytest.raises()` call.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"Flake8QuotesOptions": {
"type": "object",
"properties": {
@@ -1222,6 +1359,28 @@
},
"additionalProperties": false
},
"ParametrizeNameType": {
"type": "string",
"enum": [
"csv",
"tuple",
"list"
]
},
"ParametrizeValuesRowType": {
"type": "string",
"enum": [
"tuple",
"list"
]
},
"ParametrizeValuesType": {
"type": "string",
"enum": [
"tuple",
"list"
]
},
"Pep8NamingOptions": {
"type": "object",
"properties": {
@@ -1275,7 +1434,7 @@
"type": "object",
"properties": {
"convention": {
"description": "Whether to use Google-style or Numpy-style conventions when detecting docstring sections. By default, conventions will be inferred from the available sections.",
"description": "Whether to use Google-style or NumPy-style conventions or the PEP257 defaults when analyzing docstring sections.",
"anyOf": [
{
"$ref": "#/definitions/Convention"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.202"
version = "0.0.209"
edition = "2021"
[dependencies]
@@ -11,9 +11,9 @@ itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
once_cell = { version = "1.16.0" }
ruff = { path = ".." }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }
schemars = { version = "0.8.11" }
serde_json = {version="1.0.91"}
strum = { version = "0.24.1", features = ["strum_macros"] }

View File

@@ -4,7 +4,8 @@ use anyhow::Result;
use clap::Args;
use crate::{
generate_check_code_prefix, generate_json_schema, generate_options, generate_rules_table,
generate_check_code_prefix, generate_cli_help, generate_json_schema, generate_options,
generate_rules_table,
};
#[derive(Args)]
@@ -27,5 +28,8 @@ pub fn main(cli: &Cli) -> Result<()> {
generate_options::main(&generate_options::Cli {
dry_run: cli.dry_run,
})?;
generate_cli_help::main(&generate_cli_help::Cli {
dry_run: cli.dry_run,
})?;
Ok(())
}

View File

@@ -10,7 +10,7 @@ use anyhow::{ensure, Result};
use clap::Parser;
use codegen::{Scope, Type, Variant};
use itertools::Itertools;
use ruff::checks::{CheckCode, PREFIX_REDIRECTS};
use ruff::registry::{CheckCode, PREFIX_REDIRECTS};
use strum::IntoEnumIterator;
const ALL: &str = "ALL";
@@ -19,7 +19,7 @@ const ALL: &str = "ALL";
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// Write the generated source code to stdout (rather than to
/// `src/checks_gen.rs`).
/// `src/registry_gen.rs`).
#[arg(long)]
pub(crate) dry_run: bool,
}
@@ -172,7 +172,7 @@ pub fn main(cli: &Cli) -> Result<()> {
output.push_str("use strum_macros::{AsRefStr, EnumString};");
output.push('\n');
output.push('\n');
output.push_str("use crate::checks::CheckCode;");
output.push_str("use crate::registry::CheckCode;");
output.push('\n');
output.push_str("use crate::one_time_warning;");
output.push('\n');
@@ -204,14 +204,14 @@ pub fn main(cli: &Cli) -> Result<()> {
let Output { status, stdout, .. } = rustfmt.wait_with_output()?;
ensure!(status.success(), "rustfmt failed with {status}");
// Write the output to `src/checks_gen.rs` (or stdout).
// Write the output to `src/registry_gen.rs` (or stdout).
if cli.dry_run {
println!("{}", String::from_utf8(stdout)?);
} else {
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("src/checks_gen.rs");
.join("src/registry_gen.rs");
if fs::read(&file).map_or(true, |old| old != stdout) {
fs::write(&file, stdout)?;
}

View File

@@ -0,0 +1,38 @@
//! Generate CLI help.
use anyhow::Result;
use clap::{Args, CommandFactory};
use ruff::cli::Cli as MainCli;
use crate::utils::replace_readme_section;
const HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated cli help. -->";
const HELP_END_PRAGMA: &str = "<!-- End auto-generated cli help. -->";
#[derive(Args)]
pub struct Cli {
/// Write the generated help to stdout (rather than to `README.md`).
#[arg(long)]
pub(crate) dry_run: bool,
}
fn trim_lines(s: &str) -> String {
s.lines().map(str::trim_end).collect::<Vec<_>>().join("\n")
}
pub fn main(cli: &Cli) -> Result<()> {
let mut cmd = MainCli::command();
let output = trim_lines(cmd.render_help().to_string().trim());
if cli.dry_run {
print!("{output}");
} else {
replace_readme_section(
&format!("```shell\n{output}\n```\n"),
HELP_BEGIN_PRAGMA,
HELP_END_PRAGMA,
)?;
}
Ok(())
}

View File

@@ -1,16 +1,13 @@
//! Generate a Markdown-compatible listing of configuration options.
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use itertools::Itertools;
use ruff::settings::options::Options;
use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionField};
use crate::utils::replace_readme_section;
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated options sections. -->";
const END_PRAGMA: &str = "<!-- End auto-generated options sections. -->";
@@ -95,30 +92,7 @@ pub fn main(cli: &Cli) -> Result<()> {
if cli.dry_run {
print!("{output}");
} else {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("README.md");
let existing = fs::read_to_string(&file)?;
// Extract the prefix.
let index = existing
.find(BEGIN_PRAGMA)
.expect("Unable to find begin pragma");
let prefix = &existing[..index + BEGIN_PRAGMA.len()];
// Extract the suffix.
let index = existing
.find(END_PRAGMA)
.expect("Unable to find end pragma");
let suffix = &existing[index..];
// Write the prefix, new contents, and suffix.
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
write!(f, "{prefix}\n\n")?;
write!(f, "{output}")?;
write!(f, "{suffix}")?;
replace_readme_section(&output, BEGIN_PRAGMA, END_PRAGMA)?;
}
Ok(())

View File

@@ -1,16 +1,13 @@
//! Generate a Markdown-compatible table of supported lint rules.
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use itertools::Itertools;
use ruff::checks::{CheckCategory, CheckCode};
use ruff::registry::{CheckCategory, CheckCode};
use strum::IntoEnumIterator;
use crate::utils::replace_readme_section;
const TABLE_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated sections. -->";
const TABLE_END_PRAGMA: &str = "<!-- End auto-generated sections. -->";
@@ -85,32 +82,3 @@ pub fn main(cli: &Cli) -> Result<()> {
Ok(())
}
fn replace_readme_section(content: &str, begin_pragma: &str, end_pragma: &str) -> Result<()> {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("README.md");
let existing = fs::read_to_string(&file)?;
// Extract the prefix.
let index = existing
.find(begin_pragma)
.expect("Unable to find begin pragma");
let prefix = &existing[..index + begin_pragma.len()];
// Extract the suffix.
let index = existing
.find(end_pragma)
.expect("Unable to find end pragma");
let suffix = &existing[index..];
// Write the prefix, new contents, and suffix.
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
writeln!(f, "{prefix}")?;
write!(f, "{content}")?;
write!(f, "{suffix}")?;
Ok(())
}

View File

@@ -13,6 +13,7 @@
pub mod generate_all;
pub mod generate_check_code_prefix;
pub mod generate_cli_help;
pub mod generate_json_schema;
pub mod generate_options;
pub mod generate_rules_table;
@@ -20,3 +21,4 @@ pub mod print_ast;
pub mod print_cst;
pub mod print_tokens;
pub mod round_trip;
mod utils;

View File

@@ -14,8 +14,8 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use ruff_dev::{
generate_all, generate_check_code_prefix, generate_json_schema, generate_options,
generate_rules_table, print_ast, print_cst, print_tokens, round_trip,
generate_all, generate_check_code_prefix, generate_cli_help, generate_json_schema,
generate_options, generate_rules_table, print_ast, print_cst, print_tokens, round_trip,
};
#[derive(Parser)]
@@ -38,6 +38,8 @@ enum Commands {
GenerateRulesTable(generate_rules_table::Cli),
/// Generate a Markdown-compatible listing of configuration options.
GenerateOptions(generate_options::Cli),
/// Generate CLI help.
GenerateCliHelp(generate_cli_help::Cli),
/// Print the AST for a given Python file.
PrintAST(print_ast::Cli),
/// Print the LibCST CST for a given Python file.
@@ -56,6 +58,7 @@ fn main() -> Result<()> {
Commands::GenerateJSONSchema(args) => generate_json_schema::main(args)?,
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
Commands::GenerateOptions(args) => generate_options::main(args)?,
Commands::GenerateCliHelp(args) => generate_cli_help::main(args)?,
Commands::PrintAST(args) => print_ast::main(args)?,
Commands::PrintCST(args) => print_cst::main(args)?,
Commands::PrintTokens(args) => print_tokens::main(args)?,

View File

@@ -22,7 +22,11 @@ pub fn main(cli: &Cli) -> Result<()> {
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
let locator = SourceCodeLocator::new(&contents);
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
let mut generator = SourceCodeGenerator::new(
stylist.indentation(),
stylist.quote(),
stylist.line_ending(),
);
generator.unparse_suite(&python_ast);
println!("{}", generator.generate()?);
Ok(())

35
ruff_dev/src/utils.rs Normal file
View File

@@ -0,0 +1,35 @@
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
pub fn replace_readme_section(content: &str, begin_pragma: &str, end_pragma: &str) -> Result<()> {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("README.md");
let existing = fs::read_to_string(&file)?;
// Extract the prefix.
let index = existing
.find(begin_pragma)
.expect("Unable to find begin pragma");
let prefix = &existing[..index + begin_pragma.len()];
// Extract the suffix.
let index = existing
.find(end_pragma)
.expect("Unable to find end pragma");
let suffix = &existing[index..];
// Write the prefix, new contents, and suffix.
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
writeln!(f, "{prefix}")?;
write!(f, "{content}")?;
write!(f, "{suffix}")?;
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.202"
version = "0.0.209"
edition = "2021"
[lib]

View File

@@ -1,21 +0,0 @@
[flake8]
exclude =
# Defaults
.svn,
CVS,
.bzr,
.hg,
.git,
__pycache__,
.tox,
.idea,
.mypy_cache,
.venv,
node_modules,
# Custom
_state_machine.py,
test_fstring.py,
bad_coding2.py,
badsyntax_*.py
ignore_names = foo, bar
quotes = double

139
scripts/add_check.py Normal file
View File

@@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""Generate boilerplate for a new check.
Example usage:
python scripts/add_check.py \
--name PreferListBuiltin \
--code PIE807 \
--plugin flake8-pie
"""
import argparse
import os
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def dir_name(plugin: str) -> str:
return plugin.replace("-", "_")
def pascal_case(plugin: str) -> str:
"""Convert from snake-case to PascalCase."""
return "".join(word.title() for word in plugin.split("-"))
def snake_case(name: str) -> str:
"""Convert from PascalCase to snake_case."""
return "".join(f"_{word.lower()}" if word.isupper() else word for word in name).lstrip("_")
def main(*, name: str, code: str, plugin: str) -> None:
# Create a test fixture.
with open(
os.path.join(ROOT_DIR, f"resources/test/fixtures/{dir_name(plugin)}/{code}.py"),
"a",
):
pass
# Add the relevant `#testcase` macro.
with open(os.path.join(ROOT_DIR, f"src/{dir_name(plugin)}/mod.rs"), "r") as fp:
content = fp.read()
with open(os.path.join(ROOT_DIR, f"src/{dir_name(plugin)}/mod.rs"), "w") as fp:
for line in content.splitlines():
if line.strip() == "fn checks(check_code: CheckCode, path: &Path) -> Result<()> {":
indent = line.split("fn checks(check_code: CheckCode, path: &Path) -> Result<()> {")[0]
fp.write(f'{indent}#[test_case(CheckCode::{code}, Path::new("{code}.py"); "{code}")]')
fp.write("\n")
fp.write(line)
fp.write("\n")
# Add the relevant plugin function.
with open(os.path.join(ROOT_DIR, f"src/{dir_name(plugin)}/plugins.rs"), "a") as fp:
fp.write(
f"""
/// {code}
pub fn {snake_case(name)}(checker: &mut Checker) {{}}
"""
)
fp.write("\n")
# Add the relevant sections to `src/registry.rs`.
with open(os.path.join(ROOT_DIR, "src/registry.rs"), "r") as fp:
content = fp.read()
index = 0
with open(os.path.join(ROOT_DIR, "src/registry.rs"), "w") as fp:
for line in content.splitlines():
fp.write(line)
fp.write("\n")
if line.strip() == f"// {plugin}":
if index == 0:
# `CheckCode` definition
indent = line.split(f"// {plugin}")[0]
fp.write(f"{indent}{code},")
fp.write("\n")
elif index == 1:
# `CheckKind` definition
indent = line.split(f"// {plugin}")[0]
fp.write(f"{indent}{name},")
fp.write("\n")
elif index == 2:
# `CheckCode#kind()`
indent = line.split(f"// {plugin}")[0]
fp.write(f"{indent}CheckCode::{code} => CheckKind::{name},")
fp.write("\n")
elif index == 3:
# `CheckCode#category()`
indent = line.split(f"// {plugin}")[0]
fp.write(f"{indent}CheckCode::{code} => CheckCategory::{pascal_case(plugin)},")
fp.write("\n")
elif index == 4:
# `CheckKind#code()`
indent = line.split(f"// {plugin}")[0]
fp.write(f"{indent}CheckKind::{name} => &CheckCode::{code},")
fp.write("\n")
elif index == 5:
# `CheckCode#body`
indent = line.split(f"// {plugin}")[0]
fp.write(f'{indent}CheckKind::{name} => todo!("Write message body for {code}"),')
fp.write("\n")
index += 1
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate boilerplate for a new check.",
epilog="python scripts/add_check.py --name PreferListBuiltin --code PIE807 --plugin flake8-pie",
)
parser.add_argument(
"--name",
type=str,
required=True,
help="The name of the check to generate, in PascalCase (e.g., 'LineTooLong').",
)
parser.add_argument(
"--code",
type=str,
required=True,
help="The code of the check to generate (e.g., 'A001').",
)
parser.add_argument(
"--plugin",
type=str,
required=True,
help="The plugin with which the check is associated (e.g., 'flake8-builtins').",
)
args = parser.parse_args()
main(name=args.name, code=args.code, plugin=args.plugin)

133
scripts/add_plugin.py Normal file
View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""Generate boilerplate for a new plugin.
Example usage:
python scripts/add_plugin.py \
flake8-pie \
--url https://pypi.org/project/flake8-pie/0.16.0/
"""
import argparse
import os
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def dir_name(plugin: str) -> str:
return plugin.replace("-", "_")
def pascal_case(plugin: str) -> str:
return "".join(word.title() for word in plugin.split("-"))
def main(*, plugin: str, url: str) -> None:
# Create the test fixture folder.
os.makedirs(
os.path.join(ROOT_DIR, f"resources/test/fixtures/{dir_name(plugin)}"),
exist_ok=True,
)
# Create the Rust module.
os.makedirs(os.path.join(ROOT_DIR, f"src/{dir_name(plugin)}"), exist_ok=True)
with open(os.path.join(ROOT_DIR, f"src/{dir_name(plugin)}/plugins.rs"), "a"):
pass
with open(os.path.join(ROOT_DIR, f"src/{dir_name(plugin)}/mod.rs"), "w+") as fp:
fp.write("pub mod plugins;\n")
fp.write("\n")
fp.write(
"""#[cfg(test)]
mod tests {
use std::convert::AsRef;
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::registry::CheckCode;
use crate::linter::test_path;
use crate::settings;
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let checks = test_path(
Path::new("./resources/test/fixtures/%s")
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
)?;
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
}
"""
% dir_name(plugin)
)
# Add the plugin to `lib.rs`.
with open(os.path.join(ROOT_DIR, "src/lib.rs"), "a") as fp:
fp.write(f"pub mod {dir_name(plugin)};")
# Add the relevant sections to `src/registry.rs`.
with open(os.path.join(ROOT_DIR, "src/registry.rs"), "r") as fp:
content = fp.read()
with open(os.path.join(ROOT_DIR, "src/registry.rs"), "w") as fp:
for line in content.splitlines():
if line.strip() == "// Ruff":
indent = line.split("// Ruff")[0]
fp.write(f"{indent}// {plugin}")
fp.write("\n")
elif line.strip() == "Ruff,":
indent = line.split("Ruff,")[0]
fp.write(f"{indent}{pascal_case(plugin)},")
fp.write("\n")
elif line.strip() == 'CheckCategory::Ruff => "Ruff-specific rules",':
indent = line.split('CheckCategory::Ruff => "Ruff-specific rules",')[0]
fp.write(f'{indent}CheckCategory::{pascal_case(plugin)} => "{plugin}",')
fp.write("\n")
elif line.strip() == "CheckCategory::Ruff => vec![CheckCodePrefix::RUF],":
indent = line.split("CheckCategory::Ruff => vec![CheckCodePrefix::RUF],")[0]
fp.write(
f"{indent}CheckCategory::{pascal_case(plugin)} => vec![\n"
f'{indent} todo!("Fill-in prefix after generating codes")\n'
f"{indent}],"
)
fp.write("\n")
elif line.strip() == "CheckCategory::Ruff => None,":
indent = line.split("CheckCategory::Ruff => None,")[0]
fp.write(f"{indent}CheckCategory::{pascal_case(plugin)} => " f'Some(("{url}", &Platform::PyPI)),')
fp.write("\n")
fp.write(line)
fp.write("\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate boilerplate for a new plugin.",
epilog=(
"Example usage: python scripts/add_plugin.py flake8-pie "
"--url https://pypi.org/project/flake8-pie/0.16.0/"
),
)
parser.add_argument(
"plugin",
required=True,
type=str,
help="The name of the plugin to generate.",
)
parser.add_argument(
"--url",
required=True,
type=str,
help="The URL of the latest release in PyPI.",
)
args = parser.parse_args()
main(plugin=args.plugin, url=args.url)

462
scripts/poetry.lock generated
View File

@@ -1,10 +1,16 @@
# This file is automatically @generated by Poetry and should not be changed by hand.
[[package]]
name = "astroid"
version = "2.12.4"
version = "2.12.13"
description = "An abstract syntax tree for Python with inference support."
category = "main"
optional = false
python-versions = ">=3.7.2"
files = [
{file = "astroid-2.12.13-py3-none-any.whl", hash = "sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907"},
{file = "astroid-2.12.13.tar.gz", hash = "sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"},
]
[package.dependencies]
lazy-object-proxy = ">=1.4.0"
@@ -12,39 +18,43 @@ wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""}
[[package]]
name = "autoflake"
version = "1.5.1"
version = "1.7.8"
description = "Removes unused imports and unused variables"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "autoflake-1.7.8-py3-none-any.whl", hash = "sha256:46373ef69b6714f5064c923bb28bd797c4f8a9497f557d87fc36665c6d956b39"},
{file = "autoflake-1.7.8.tar.gz", hash = "sha256:e7e46372dee46fa1c97acf310d99d922b63d369718a270809d7c278d34a194cf"},
]
[package.dependencies]
pyflakes = ">=1.1.0"
toml = ">=0.10.2"
pyflakes = ">=1.1.0,<3"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
[[package]]
name = "colorama"
version = "0.4.5"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "cycler"
version = "0.11.0"
description = "Composable style cycles"
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "dill"
version = "0.3.5.1"
version = "0.3.6"
description = "serialize all of python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
python-versions = ">=3.7"
files = [
{file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"},
{file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"},
]
[package.extras]
graph = ["objgraph (>=1.7.2)"]
@@ -56,82 +66,62 @@ description = "the modular source code checker: pep8 pyflakes and co"
category = "main"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
{file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
]
[package.dependencies]
mccabe = ">=0.7.0,<0.8.0"
pycodestyle = ">=2.9.0,<2.10.0"
pyflakes = ">=2.5.0,<2.6.0"
[[package]]
name = "fonttools"
version = "4.37.1"
description = "Tools to manipulate font files"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=14.0.0)", "xattr", "zopfli (>=0.1.4)"]
graphite = ["lz4 (>=1.7.4.2)"]
interpolatable = ["munkres", "scipy"]
lxml = ["lxml (>=4.0,<5)"]
pathops = ["skia-pathops (>=0.5.0)"]
plot = ["matplotlib"]
repacker = ["uharfbuzz (>=0.23.0)"]
symfont = ["sympy"]
type1 = ["xattr"]
ufo = ["fs (>=2.2.0,<3)"]
unicode = ["unicodedata2 (>=14.0.0)"]
woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
[[package]]
name = "isort"
version = "5.10.1"
version = "5.11.4"
description = "A Python utility / library to sort Python imports."
category = "main"
optional = false
python-versions = ">=3.6.1,<4.0"
python-versions = ">=3.7.0"
files = [
{file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"},
{file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"},
]
[package.extras]
colors = ["colorama (>=0.4.3,<0.5.0)"]
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
plugins = ["setuptools"]
requirements_deprecated_finder = ["pip-api", "pipreqs"]
[[package]]
name = "kiwisolver"
version = "1.4.4"
description = "A fast implementation of the Cassowary constraint solver"
category = "main"
optional = false
python-versions = ">=3.7"
requirements-deprecated-finder = ["pip-api", "pipreqs"]
[[package]]
name = "lazy-object-proxy"
version = "1.7.1"
version = "1.8.0"
description = "A fast and thorough lazy object proxy."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "matplotlib"
version = "3.5.3"
description = "Python plotting package"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
cycler = ">=0.10"
fonttools = ">=4.22.0"
kiwisolver = ">=1.0.1"
numpy = ">=1.17"
packaging = ">=20.0"
pillow = ">=6.2.0"
pyparsing = ">=2.2.1"
python-dateutil = ">=2.7"
setuptools_scm = ">=4,<7"
files = [
{file = "lazy-object-proxy-1.8.0.tar.gz", hash = "sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156"},
{file = "lazy_object_proxy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe"},
{file = "lazy_object_proxy-1.8.0-cp310-cp310-win32.whl", hash = "sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25"},
{file = "lazy_object_proxy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b"},
{file = "lazy_object_proxy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7"},
{file = "lazy_object_proxy-1.8.0-cp311-cp311-win32.whl", hash = "sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e"},
{file = "lazy_object_proxy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d"},
{file = "lazy_object_proxy-1.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c"},
{file = "lazy_object_proxy-1.8.0-cp37-cp37m-win32.whl", hash = "sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd"},
{file = "lazy_object_proxy-1.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858"},
{file = "lazy_object_proxy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada"},
{file = "lazy_object_proxy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f"},
{file = "lazy_object_proxy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c"},
{file = "lazy_object_proxy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288"},
{file = "lazy_object_proxy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f"},
{file = "lazy_object_proxy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0"},
{file = "lazy_object_proxy-1.8.0-pp37-pypy37_pp73-any.whl", hash = "sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891"},
{file = "lazy_object_proxy-1.8.0-pp38-pypy38_pp73-any.whl", hash = "sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec"},
{file = "lazy_object_proxy-1.8.0-pp39-pypy39_pp73-any.whl", hash = "sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8"},
]
[[package]]
name = "mccabe"
@@ -140,49 +130,26 @@ description = "McCabe checker, plugin for flake8"
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "numpy"
version = "1.23.2"
description = "NumPy is the fundamental package for array computing with Python."
category = "main"
optional = false
python-versions = ">=3.8"
[[package]]
name = "packaging"
version = "21.3"
description = "Core utilities for Python packages"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pillow"
version = "9.2.0"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"]
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]]
name = "platformdirs"
version = "2.5.2"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
version = "2.6.2"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"},
{file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"},
]
[package.extras]
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
[[package]]
name = "pycodestyle"
@@ -191,6 +158,10 @@ description = "Python style guide checker"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
{file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
]
[[package]]
name = "pyflakes"
@@ -199,19 +170,27 @@ description = "passive checker of Python programs"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
{file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
]
[[package]]
name = "pylint"
version = "2.15.0"
version = "2.15.9"
description = "python code static checker"
category = "main"
optional = false
python-versions = ">=3.7.2"
files = [
{file = "pylint-2.15.9-py3-none-any.whl", hash = "sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb"},
{file = "pylint-2.15.9.tar.gz", hash = "sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4"},
]
[package.dependencies]
astroid = ">=2.12.4,<=2.14.0-dev0"
astroid = ">=2.12.13,<=2.14.0-dev0"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
dill = ">=0.2"
dill = {version = ">=0.2", markers = "python_version < \"3.11\""}
isort = ">=4.2.5,<6"
mccabe = ">=0.6,<0.8"
platformdirs = ">=2.2.0"
@@ -222,71 +201,6 @@ tomlkit = ">=0.10.1"
spelling = ["pyenchant (>=3.2,<4.0)"]
testutils = ["gitpython (>3)"]
[[package]]
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "main"
optional = false
python-versions = ">=3.6.8"
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "python-dateutil"
version = "2.8.2"
description = "Extensions to the standard Python datetime module"
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
[package.dependencies]
six = ">=1.5"
[[package]]
name = "scipy"
version = "1.9.1"
description = "SciPy: Scientific Library for Python"
category = "main"
optional = false
python-versions = ">=3.8,<3.12"
[package.dependencies]
numpy = ">=1.18.5,<1.25.0"
[[package]]
name = "setuptools-scm"
version = "6.4.2"
description = "the blessed package to manage your versions by scm tags"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
packaging = ">=20.0"
tomli = ">=1.0.0"
[package.extras]
test = ["pytest (>=6.2)", "virtualenv (>20)"]
toml = ["setuptools (>=42)"]
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "tomli"
version = "2.0.1"
@@ -294,14 +208,22 @@ description = "A lil' TOML parser"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "tomlkit"
version = "0.11.4"
version = "0.11.6"
description = "Style preserving TOML library"
category = "main"
optional = false
python-versions = ">=3.6,<4.0"
python-versions = ">=3.6"
files = [
{file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"},
{file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"},
]
[[package]]
name = "wrapt"
@@ -310,190 +232,7 @@ description = "Module for decorators, wrappers and monkey patching."
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[metadata]
lock-version = "1.1"
python-versions = ">=3.10,<3.11"
content-hash = "45e5caa8c4ac70f1b0d0a4f52a19bf7e2cacd55fa2e3f2ac36480491974d3cf8"
[metadata.files]
astroid = [
{file = "astroid-2.12.4-py3-none-any.whl", hash = "sha256:af71cdc0775b6e4d88076746620e2c8cd1bf4533a9977cfdd00eeea97d95530c"},
{file = "astroid-2.12.4.tar.gz", hash = "sha256:39fa822c82dc112f5072a208ddf01c58184043aa90e3e469786fa0520c71aaa7"},
]
autoflake = [
{file = "autoflake-1.5.1-py2.py3-none-any.whl", hash = "sha256:275e05e3caa8307269ad4d46f2e058b76a5c41ca7d1a7f57158d64e9df3ffdd6"},
{file = "autoflake-1.5.1.tar.gz", hash = "sha256:8272efbecf7c6d5e2b00fa3b2998478a3ad92d7c914a49a527d733dae7f800c5"},
]
colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
cycler = [
{file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"},
{file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"},
]
dill = [
{file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"},
{file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"},
]
flake8 = []
fonttools = [
{file = "fonttools-4.37.1-py3-none-any.whl", hash = "sha256:fff6b752e326c15756c819fe2fe7ceab69f96a1dbcfe8911d0941cdb49905007"},
{file = "fonttools-4.37.1.zip", hash = "sha256:4606e1a88ee1f6699d182fea9511bd9a8a915d913eab4584e5226da1180fcce7"},
]
isort = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
]
kiwisolver = []
lazy-object-proxy = [
{file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"},
{file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"},
]
matplotlib = [
{file = "matplotlib-3.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a206a1b762b39398efea838f528b3a6d60cdb26fe9d58b48265787e29cd1d693"},
{file = "matplotlib-3.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd45a6f3e93a780185f70f05cf2a383daed13c3489233faad83e81720f7ede24"},
{file = "matplotlib-3.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d62880e1f60e5a30a2a8484432bcb3a5056969dc97258d7326ad465feb7ae069"},
{file = "matplotlib-3.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ab29589cef03bc88acfa3a1490359000c18186fc30374d8aa77d33cc4a51a4a"},
{file = "matplotlib-3.5.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2886cc009f40e2984c083687251821f305d811d38e3df8ded414265e4583f0c5"},
{file = "matplotlib-3.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c995f7d9568f18b5db131ab124c64e51b6820a92d10246d4f2b3f3a66698a15b"},
{file = "matplotlib-3.5.3-cp310-cp310-win32.whl", hash = "sha256:6bb93a0492d68461bd458eba878f52fdc8ac7bdb6c4acdfe43dba684787838c2"},
{file = "matplotlib-3.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:2e6d184ebe291b9e8f7e78bbab7987d269c38ea3e062eace1fe7d898042ef804"},
{file = "matplotlib-3.5.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ea6aef5c4338e58d8d376068e28f80a24f54e69f09479d1c90b7172bad9f25b"},
{file = "matplotlib-3.5.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:839d47b8ead7ad9669aaacdbc03f29656dc21f0d41a6fea2d473d856c39c8b1c"},
{file = "matplotlib-3.5.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b4fa56159dc3c7f9250df88f653f085068bcd32dcd38e479bba58909254af7f"},
{file = "matplotlib-3.5.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:94ff86af56a3869a4ae26a9637a849effd7643858a1a04dd5ee50e9ab75069a7"},
{file = "matplotlib-3.5.3-cp37-cp37m-win32.whl", hash = "sha256:35a8ad4dddebd51f94c5d24bec689ec0ec66173bf614374a1244c6241c1595e0"},
{file = "matplotlib-3.5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:43e9d3fa077bf0cc95ded13d331d2156f9973dce17c6f0c8b49ccd57af94dbd9"},
{file = "matplotlib-3.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:22227c976ad4dc8c5a5057540421f0d8708c6560744ad2ad638d48e2984e1dbc"},
{file = "matplotlib-3.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf618a825deb6205f015df6dfe6167a5d9b351203b03fab82043ae1d30f16511"},
{file = "matplotlib-3.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9befa5954cdbc085e37d974ff6053da269474177921dd61facdad8023c4aeb51"},
{file = "matplotlib-3.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3840c280ebc87a48488a46f760ea1c0c0c83fcf7abbe2e6baf99d033fd35fd8"},
{file = "matplotlib-3.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dacddf5bfcec60e3f26ec5c0ae3d0274853a258b6c3fc5ef2f06a8eb23e042be"},
{file = "matplotlib-3.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b428076a55fb1c084c76cb93e68006f27d247169f056412607c5c88828d08f88"},
{file = "matplotlib-3.5.3-cp38-cp38-win32.whl", hash = "sha256:874df7505ba820e0400e7091199decf3ff1fde0583652120c50cd60d5820ca9a"},
{file = "matplotlib-3.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:b28de401d928890187c589036857a270a032961411934bdac4cf12dde3d43094"},
{file = "matplotlib-3.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3211ba82b9f1518d346f6309df137b50c3dc4421b4ed4815d1d7eadc617f45a1"},
{file = "matplotlib-3.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6fe807e8a22620b4cd95cfbc795ba310dc80151d43b037257250faf0bfcd82bc"},
{file = "matplotlib-3.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c096363b206a3caf43773abebdbb5a23ea13faef71d701b21a9c27fdcef72f4"},
{file = "matplotlib-3.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcdfcb0f976e1bac6721d7d457c17be23cf7501f977b6a38f9d38a3762841f7"},
{file = "matplotlib-3.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e64ac9be9da6bfff0a732e62116484b93b02a0b4d4b19934fb4f8e7ad26ad6a"},
{file = "matplotlib-3.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:73dd93dc35c85dece610cca8358003bf0760d7986f70b223e2306b4ea6d1406b"},
{file = "matplotlib-3.5.3-cp39-cp39-win32.whl", hash = "sha256:879c7e5fce4939c6aa04581dfe08d57eb6102a71f2e202e3314d5fbc072fd5a0"},
{file = "matplotlib-3.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:ab8d26f07fe64f6f6736d635cce7bfd7f625320490ed5bfc347f2cdb4fae0e56"},
{file = "matplotlib-3.5.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:99482b83ebf4eb6d5fc6813d7aacdefdd480f0d9c0b52dcf9f1cc3b2c4b3361a"},
{file = "matplotlib-3.5.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f814504e459c68118bf2246a530ed953ebd18213dc20e3da524174d84ed010b2"},
{file = "matplotlib-3.5.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57f1b4e69f438a99bb64d7f2c340db1b096b41ebaa515cf61ea72624279220ce"},
{file = "matplotlib-3.5.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d2484b350bf3d32cae43f85dcfc89b3ed7bd2bcd781ef351f93eb6fb2cc483f9"},
{file = "matplotlib-3.5.3.tar.gz", hash = "sha256:339cac48b80ddbc8bfd05daae0a3a73414651a8596904c2a881cfd1edb65f26c"},
]
mccabe = []
numpy = []
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pillow = []
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
]
pycodestyle = []
pyflakes = []
pylint = [
{file = "pylint-2.15.0-py3-none-any.whl", hash = "sha256:4b124affc198b7f7c9b5f9ab690d85db48282a025ef9333f51d2d7281b92a6c3"},
{file = "pylint-2.15.0.tar.gz", hash = "sha256:4f3f7e869646b0bd63b3dfb79f3c0f28fc3d2d923ea220d52620fd625aed92b0"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
python-dateutil = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
]
scipy = [
{file = "scipy-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c61b4a91a702e8e04aeb0bfc40460e1f17a640977c04dda8757efb0199c75332"},
{file = "scipy-1.9.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d79da472015d0120ba9b357b28a99146cd6c17b9609403164b1a8ed149b4dfc8"},
{file = "scipy-1.9.1-cp310-cp310-macosx_12_0_universal2.macosx_10_9_x86_64.whl", hash = "sha256:825951b88f56765aeb6e5e38ac9d7d47407cfaaeb008d40aa1b45a2d7ea2731e"},
{file = "scipy-1.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f950a04b33e17b38ff561d5a0951caf3f5b47caa841edd772ffb7959f20a6af0"},
{file = "scipy-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cc81ac25659fec73599ccc52c989670e5ccd8974cf34bacd7b54a8d809aff1a"},
{file = "scipy-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:8d3faa40ac16c6357aaf7ea50394ea6f1e8e99d75e927a51102b1943b311b4d9"},
{file = "scipy-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7a412c476a91b080e456229e413792bbb5d6202865dae963d1e6e28c2bb58691"},
{file = "scipy-1.9.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:eb954f5aca4d26f468bbebcdc5448348eb287f7bea536c6306f62ea062f63d9a"},
{file = "scipy-1.9.1-cp38-cp38-macosx_12_0_universal2.macosx_10_9_x86_64.whl", hash = "sha256:3c6f5d1d4b9a5e4fe5e14f26ffc9444fc59473bbf8d45dc4a9a15283b7063a72"},
{file = "scipy-1.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bc4e2c77d4cd015d739e75e74ebbafed59ba8497a7ed0fd400231ed7683497c4"},
{file = "scipy-1.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0419485dbcd0ed78c0d5bf234c5dd63e86065b39b4d669e45810d42199d49521"},
{file = "scipy-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34441dfbee5b002f9e15285014fd56e5e3372493c3e64ae297bae2c4b9659f5a"},
{file = "scipy-1.9.1-cp38-cp38-win32.whl", hash = "sha256:b97b479f39c7e4aaf807efd0424dec74bbb379108f7d22cf09323086afcd312c"},
{file = "scipy-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8fe305d9d67a81255e06203454729405706907dccbdfcc330b7b3482a6c371d"},
{file = "scipy-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:39ab9240cd215a9349c85ab908dda6d732f7d3b4b192fa05780812495536acc4"},
{file = "scipy-1.9.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:71487c503e036740635f18324f62a11f283a632ace9d35933b2b0a04fd898c98"},
{file = "scipy-1.9.1-cp39-cp39-macosx_12_0_universal2.macosx_10_9_x86_64.whl", hash = "sha256:3bc1ab68b9a096f368ba06c3a5e1d1d50957a86665fc929c4332d21355e7e8f4"},
{file = "scipy-1.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f7c39f7dbb57cce00c108d06d731f3b0e2a4d3a95c66d96bce697684876ce4d4"},
{file = "scipy-1.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47d1a95bd9d37302afcfe1b84c8011377c4f81e33649c5a5785db9ab827a6ade"},
{file = "scipy-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d7cf7b25c9f23c59a766385f6370dab0659741699ecc7a451f9b94604938ce"},
{file = "scipy-1.9.1-cp39-cp39-win32.whl", hash = "sha256:09412eb7fb60b8f00b328037fd814d25d261066ebc43a1e339cdce4f7502877e"},
{file = "scipy-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:90c805f30c46cf60f1e76e947574f02954d25e3bb1e97aa8a07bc53aa31cf7d1"},
{file = "scipy-1.9.1.tar.gz", hash = "sha256:26d28c468900e6d5fdb37d2812ab46db0ccd22c63baa095057871faa3a498bc9"},
]
setuptools-scm = [
{file = "setuptools_scm-6.4.2-py3-none-any.whl", hash = "sha256:acea13255093849de7ccb11af9e1fb8bde7067783450cee9ef7a93139bddf6d4"},
{file = "setuptools_scm-6.4.2.tar.gz", hash = "sha256:6833ac65c6ed9711a4d5d2266f8024cfa07c533a0e55f4c12f6eff280a5a9e30"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
tomlkit = [
{file = "tomlkit-0.11.4-py3-none-any.whl", hash = "sha256:25d4e2e446c453be6360c67ddfb88838cfc42026322770ba13d1fbd403a93a5c"},
{file = "tomlkit-0.11.4.tar.gz", hash = "sha256:3235a9010fae54323e727c3ac06fb720752fe6635b3426e379daec60fbd44a83"},
]
wrapt = [
files = [
{file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
@@ -559,3 +298,8 @@ wrapt = [
{file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
{file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.11"
content-hash = "959633dfe6335ab3f943a95c5fdd12ff1bb66cd03c5917704f10ae38d3a5009c"

View File

@@ -1,3 +1,11 @@
[tool.black]
line-length = 120
[tool.ruff]
line-length = 120
select = ["E", "F", "W", "I", "C", "RET", "ANN", "UP"]
target-version = "py310"
[tool.poetry]
name = "scripts"
version = "0.1.0"
@@ -8,12 +16,9 @@ authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]
python = ">=3.10,<3.11"
autoflake = "^1.4"
flake8 = "^5.0.4"
matplotlib = "^3.5.3"
numpy = "^1.23.2"
pycodestyle = "^2.9.1"
pyflakes = "^2.5.0"
pylint = "^2.15.0"
scipy = "^1.9.1"
[tool.poetry.dev-dependencies]

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
"""Wrapper around Flake8 to enable multiprocessing on all operating systems.
As of Python 3.8, macOS's default "start method" for multiprocessing is `spawn`. Flake8

View File

@@ -5,10 +5,11 @@ use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{
Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword, KeywordData,
Location, Stmt, StmtKind,
Located, Location, Stmt, StmtKind,
};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use rustpython_parser::token::StringKind;
use crate::ast::types::Range;
use crate::SourceCodeLocator;
@@ -212,6 +213,23 @@ pub fn is_constant_non_singleton(expr: &Expr) -> bool {
is_constant(expr) && !is_singleton(expr)
}
/// Return `true` if an `Expr` is not a reference to a variable (or something
/// that could resolve to a variable, like a function call).
pub fn is_non_variable(expr: &Expr) -> bool {
matches!(
expr.node,
ExprKind::Constant { .. }
| ExprKind::Tuple { .. }
| ExprKind::List { .. }
| ExprKind::Set { .. }
| ExprKind::Dict { .. }
| ExprKind::SetComp { .. }
| ExprKind::ListComp { .. }
| ExprKind::DictComp { .. }
| ExprKind::GeneratorExp { .. }
)
}
/// Return the `Keyword` with the given name, if it's present in the list of
/// `Keyword` arguments.
pub fn find_keyword<'a>(keywords: &'a [Keyword], keyword_name: &str) -> Option<&'a Keyword> {
@@ -335,20 +353,17 @@ pub fn to_absolute(relative: Location, base: Location) -> Location {
/// Return `true` if a `Stmt` has leading content.
pub fn match_leading_content(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
let range = Range {
location: Location::new(stmt.location.row(), 0),
end_location: stmt.location,
};
let range = Range::new(Location::new(stmt.location.row(), 0), stmt.location);
let prefix = locator.slice_source_code_range(&range);
prefix.chars().any(|char| !char.is_whitespace())
}
/// Return `true` if a `Stmt` has trailing content.
pub fn match_trailing_content(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
let range = Range {
location: stmt.end_location.unwrap(),
end_location: Location::new(stmt.end_location.unwrap().row() + 1, 0),
};
let range = Range::new(
stmt.end_location.unwrap(),
Location::new(stmt.end_location.unwrap().row() + 1, 0),
);
let suffix = locator.slice_source_code_range(&range);
for char in suffix.chars() {
if char == '#' {
@@ -384,10 +399,7 @@ pub fn identifier_range(stmt: &Stmt, locator: &SourceCodeLocator) -> Range {
let contents = locator.slice_source_code_range(&Range::from_located(stmt));
for (start, tok, end) in lexer::make_tokenizer_located(&contents, stmt.location).flatten() {
if matches!(tok, Tok::Name { .. }) {
return Range {
location: start,
end_location: end,
};
return Range::new(start, end);
}
}
error!("Failed to find identifier for {:?}", stmt);
@@ -395,6 +407,19 @@ pub fn identifier_range(stmt: &Stmt, locator: &SourceCodeLocator) -> Range {
Range::from_located(stmt)
}
// Return the ranges of `Name` tokens within a specified node.
pub fn find_names<T>(located: &Located<T>, locator: &SourceCodeLocator) -> Vec<Range> {
let contents = locator.slice_source_code_range(&Range::from_located(located));
lexer::make_tokenizer_located(&contents, located.location)
.flatten()
.filter(|(_, tok, _)| matches!(tok, Tok::Name { .. }))
.map(|(start, _, end)| Range {
location: start,
end_location: end,
})
.collect()
}
/// Return the `Range` of `name` in `Excepthandler`.
pub fn excepthandler_name_range(
handler: &Excepthandler,
@@ -406,17 +431,107 @@ pub fn excepthandler_name_range(
match (name, type_) {
(Some(_), Some(type_)) => {
let type_end_location = type_.end_location.unwrap();
let contents = locator.slice_source_code_range(&Range {
location: type_end_location,
end_location: body[0].location,
});
let contents =
locator.slice_source_code_range(&Range::new(type_end_location, body[0].location));
let range = lexer::make_tokenizer_located(&contents, type_end_location)
.flatten()
.tuple_windows()
.find(|(tok, next_tok)| {
matches!(tok.1, Tok::As) && matches!(next_tok.1, Tok::Name { .. })
})
.map(|((..), (location, _, end_location))| Range {
.map(|((..), (location, _, end_location))| Range::new(location, end_location));
range
}
_ => None,
}
}
/// Return the `Range` of `except` in `Excepthandler`.
pub fn except_range(handler: &Excepthandler, locator: &SourceCodeLocator) -> Range {
let ExcepthandlerKind::ExceptHandler { body, type_, .. } = &handler.node;
let end = if let Some(type_) = type_ {
type_.location
} else {
body.first()
.expect("Expected body to be non-empty")
.location
};
let contents = locator.slice_source_code_range(&Range {
location: handler.location,
end_location: end,
});
let range = lexer::make_tokenizer_located(&contents, handler.location)
.flatten()
.find(|(_, kind, _)| matches!(kind, Tok::Except { .. }))
.map(|(location, _, end_location)| Range {
location,
end_location,
})
.expect("Failed to find `except` range");
range
}
/// Find f-strings that don't contain any formatted values in a `JoinedStr`.
pub fn find_useless_f_strings(expr: &Expr, locator: &SourceCodeLocator) -> Vec<(Range, Range)> {
let contents = locator.slice_source_code_range(&Range::from_located(expr));
lexer::make_tokenizer_located(&contents, expr.location)
.flatten()
.filter_map(|(location, tok, end_location)| match tok {
Tok::String {
kind: StringKind::FString | StringKind::RawFString,
..
} => {
let first_char = locator.slice_source_code_range(&Range {
location,
end_location: Location::new(location.row(), location.column() + 1),
});
// f"..." => f_position = 0
// fr"..." => f_position = 0
// rf"..." => f_position = 1
let f_position = usize::from(!(first_char == "f" || first_char == "F"));
Some((
Range {
location: Location::new(location.row(), location.column() + f_position),
end_location: Location::new(
location.row(),
location.column() + f_position + 1,
),
},
Range {
location,
end_location,
},
))
}
_ => None,
})
.collect()
}
/// Return the `Range` of `else` in `For`, `AsyncFor`, and `While` statements.
pub fn else_range(stmt: &Stmt, locator: &SourceCodeLocator) -> Option<Range> {
match &stmt.node {
StmtKind::For { body, orelse, .. }
| StmtKind::AsyncFor { body, orelse, .. }
| StmtKind::While { body, orelse, .. }
if !orelse.is_empty() =>
{
let body_end = body
.last()
.expect("Expected body to be non-empty")
.end_location
.unwrap();
let contents = locator.slice_source_code_range(&Range {
location: body_end,
end_location: orelse
.first()
.expect("Expected orelse to be non-empty")
.location,
});
let range = lexer::make_tokenizer_located(&contents, body_end)
.flatten()
.find(|(_, kind, _)| matches!(kind, Tok::Else))
.map(|(location, _, end_location)| Range {
location,
end_location,
});
@@ -435,10 +550,10 @@ pub fn preceded_by_continuation(stmt: &Stmt, locator: &SourceCodeLocator) -> boo
// make conservative choices.
// TODO(charlie): Come up with a more robust strategy.
if stmt.location.row() > 1 {
let range = Range {
location: Location::new(stmt.location.row() - 1, 0),
end_location: Location::new(stmt.location.row(), 0),
};
let range = Range::new(
Location::new(stmt.location.row() - 1, 0),
Location::new(stmt.location.row(), 0),
);
let line = locator.slice_source_code_range(&range);
if line.trim().ends_with('\\') {
return true;
@@ -466,7 +581,9 @@ mod tests {
use rustpython_ast::Location;
use rustpython_parser::parser;
use crate::ast::helpers::{identifier_range, match_module_member, match_trailing_content};
use crate::ast::helpers::{
else_range, identifier_range, match_module_member, match_trailing_content,
};
use crate::ast::types::Range;
use crate::source_code_locator::SourceCodeLocator;
@@ -660,10 +777,7 @@ y = 2
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(1, 4),
end_location: Location::new(1, 5),
}
Range::new(Location::new(1, 4), Location::new(1, 5),)
);
let contents = r#"
@@ -677,10 +791,7 @@ def \
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(2, 2),
end_location: Location::new(2, 3),
}
Range::new(Location::new(2, 2), Location::new(2, 3),)
);
let contents = "class Class(): pass".trim();
@@ -689,10 +800,7 @@ def \
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(1, 6),
end_location: Location::new(1, 11),
}
Range::new(Location::new(1, 6), Location::new(1, 11),)
);
let contents = "class Class: pass".trim();
@@ -701,10 +809,7 @@ def \
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(1, 6),
end_location: Location::new(1, 11),
}
Range::new(Location::new(1, 6), Location::new(1, 11),)
);
let contents = r#"
@@ -718,10 +823,7 @@ class Class():
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(2, 6),
end_location: Location::new(2, 11),
}
Range::new(Location::new(2, 6), Location::new(2, 11),)
);
let contents = r#"x = y + 1"#.trim();
@@ -730,12 +832,29 @@ class Class():
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(1, 0),
end_location: Location::new(1, 9),
}
Range::new(Location::new(1, 0), Location::new(1, 9),)
);
Ok(())
}
#[test]
fn test_else_range() -> Result<()> {
let contents = r#"
for x in y:
pass
else:
pass
"#
.trim();
let program = parser::parse_program(contents, "<filename>")?;
let stmt = program.first().unwrap();
let locator = SourceCodeLocator::new(contents);
let range = else_range(stmt, &locator).unwrap();
assert_eq!(range.location.row(), 3);
assert_eq!(range.location.column(), 0);
assert_eq!(range.end_location.row(), 3);
assert_eq!(range.end_location.column(), 4);
Ok(())
}
}

View File

@@ -266,10 +266,7 @@ pub type LocatedCmpop<U = ()> = Located<Cmpop, U>;
/// `CPython` doesn't either. This method iterates over the token stream and
/// re-identifies `Cmpop` nodes, annotating them with valid ranges.
pub fn locate_cmpops(contents: &str) -> Vec<LocatedCmpop> {
let mut tok_iter = lexer::make_tokenizer(contents)
.flatten()
.into_iter()
.peekable();
let mut tok_iter = lexer::make_tokenizer(contents).flatten().peekable();
let mut ops: Vec<LocatedCmpop> = vec![];
let mut count: usize = 0;
loop {

View File

@@ -22,12 +22,16 @@ pub struct Range {
}
impl Range {
pub fn from_located<T>(located: &Located<T>) -> Self {
Range {
location: located.location,
end_location: located.end_location.unwrap(),
pub fn new(location: Location, end_location: Location) -> Self {
Self {
location,
end_location,
}
}
pub fn from_located<T>(located: &Located<T>) -> Self {
Range::new(located.location, located.end_location.unwrap())
}
}
#[derive(Debug)]

View File

@@ -38,6 +38,9 @@ pub trait Visitor<'a> {
fn visit_excepthandler(&mut self, excepthandler: &'a Excepthandler) {
walk_excepthandler(self, excepthandler);
}
fn visit_format_spec(&mut self, format_spec: &'a Expr) {
walk_expr(self, format_spec);
}
fn visit_arguments(&mut self, arguments: &'a Arguments) {
walk_arguments(self, arguments);
}
@@ -59,6 +62,15 @@ pub trait Visitor<'a> {
fn visit_pattern(&mut self, pattern: &'a Pattern) {
walk_pattern(self, pattern);
}
fn visit_body(&mut self, body: &'a [Stmt]) {
walk_body(self, body);
}
}
pub fn walk_body<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, body: &'a [Stmt]) {
for stmt in body {
visitor.visit_stmt(stmt);
}
}
pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
@@ -77,9 +89,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
for expr in returns {
visitor.visit_annotation(expr);
}
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
}
StmtKind::AsyncFunctionDef {
args,
@@ -95,9 +105,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
for expr in returns {
visitor.visit_annotation(expr);
}
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
}
StmtKind::ClassDef {
bases,
@@ -115,9 +123,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
for expr in decorator_list {
visitor.visit_expr(expr);
}
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
}
StmtKind::Return { value } => {
if let Some(expr) = value {
@@ -161,12 +167,8 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
} => {
visitor.visit_expr(iter);
visitor.visit_expr(target);
for stmt in body {
visitor.visit_stmt(stmt);
}
for stmt in orelse {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
visitor.visit_body(orelse);
}
StmtKind::AsyncFor {
target,
@@ -177,46 +179,30 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
} => {
visitor.visit_expr(iter);
visitor.visit_expr(target);
for stmt in body {
visitor.visit_stmt(stmt);
}
for stmt in orelse {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
visitor.visit_body(orelse);
}
StmtKind::While { test, body, orelse } => {
visitor.visit_expr(test);
for stmt in body {
visitor.visit_stmt(stmt);
}
for stmt in orelse {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
visitor.visit_body(orelse);
}
StmtKind::If { test, body, orelse } => {
visitor.visit_expr(test);
for stmt in body {
visitor.visit_stmt(stmt);
}
for stmt in orelse {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
visitor.visit_body(orelse);
}
StmtKind::With { items, body, .. } => {
for withitem in items {
visitor.visit_withitem(withitem);
}
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
}
StmtKind::AsyncWith { items, body, .. } => {
for withitem in items {
visitor.visit_withitem(withitem);
}
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
}
StmtKind::Match { subject, cases } => {
// TODO(charlie): Handle `cases`.
@@ -239,18 +225,12 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
orelse,
finalbody,
} => {
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
for excepthandler in handlers {
visitor.visit_excepthandler(excepthandler);
}
for stmt in orelse {
visitor.visit_stmt(stmt);
}
for stmt in finalbody {
visitor.visit_stmt(stmt);
}
visitor.visit_body(orelse);
visitor.visit_body(finalbody);
}
StmtKind::Assert { test, msg } => {
visitor.visit_expr(test);
@@ -387,7 +367,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
} => {
visitor.visit_expr(value);
if let Some(expr) = format_spec {
visitor.visit_expr(expr);
visitor.visit_format_spec(expr);
}
}
ExprKind::JoinedStr { values } => {
@@ -466,9 +446,7 @@ pub fn walk_excepthandler<'a, V: Visitor<'a> + ?Sized>(
if let Some(expr) = type_ {
visitor.visit_expr(expr);
}
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.visit_body(body);
}
}
}
@@ -519,9 +497,7 @@ pub fn walk_match_case<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, match_case:
if let Some(expr) = &match_case.guard {
visitor.visit_expr(expr);
}
for stmt in &match_case.body {
visitor.visit_stmt(stmt);
}
visitor.visit_body(&match_case.body);
}
pub fn walk_pattern<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, pattern: &'a Pattern) {

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