Compare commits

...

48 Commits

Author SHA1 Message Date
Charlie Marsh
7b59cd2d32 Bump version to 0.0.211 2023-01-04 23:13:04 -05:00
Charlie Marsh
b8ed4d402a Implement SIM110 and SIM111 (conversion to any and all) (#1653) 2023-01-04 23:08:12 -05:00
messense
46dcf3c4c0 Implement flake8-simplify SIM107 (#1650) 2023-01-04 23:02:25 -05:00
Charlie Marsh
ef7777703b Run generation steps 2023-01-04 22:05:04 -05:00
messense
6a1edeb694 Cancel outdated in-progress workflow automatically (#1652) 2023-01-04 22:03:26 -05:00
Charlie Marsh
fb8024a6ac Implement nested with detection (SIM117) (#1651) 2023-01-04 21:22:25 -05:00
Edgar R. M
2f71bdfbfc Implement flake8-bandit rule S108 (#1644) 2023-01-04 21:11:13 -05:00
Charlie Marsh
30d6688c26 Implement nested-if detection (#1649) 2023-01-04 20:55:01 -05:00
Charlie Marsh
9041423b92 Move external licenses to separate directory (#1648) 2023-01-04 20:07:51 -05:00
Charlie Marsh
c91c435dc9 Fix URL in pyproject.toml 2023-01-04 19:23:40 -05:00
Charlie Marsh
5cb162bd7b Add missing McCabe comment 2023-01-04 19:16:36 -05:00
Charlie Marsh
7339d7eccf Implement builtin import removal (#1645) 2023-01-04 19:10:16 -05:00
Charlie Marsh
2ff816f108 DRY up unused import removal code (#1643) 2023-01-04 19:03:52 -05:00
Charlie Marsh
980d10b952 Add Ruff badge to README (#1642) 2023-01-04 17:27:01 -05:00
Charlie Marsh
6c5db1008d Add badge JSON (#1641) 2023-01-04 17:24:27 -05:00
Martin Fischer
ca48492137 Add task-tags & ignore-overlong-task-comments settings (#1550) 2023-01-04 17:10:21 -05:00
Charlie Marsh
7c23701b62 Note a few more incompatibilities (#1639) 2023-01-04 16:05:15 -05:00
Charlie Marsh
8da2c4815a Tweak Yoda condition message (#1638) 2023-01-04 15:58:01 -05:00
Charlie Marsh
34fec8cbd0 Rename flake8-bandit rules from plugins to checks (#1637) 2023-01-04 15:49:05 -05:00
Edgar R. M
1817f8752b Implement flake8-bandit rule S103 (#1636) 2023-01-04 15:47:38 -05:00
Colin Delahunty
8b8e6e44ea Adding my company to the "used in" category of the Readme. (#1631) 2023-01-04 12:31:18 -05:00
Harutaka Kawamura
12166584c4 Minor fixes for SIM105 (#1633) 2023-01-04 11:01:06 -05:00
Charlie Marsh
9ede902328 Run generate-all 2023-01-04 10:46:24 -05:00
messense
0df28bdd4e Implement flake8-simplify SIM105 rule (#1621) 2023-01-04 08:10:59 -05:00
Charlie Marsh
8b07f9517a Implement SIM220 and SIM221 (#1630) 2023-01-04 08:07:00 -05:00
Harutaka Kawamura
0a0e1926f2 Check SIM118 in comprehension (#1627) 2023-01-04 07:05:13 -05:00
Charlie Marsh
4473c7e905 Update stale references in CONTRIBUTING.md 2023-01-03 23:44:10 -05:00
Charlie Marsh
20930b3675 Add some more users to the README (#1623) 2023-01-03 23:06:08 -05:00
Colin Delahunty
fb1a638a96 Implement yield-to-yield from conversion (#1544) 2023-01-03 22:56:52 -05:00
Edgar R. M
16964409a8 Implement missing fixes for PT006 (#1622) 2023-01-03 22:52:51 -05:00
Charlie Marsh
aacfc9ee0b Bump version to 0.0.210 2023-01-03 21:46:08 -05:00
Charlie Marsh
bd14f92898 Avoid byte-string conversions (#1618) 2023-01-03 21:45:14 -05:00
Charlie Marsh
cc116b0192 Treat convention as setting ignore, rather than select (#1611) 2023-01-03 21:27:53 -05:00
Charlie Marsh
0d27c0be27 Treat .pyi files as __future__ annotations-enabled (#1616) 2023-01-03 21:27:26 -05:00
Edgar R. M
60359c6adf Fix leftover whitespace when removing pass for PIE790 (#1612) 2023-01-03 20:44:59 -05:00
Charlie Marsh
77692e4b5f Associate inline comments with parenthesized ImportFrom statements (#1609) 2023-01-03 20:02:12 -05:00
Charlie Marsh
731f3a74a9 Fix *arg and **kwarg handling for Google docstrings (#1608) 2023-01-03 18:17:42 -05:00
Matt Oberle
03275c9c98 Add isort.order-by-type boolean setting (#1607) 2023-01-03 18:07:44 -05:00
Harutaka Kawamura
8d99e317b8 Implement autofix for PT022 (#1604) 2023-01-03 13:36:28 -05:00
Maksudul Haque
d4d67e3014 Do not Change Quotation Style for PT006 Autofix (#1600) 2023-01-03 10:16:49 -05:00
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
160 changed files with 5316 additions and 1845 deletions

View File

@@ -6,6 +6,10 @@ on:
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10

View File

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

View File

@@ -56,31 +56,31 @@ prior to merging.
There are four phases to adding a new lint rule:
1. Define the rule in `src/checks.rs`.
1. Define the rule in `src/registry.rs`.
2. Define the _logic_ for triggering the rule in `src/checkers/ast.rs` (for AST-based checks),
`src/checkers/tokens.rs` (for token-based checks), or `src/checkers/lines.rs` (for text-based checks).
3. Add a test fixture.
4. Update the generated files (documentation and generated code).
To define the rule, open up `src/checks.rs`. You'll need to define both a `CheckCode` and
To define the rule, open up `src/registry.rs`. You'll need to define both a `CheckCode` and
`CheckKind`. As an example, you can grep for `E402` and `ModuleImportNotAtTopOfFile`, and follow the
pattern implemented therein.
To trigger the rule, you'll likely want to augment the logic in `src/check_ast.rs`, which defines
To trigger the rule, you'll likely want to augment the logic in `src/checkers/ast.rs`, which defines
the Python AST visitor, responsible for iterating over the abstract syntax tree and collecting
lint-rule violations as it goes. If you need to inspect the AST, you can run
`cargo +nightly dev print-ast` with a Python file. Grep for the `Check::new` invocations to
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.
To add a test fixture, create a file under `resources/test/fixtures/[plugin-name]`, 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 `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`.
locally with (e.g.) `cargo run resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
`test_case` macro in the relevant `src/[test-suite-name]/mod.rs` file. Then, run `cargo test`. Your
`test_case` macro in the relevant `src/[plugin-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.
@@ -88,21 +88,23 @@ Finally, regenerate the documentation and generated code with `cargo +nightly de
### Example: Adding a new configuration option
Ruff's user-facing settings live in two places: first, the command-line options defined with
[clap](https://docs.rs/clap/latest/clap/) via the `Cli` struct in `src/main.rs`; and second, the
`Config` struct defined `src/pyproject.rs`, which is responsible for extracting user-defined
settings from a `pyproject.toml` file.
Ruff's user-facing settings live in a few different places.
Ultimately, these two sources of configuration are merged into the `Settings` struct defined
in `src/settings.rs`, which is then threaded through the codebase.
First, the command-line options are defined via the `Cli` struct in `src/cli.rs`.
To add a new configuration option, you'll likely want to _both_ add a CLI option to `src/main.rs`
_and_ a `pyproject.toml` parameter to `src/pyproject.rs`. If you want to pattern-match against an
existing example, grep for `dummy_variable_rgx`, which defines a regular expression to match against
acceptable unused variables (e.g., `_`).
Second, the `pyproject.toml` options are defined in `src/settings/options.rs` (via the `Options`
struct), `src/settings/configuration.rs` (via the `Configuration` struct), and `src/settings/mod.rs`
(via the `Settings` struct). These represent, respectively: the schema used to parse the
`pyproject.toml` file; an internal, intermediate representation; and the final, internal
representation used to power Ruff.
If the new plugin's configuration should be cached between runs, you'll need to add it to the
`Hash` implementation for `Settings` in `src/settings/mod.rs`.
To add a new configuration option, you'll likely want to modify these latter few files (along with
`cli.rs`, if appropriate). If you want to pattern-match against an existing example, grep for
`dummy_variable_rgx`, which defines a regular expression to match against acceptable unused
variables (e.g., `_`).
Note that plugin-specific configuration options are defined in their own modules (e.g.,
`src/flake8_unused_arguments/settings.rs`).
You may also want to add the new configuration option to the `flake8-to-ruff` tool, which is
responsible for converting `flake8` configuration files to Ruff's TOML format. This logic

8
Cargo.lock generated
View File

@@ -744,7 +744,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.208-dev.0"
version = "0.0.211-dev.0"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1873,7 +1873,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.208"
version = "0.0.211"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1941,7 +1941,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.208"
version = "0.0.211"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1962,7 +1962,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.208"
version = "0.0.211"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.208"
version = "0.0.211"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -51,7 +51,7 @@ path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix
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.208", path = "ruff_macros" }
ruff_macros = { version = "0.0.211", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }

647
LICENSE
View File

@@ -19,650 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The externally maintained libraries from which parts of the Software is derived
are:
- Pyflakes, licensed as follows:
"""
Copyright 2005-2011 Divmod, Inc.
Copyright 2013-2014 Florent Xicluna
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
- autoflake, licensed as follows:
"""
Copyright (C) 2012-2018 Steven Myint
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- Flake8, licensed as follows:
"""
== Flake8 License (MIT) ==
Copyright (C) 2011-2013 Tarek Ziade <tarek@ziade.org>
Copyright (C) 2012-2016 Ian Cordasco <graffatcolmingov@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-2020, licensed as follows:
"""
Copyright (c) 2019 Anthony Sottile
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
- flake8-annotations, licensed as follows:
"""
MIT License
Copyright (c) 2019 - Present S. Co1
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-bandit, licensed as follows:
"""
Copyright (c) 2017 Tyler Wince
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
- flake8-blind-except, licensed as follows:
"""
The MIT License (MIT)
Copyright (c) 2014 Elijah Andrews
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
- flake8-bugbear, licensed as follows:
"""
The MIT License (MIT)
Copyright (c) 2016 Łukasz Langa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-comprehensions, licensed as follows:
"""
MIT License
Copyright (c) 2017 Adam Johnson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-debugger, licensed as follows:
"""
MIT License
Copyright (c) 2016 Joseph Kahn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-eradicate, licensed as follows:
"""
MIT License
Copyright (c) 2018 Nikita Sobolev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-tidy-imports, licensed as follows:
"""
MIT License
Copyright (c) 2017 Adam Johnson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-print, licensed as follows:
"""
MIT License
Copyright (c) 2016 Joseph Kahn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-quotes, licensed as follows:
"""
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
- flake8-return, licensed as follows:
"""
MIT License
Copyright (c) 2019 Afonasev Evgeniy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-implicit-str-concat, licensed as follows:
"""
The MIT License (MIT)
Copyright (c) 2019 Dylan Turner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
- flake8-import-conventions, licensed as follows:
"""
MIT License
Copyright (c) 2021 João Palmeiro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-unused-arguments, licensed as follows:
"""
MIT License
Copyright (c) 2019 Nathan Hoad
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-simplify, licensed as follows:
"""
MIT License
Copyright (c) 2020 Martin Thoma
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- isort, licensed as follows:
"""
The MIT License (MIT)
Copyright (c) 2013 Timothy Edmund Crosley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
- pep8-naming, licensed as follows:
"""
Copyright © 2013 Florent Xicluna <florent.xicluna@gmail.com>
Licensed under the terms of the Expat License
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- pycodestyle, licensed as follows:
"""
Copyright © 2006-2009 Johann C. Rocholl <johann@rocholl.net>
Copyright © 2009-2014 Florent Xicluna <florent.xicluna@gmail.com>
Copyright © 2014-2020 Ian Lee <IanLee1521@gmail.com>
Licensed under the terms of the Expat License
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- pydocstyle, licensed as follows:
"""
Copyright (c) 2012 GreenSteam, <http://greensteam.dk/>
Copyright (c) 2014-2020 Amir Rachum, <http://amir.rachum.com/>
Copyright (c) 2020 Sambhav Kothari, <https://github.com/samj1912>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- pygrep-hooks, licensed as follows:
"""
Copyright (c) 2018 Anthony Sottile
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
- pyupgrade, licensed as follows:
"""
Copyright (c) 2017 Anthony Sottile
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
- RustPython, licensed as follows:
"""
MIT License
Copyright (c) 2020 RustPython Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

210
README.md
View File

@@ -1,5 +1,6 @@
# Ruff
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v0.json)](https://github.com/charliermarsh/ruff)
[![image](https://img.shields.io/pypi/v/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/l/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/pyversions/ruff.svg)](https://pypi.python.org/pypi/ruff)
@@ -53,8 +54,11 @@ Ruff is extremely actively developed and used in major open-source projects like
- [Hatch](https://github.com/pypa/hatch)
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [Synapse](https://github.com/matrix-org/synapse)
- [Ibis](https://github.com/ibis-project/ibis)
- [Saleor](https://github.com/saleor/saleor)
- [Polars](https://github.com/pola-rs/polars)
- [Ibis](https://github.com/ibis-project/ibis)
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
- [`pyca/cryptography`](https://github.com/pyca/cryptography)
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
@@ -176,7 +180,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.208'
rev: 'v0.0.211'
hooks:
- id: ruff
# Respect `exclude` and `extend-exclude` settings.
@@ -277,7 +281,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
@@ -404,7 +408,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
@@ -449,7 +453,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:
@@ -693,6 +697,8 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| 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 | 🛠 |
| UP028 | RewriteYieldFrom | Replace `yield` over `for` loop with `yield from` | 🛠 |
| UP029 | UnnecessaryBuiltinImport | Unnecessary builtin import: `...` | 🛠 |
### pep8-naming (N)
@@ -759,10 +765,12 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/4.1.1/) on
| ---- | ---- | ------- | --- |
| S101 | AssertUsed | Use of `assert` detected | |
| S102 | ExecUsed | Use of `exec` detected | |
| S103 | BadFilePermissions | `os.chmod` setting a permissive mask `0o777` on file or directory | |
| S104 | HardcodedBindAllInterfaces | Possible binding to all interfaces | |
| S105 | HardcodedPasswordString | Possible hardcoded password: `"..."` | |
| S106 | HardcodedPasswordFuncArg | Possible hardcoded password: `"..."` | |
| S107 | HardcodedPasswordDefault | Possible hardcoded password: `"..."` | |
| S108 | HardcodedTempFile | Probable insecure usage of temp file/directory: `"..."` | |
### flake8-blind-except (BLE)
@@ -919,7 +927,7 @@ For more, see [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style
| 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` | |
| 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 | |
@@ -957,10 +965,18 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/0.19.3/
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| SIM102 | NestedIfStatements | Use a single `if` statement instead of nested `if` statements | |
| SIM105 | UseContextlibSuppress | Use `contextlib.suppress(...)` instead of try-except-pass | |
| SIM107 | ReturnInTryExceptFinally | Don't use `return` in `try`/`except` and `finally` | |
| SIM110 | ConvertLoopToAny | Use `return any(x for x in y)` instead of `for` loop | 🛠 |
| SIM111 | ConvertLoopToAll | Use `return all(x for x in y)` instead of `for` loop | 🛠 |
| SIM117 | MultipleWithStatements | Use a single `with` statement with multiple contexts instead of nested `with` statements | |
| SIM118 | KeyInDict | Use `key in dict` instead of `key in dict.keys()` | 🛠 |
| SIM220 | AAndNotA | Use `False` instead of `... and not ...` | 🛠 |
| SIM221 | AOrNotA | Use `True` instead of `... or not ...` | 🛠 |
| 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)` | |
| SIM300 | YodaConditions | Yoda conditions are discouraged, use `left == right` instead | 🛠 |
### flake8-tidy-imports (TID)
@@ -1328,11 +1344,11 @@ Under those conditions, Ruff implements every rule in Flake8.
Ruff also re-implements some of the most popular Flake8 plugins and related code quality tools
natively, including:
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
- [`autoflake`](https://pypi.org/project/autoflake/) ([#1647](https://github.com/charliermarsh/ruff/issues/1647))
- [`eradicate`](https://pypi.org/project/eradicate/)
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) ([#1646](https://github.com/charliermarsh/ruff/issues/1646))
- [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/)
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
@@ -1345,19 +1361,19 @@ 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-pie`](https://pypi.org/project/flake8-pie/) ([#1543](https://github.com/charliermarsh/ruff/issues/1543))
- [`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/) (4/37)
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) ([#998](https://github.com/charliermarsh/ruff/issues/998))
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/)
- [`isort`](https://pypi.org/project/isort/)
- [`mccabe`](https://pypi.org/project/mccabe/)
- [`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/) (21/33)
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) ([#980](https://github.com/charliermarsh/ruff/issues/980))
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) ([#827](https://github.com/charliermarsh/ruff/issues/827))
- [`yesqa`](https://github.com/asottile/yesqa)
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
@@ -1372,6 +1388,13 @@ Beyond the rule set, Ruff suffers from the following limitations vis-à-vis Flak
2. Flake8 has a plugin architecture and supports writing custom lint rules. (Instead, popular Flake8
plugins are re-implemented in Rust as part of Ruff itself.)
There are a few other minor incompatibilities between Ruff and the originating Flake8 plugins:
- Ruff doesn't implement the "opinionated" lint rules from `flake8-bugbear`.
- Depending on your project structure, Ruff and `isort` can differ in their detection of first-party
code. (This is often solved by modifying the `src` property, e.g., to `src = ["src"]`, if your
code is nested in a `src` directory.)
### How does Ruff compare to Pylint?
At time of writing, Pylint implements 409 total rules, while Ruff implements 224, of which
@@ -1390,7 +1413,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) ([#1646](https://github.com/charliermarsh/ruff/issues/1646))
- [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/)
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
@@ -1403,23 +1426,23 @@ 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-pie`](https://pypi.org/project/flake8-pie/) ([#1543](https://github.com/charliermarsh/ruff/issues/1543))
- [`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/) (4/37)
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) ([#998](https://github.com/charliermarsh/ruff/issues/998))
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/)
- [`mccabe`](https://pypi.org/project/mccabe/)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
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/) (21/33).
[`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) ([#980](https://github.com/charliermarsh/ruff/issues/980)), and a subset of the rules
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) ([#827](https://github.com/charliermarsh/ruff/issues/827)).
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?
@@ -1493,53 +1516,21 @@ For example, if you're coming from `flake8-docstrings`, and your originating con
`--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:
Alongside `convention`, you'll want to explicitly enable the `D` error code class, like so:
```toml
[tool.ruff]
# 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",
"D205",
"D206",
"D207",
"D208",
"D209",
"D210",
"D211",
"D214",
"D215",
"D300",
"D301",
"D400",
"D403",
"D404",
"D405",
"D406",
"D407",
"D408",
"D409",
"D410",
"D411",
"D412",
"D414",
"D418",
"D419",
"D",
]
[tool.ruff.pydocstyle]
convention = "google"
```
Setting a `convention` force-disables any rules that are incompatible with that convention, no
matter how they're provided, which avoids accidental incompatibilities and simplifies configuration.
### How can I tell what settings Ruff is using to check my code?
Run `ruff /path/to/code.py --show-settings` to view the resolved settings for a given file.
@@ -2241,6 +2232,27 @@ target-version = "py37"
---
#### [`task-tags`](#task-tags)
A list of task tags to recognize (e.g., "TODO", "FIXME", "XXX").
Comments starting with these tags will be ignored by commented-out code
detection (`ERA`), and skipped by line-length checks (`E501`) if
`ignore-overlong-task-comments` is set to `true`.
**Default value**: `["TODO", "FIXME", "XXX"]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff]
task-tags = ["HACK"]
```
---
#### [`unfixable`](#unfixable)
A list of check code prefixes to consider un-autofix-able.
@@ -2355,6 +2367,43 @@ suppress-none-returning = true
---
### `flake8-bandit`
#### [`hardcoded-tmp-directory`](#hardcoded-tmp-directory)
A list of directories to consider temporary.
**Default value**: `["/tmp", "/var/tmp", "/dev/shm"]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.flake8-bandit]
hardcoded-tmp-directory = ["/foo/bar"]
```
---
#### [`hardcoded-tmp-directory-extend`](#hardcoded-tmp-directory-extend)
A list of directories to consider temporary, in addition to those
specified by `hardcoded-tmp-directory`.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.flake8-bandit]
extend-hardcoded-tmp-directory = ["/foo/bar"]
```
---
### `flake8-bugbear`
#### [`extend-immutable-calls`](#extend-immutable-calls)
@@ -2851,6 +2900,24 @@ known-third-party = ["src"]
---
#### [`order-by-type`](#order-by-type)
Order imports by type, which is determined by case, in addition to
alphabetically.
**Default value**: `true`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.isort]
order-by-type = true
```
---
#### [`single-line-exclusions`](#single-line-exclusions)
One or more modules to exclude from the single line rule.
@@ -2969,6 +3036,27 @@ staticmethod-decorators = ["staticmethod", "stcmthd"]
---
### `pycodestyle`
#### [`ignore-overlong-task-comments`](#ignore-overlong-task-comments)
Whether or not line-length checks (`E501`) should be triggered for
comments starting with `task-tags` (by default: ["TODO", "FIXME",
and "XXX"]).
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.pycodestyle]
ignore-overlong-task-comments = true
```
---
### `pydocstyle`
#### [`convention`](#convention)

8
assets/badge/v0.json Normal file
View File

@@ -0,0 +1,8 @@
{
"label": "",
"message": "Ruff",
"logoSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"128\" height=\"128\"><path d=\"M115.36 61.84 70.22 50.49 114.45 2.4a1.222 1.222 0 0 0-1.54-1.87L12.3 61.98c-.41.25-.64.72-.57 1.2.06.48.4.87.87 1.01l45.07 13.25-44.29 48.16c-.42.46-.44 1.15-.04 1.61.24.29.58.44.94.44.22 0 .45-.06.65-.19l100.78-63.41c.42-.26.64-.75.56-1.22-.08-.49-.43-.88-.91-.99z\" style=\"fill:#fcc21b\"/></svg>",
"logoWidth": 10,
"labelColor": "grey",
"color": "#E15759"
}

View File

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

View File

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

View File

@@ -74,7 +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(|| plugin::resolve_select(&plugins));
let mut ignore = flake8
.get("ignore")
.and_then(|value| {
@@ -291,13 +291,6 @@ pub fn convert(
}
}
// 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));
@@ -397,8 +390,10 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
task_tags: None,
update_check: None,
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
@@ -409,6 +404,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pyupgrade: None,
});
@@ -457,8 +453,10 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
task_tags: None,
update_check: None,
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
@@ -469,6 +467,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pyupgrade: None,
});
@@ -517,8 +516,10 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
task_tags: None,
update_check: None,
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
@@ -529,6 +530,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pyupgrade: None,
});
@@ -577,8 +579,10 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
task_tags: None,
update_check: None,
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
@@ -589,6 +593,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pyupgrade: None,
});
@@ -637,8 +642,10 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
task_tags: None,
update_check: None,
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
@@ -654,6 +661,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pyupgrade: None,
});
@@ -697,6 +705,7 @@ mod tests {
required_version: None,
respect_gitignore: None,
select: Some(vec![
CheckCodePrefix::D,
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
@@ -705,8 +714,10 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
task_tags: None,
update_check: None,
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
@@ -717,6 +728,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pycodestyle: None,
pydocstyle: Some(pydocstyle::settings::Options {
convention: Some(Convention::Numpy),
}),
@@ -768,8 +780,10 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
task_tags: None,
update_check: None,
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_errmsg: None,
flake8_pytest_style: None,
@@ -785,6 +799,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pyupgrade: None,
});

View File

@@ -97,7 +97,7 @@ impl fmt::Debug for Plugin {
}
impl Plugin {
pub fn default(&self) -> CheckCodePrefix {
pub fn prefix(&self) -> CheckCodePrefix {
match self {
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
Plugin::Flake8Bandit => CheckCodePrefix::S,
@@ -125,48 +125,6 @@ impl Plugin {
Plugin::Pyupgrade => CheckCodePrefix::UP,
}
}
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
match self {
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
Plugin::Flake8Bandit => vec![CheckCodePrefix::S],
Plugin::Flake8BlindExcept => vec![CheckCodePrefix::BLE],
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C4],
Plugin::Flake8Datetimez => vec![CheckCodePrefix::DTZ],
Plugin::Flake8Debugger => vec![CheckCodePrefix::T1],
Plugin::Flake8Docstrings => {
// Use the user-provided docstring.
for key in ["docstring-convention", "docstring_convention"] {
if let Some(Some(value)) = flake8.get(key) {
match DocstringConvention::from_str(value) {
Ok(convention) => return convention.select(),
Err(e) => {
eprintln!("Unexpected '{key}' value: {value} ({e}");
return vec![];
}
}
}
}
// 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],
Plugin::Flake8TidyImports => vec![CheckCodePrefix::TID],
Plugin::McCabe => vec![CheckCodePrefix::C9],
Plugin::PandasVet => vec![CheckCodePrefix::PD],
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
Plugin::Pyupgrade => vec![CheckCodePrefix::UP],
}
}
}
pub enum DocstringConvention {
@@ -190,23 +148,6 @@ impl FromStr for DocstringConvention {
}
}
impl DocstringConvention {
fn select(&self) -> Vec<CheckCodePrefix> {
match self {
DocstringConvention::All => vec![CheckCodePrefix::D],
DocstringConvention::Pep257 => vec![
// Covered by the `convention` setting.
],
DocstringConvention::Numpy => vec![
// Covered by the `convention` setting.
],
DocstringConvention::Google => vec![
// Covered by the `convention` setting.
],
}
}
}
/// Infer the enabled plugins based on user-provided options.
///
/// For example, if the user specified a `mypy-init-return` setting, we should
@@ -356,7 +297,7 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
if prefix
.codes()
.iter()
.any(|code| plugin.default().codes().contains(code))
.any(|code| plugin.prefix().codes().contains(code))
{
return true;
}
@@ -367,18 +308,9 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
}
/// Resolve the set of enabled `CheckCodePrefix` values for the given plugins.
pub fn resolve_select(
flake8: &HashMap<String, Option<String>>,
plugins: &[Plugin],
) -> BTreeSet<CheckCodePrefix> {
// Include default Pyflakes and pycodestyle checks.
pub fn resolve_select(plugins: &[Plugin]) -> BTreeSet<CheckCodePrefix> {
let mut select = BTreeSet::from([CheckCodePrefix::F, CheckCodePrefix::E, CheckCodePrefix::W]);
// Add prefix codes for every plugin.
for plugin in plugins {
select.extend(plugin.select(flake8));
}
select.extend(plugins.iter().map(Plugin::prefix));
select
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Joseph Kahn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Martin Thoma
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,19 @@
Copyright (C) 2012-2018 Steven Myint
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,22 @@
== Flake8 License (MIT) ==
Copyright (C) 2011-2013 Tarek Ziade <tarek@ziade.org>
Copyright (C) 2012-2016 Ian Cordasco <graffatcolmingov@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Łukasz Langa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Timothy Edmund Crosley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,23 @@
Copyright © 2013 Florent Xicluna <florent.xicluna@gmail.com>
Licensed under the terms of the Expat License
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,25 @@
Copyright © 2006-2009 Johann C. Rocholl <johann@rocholl.net>
Copyright © 2009-2014 Florent Xicluna <florent.xicluna@gmail.com>
Copyright © 2014-2020 Ian Lee <IanLee1521@gmail.com>
Licensed under the terms of the Expat License
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,23 @@
Copyright (c) 2012 GreenSteam, <http://greensteam.dk/>
Copyright (c) 2014-2020 Amir Rachum, <http://amir.rachum.com/>
Copyright (c) 2020 Sambhav Kothari, <https://github.com/samj1912>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
Copyright 2005-2011 Divmod, Inc.
Copyright 2013-2014 Florent Xicluna
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 RustPython Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Adam Johnson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Adam Johnson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Afonasev Evgeniy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,19 @@
Copyright (c) 2019 Anthony Sottile
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,19 @@
Copyright (c) 2017 Anthony Sottile
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Elijah Andrews
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 Dylan Turner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Joseph Kahn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 João Palmeiro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Nathan Hoad
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,19 @@
Copyright (c) 2018 Anthony Sottile
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 - Present S. Co1
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,19 @@
Copyright (c) 2017 Tyler Wince
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Nikita Sobolev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,17 @@
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.208"
version = "0.0.211"
description = "An extremely fast Python linter, written in Rust."
authors = [
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
@@ -31,8 +31,14 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
]
urls = { repository = "https://github.com/charliermarsh/ruff-lsp" }
urls = { repository = "https://github.com/charliermarsh/ruff" }
[tool.maturin]
bindings = "bin"
strip = true
[tool.setuptools]
license-files = [
"LICENSE",
"licenses/*",
]

View File

@@ -0,0 +1,22 @@
import os
import stat
keyfile = "foo"
os.chmod("/etc/passwd", 0o227) # Error
os.chmod("/etc/passwd", 0o7) # Error
os.chmod("/etc/passwd", 0o664) # OK
os.chmod("/etc/passwd", 0o777) # Error
os.chmod("/etc/passwd", 0o770) # Error
os.chmod("/etc/passwd", 0o776) # Error
os.chmod("/etc/passwd", 0o760) # OK
os.chmod("~/.bashrc", 511) # Error
os.chmod("/etc/hosts", 0o777) # Error
os.chmod("/tmp/oh_hai", 0x1FF) # Error
os.chmod("/etc/passwd", stat.S_IRWXU) # OK
os.chmod(keyfile, 0o777) # Error
os.chmod(keyfile, 0o7 | 0o70 | 0o700) # Error
os.chmod(keyfile, stat.S_IRWXO | stat.S_IRWXG | stat.S_IRWXU) # Error
os.chmod("~/hidden_exec", stat.S_IXGRP) # Error
os.chmod("~/hidden_exec", stat.S_IXOTH) # OK
os.chmod("/etc/passwd", stat.S_IWOTH) # Error

View File

@@ -0,0 +1,16 @@
# ok
with open("/abc/tmp", "w") as f:
f.write("def")
with open("/tmp/abc", "w") as f:
f.write("def")
with open("/var/tmp/123", "w") as f:
f.write("def")
with open("/dev/shm/unit/test", "w") as f:
f.write("def")
# not ok by config
with open("/foo/bar", "w") as f:
f.write("def")

View File

@@ -9,6 +9,11 @@ if foo:
pass # PIE790
def multi_statement() -> None:
"""This is a function."""
pass; print("hello")
if foo:
pass
else:

View File

@@ -11,6 +11,16 @@ 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_csv_bad_quotes(param1, param2):
...
@pytest.mark.parametrize(("param1", "param2"), [(1, 2), (3, 4)])
def test_tuple(param1, param2):
...
@@ -29,3 +39,13 @@ def test_list(param1, param2):
@pytest.mark.parametrize(["param1"], [1, 2, 3])
def test_list_one_elem(param1, param2):
...
@pytest.mark.parametrize([some_expr, another_expr], [1, 2, 3])
def test_list_expressions(param1, param2):
...
@pytest.mark.parametrize([some_expr, "param2"], [1, 2, 3])
def test_list_mixed_expr_literal(param1, param2):
...

View File

@@ -0,0 +1,25 @@
if a: # SIM102
if b:
c
if a:
pass
elif b: # SIM102
if c:
d
if a:
if b:
c
else:
d
if __name__ == "__main__":
if foo():
...
if a:
d
if b:
c

View File

@@ -0,0 +1,43 @@
def foo():
pass
try:
foo()
except ValueError: # SIM105
pass
try:
foo()
except (ValueError, OSError): # SIM105
pass
try:
foo()
except: # SIM105
pass
try:
foo()
except (a.Error, b.Error): # SIM105
pass
try:
foo()
except ValueError:
print('foo')
except OSError:
pass
try:
foo()
except ValueError:
pass
else:
print('bar')
try:
foo()
except ValueError:
pass
finally:
print('bar')

View File

@@ -0,0 +1,22 @@
# Bad: This example returns 3!
def foo():
try:
1 / 0
return "1"
except:
return "2"
finally:
return "3"
# Good
def foo():
return_value = None
try:
1 / 0
return_value = "1"
except:
return_value = "2"
finally:
return_value = "3" # you might also want to check if "except" happened
return return_value

View File

@@ -0,0 +1,47 @@
def f():
for x in iterable: # SIM110
if check(x):
return True
return False
def f():
for x in iterable:
if check(x):
return True
return True
def f():
for el in [1, 2, 3]:
if is_true(el):
return True
raise Exception
def f():
for x in iterable: # SIM111
if check(x):
return False
return True
def f():
for x in iterable: # SIM111
if not x.is_empty():
return False
return True
def f():
for x in iterable:
if check(x):
return False
return False
def f():
for x in iterable:
if check(x):
return "foo"
return "bar"

View File

@@ -0,0 +1,33 @@
def f():
for x in iterable: # SIM110
if check(x):
return True
return False
def f():
for el in [1, 2, 3]:
if is_true(el):
return True
raise Exception
def f():
for x in iterable: # SIM111
if check(x):
return False
return True
def f():
for x in iterable: # SIM 111
if not x.is_empty():
return False
return True
def f():
for x in iterable:
if check(x):
return "foo"
return "bar"

View File

@@ -0,0 +1,13 @@
with A() as a: # SIM117
with B() as b:
print("hello")
with A() as a:
a()
with B() as b:
print("hello")
with A() as a:
with B() as b:
print("hello")
a()

View File

@@ -1,12 +1,22 @@
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]
[k for k in obj.keys()] # SIM118
{k for k in obj.keys()} # SIM118
{k: k for k in obj.keys()} # SIM118
(k for k in obj.keys()) # SIM118

View File

@@ -0,0 +1,17 @@
if a and not a:
pass
if (a and not a) and b:
pass
if (a and not a) or b:
pass
if a:
pass
if not a:
pass
if a and not b:
pass

View File

@@ -0,0 +1,17 @@
if a or not a:
pass
if (a or not a) or b:
pass
if (a or not a) and b:
pass
if a:
pass
if not a:
pass
if a or not b:
pass

View File

@@ -1,5 +1,6 @@
# Errors
"yoda" == compare # SIM300
'yoda' == compare # SIM300
42 == age # SIM300
# OK

View File

@@ -0,0 +1,11 @@
from a.prometheus.metrics import ( # type:ignore[attr-defined]
TERMINAL_CURRENTLY_RUNNING_TOTAL,
)
from b.prometheus.metrics import (
TERMINAL_CURRENTLY_RUNNING_TOTAL, # type:ignore[attr-defined]
)
from c.prometheus.metrics import TERMINAL_CURRENTLY_RUNNING_TOTAL # type:ignore[attr-defined]
from d.prometheus.metrics import TERMINAL_CURRENTLY_RUNNING_TOTAL, OTHER_RUNNING_TOTAL # type:ignore[attr-defined]

View File

@@ -0,0 +1,6 @@
# TODO: comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
# TODO comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
# TODO comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
# FIXME: comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
# FIXME comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
# FIXME comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`

View File

@@ -1,5 +1,5 @@
def f(x, y, z):
"""Do f.
"""Do something.
Args:
x: the value
@@ -12,7 +12,7 @@ def f(x, y, z):
def f(x, y, z):
"""Do f.
"""Do something.
Args:
x:
@@ -25,7 +25,7 @@ def f(x, y, z):
def f(x, y, z):
"""Do f.
"""Do something.
Args:
x:
@@ -37,7 +37,7 @@ def f(x, y, z):
def f(x, y, z):
"""Do f.
"""Do something.
Args:
x: the value def
@@ -50,7 +50,7 @@ def f(x, y, z):
def f(x, y, z):
"""Do f.
"""Do something.
Args:
x: the value
@@ -63,7 +63,7 @@ def f(x, y, z):
def f(x, y, z):
"""Do g.
"""Do something.
Args:
x: the value
@@ -75,10 +75,41 @@ def f(x, y, z):
def f(x, y, z):
"""Do h.
"""Do something.
Args:
x: the value
z: A final argument
"""
return x
def f(x, *args, **kwargs):
"""Do something.
Args:
x: the value
*args: variable arguments
**kwargs: keyword arguments
"""
return x
def f(x, *args, **kwargs):
"""Do something.
Args:
*args: variable arguments
**kwargs: keyword arguments
"""
return x
def f(x, *args, **kwargs):
"""Do something.
Args:
x: the value
**kwargs: keyword arguments
"""
return x

View File

@@ -0,0 +1,3 @@
class Pool:
@classmethod
def create(cls) -> Pool: ...

View File

@@ -7,12 +7,14 @@ str("foo", **k)
str("foo", encoding="UTF-8")
str("foo"
"bar")
str(b"foo")
bytes("foo", encoding="UTF-8")
bytes(*a)
bytes("foo", *a)
bytes("foo", **a)
bytes(b"foo"
b"bar")
bytes("foo")
# These become string or byte literals
str()

View File

@@ -0,0 +1,74 @@
def f():
for x in y:
yield x
def g():
for x, y in z:
yield (x, y)
def h():
for x in [1, 2, 3]:
yield x
def i():
for x in {x for x in y}:
yield x
def j():
for x in (1, 2, 3):
yield x
def k():
for x, y in {3: "x", 6: "y"}:
yield x, y
def f(): # Comment one\n'
# Comment two\n'
for x, y in { # Comment three\n'
3: "x", # Comment four\n'
# Comment five\n'
6: "y", # Comment six\n'
}: # Comment seven\n'
# Comment eight\n'
yield x, y # Comment nine\n'
# Comment ten',
def f():
for x, y in [{3: (3, [44, "long ss"]), 6: "y"}]:
yield x, y
def f():
for x, y in z():
yield x, y
def f():
def func():
# This comment is preserved\n'
for x, y in z(): # Comment one\n'
# Comment two\n'
yield x, y # Comment three\n'
# Comment four\n'
# Comment\n'
def g():
print(3)
def f():
for x in y:
yield x
for z in x:
yield z
def f():
for x, y in z():
yield x, y
x = 1

View File

@@ -0,0 +1,113 @@
# These should NOT change
def f():
for x in z:
yield
def f():
for x in z:
yield y
def f():
for x, y in z:
yield x
def f():
for x, y in z:
yield y
def f():
for a, b in z:
yield x, y
def f():
for x, y in z:
yield y, x
def f():
for x, y, c in z:
yield x, y
def f():
for x in z:
x = 22
yield x
def f():
for x in z:
yield x
else:
print("boom!")
def f():
for x in range(5):
yield x
print(x)
def f():
def g():
print(x)
for x in range(5):
yield x
g()
def f():
def g():
def h():
print(x)
return h
for x in range(5):
yield x
g()()
def f(x):
for x in y:
yield x
del x
async def f():
for x in y:
yield x
def f():
x = 1
print(x)
for x in y:
yield x
def f():
for x in y:
yield x
print(x)
def f():
for x in y:
yield x
z = lambda: x
def f():
for x in y:
yield x
class C:
def __init__(self):
print(x)

View File

@@ -0,0 +1,9 @@
from builtins import *
from builtins import ascii, bytes, compile
from builtins import str as _str
from six.moves import filter, zip, zip_longest
from io import open
import io
import six
import six.moves
import builtins

View File

@@ -121,6 +121,17 @@
}
]
},
"flake8-bandit": {
"description": "Options for the `flake8-bandit` plugin.",
"anyOf": [
{
"$ref": "#/definitions/Flake8BanditOptions"
},
{
"type": "null"
}
]
},
"flake8-bugbear": {
"description": "Options for the `flake8-bugbear` plugin.",
"anyOf": [
@@ -288,6 +299,17 @@
}
}
},
"pycodestyle": {
"description": "Options for the `pycodestyle` plugin.",
"anyOf": [
{
"$ref": "#/definitions/Pycodestyle"
},
{
"type": "null"
}
]
},
"pydocstyle": {
"description": "Options for the `pydocstyle` plugin.",
"anyOf": [
@@ -366,6 +388,16 @@
}
]
},
"task-tags": {
"description": "A list of task tags to recognize (e.g., \"TODO\", \"FIXME\", \"XXX\").\n\nComments starting with these tags will be ignored by commented-out code detection (`ERA`), and skipped by line-length checks (`E501`) if `ignore-overlong-task-comments` is set to `true`.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"unfixable": {
"description": "A list of check code prefixes to consider un-autofix-able.",
"type": [
@@ -889,16 +921,27 @@
"S10",
"S101",
"S102",
"S103",
"S104",
"S105",
"S106",
"S107",
"S108",
"SIM",
"SIM1",
"SIM10",
"SIM102",
"SIM105",
"SIM107",
"SIM11",
"SIM110",
"SIM111",
"SIM117",
"SIM118",
"SIM2",
"SIM22",
"SIM220",
"SIM221",
"SIM222",
"SIM223",
"SIM3",
@@ -969,6 +1012,8 @@
"UP025",
"UP026",
"UP027",
"UP028",
"UP029",
"W",
"W2",
"W29",
@@ -1054,6 +1099,32 @@
},
"additionalProperties": false
},
"Flake8BanditOptions": {
"type": "object",
"properties": {
"hardcoded-tmp-directory": {
"description": "A list of directories to consider temporary.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"hardcoded-tmp-directory-extend": {
"description": "A list of directories to consider temporary, in addition to those specified by `hardcoded-tmp-directory`.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"Flake8BugbearOptions": {
"type": "object",
"properties": {
@@ -1324,6 +1395,13 @@
"type": "string"
}
},
"order-by-type": {
"description": "Order imports by type, which is determined by case, in addition to alphabetically.",
"type": [
"boolean",
"null"
]
},
"single-line-exclusions": {
"description": "One or more modules to exclude from the single line rule.",
"type": [
@@ -1430,6 +1508,19 @@
},
"additionalProperties": false
},
"Pycodestyle": {
"type": "object",
"properties": {
"ignore-overlong-task-comments": {
"description": "Whether or not line-length checks (`E501`) should be triggered for comments starting with `task-tags` (by default: [\"TODO\", \"FIXME\", and \"XXX\"]).",
"type": [
"boolean",
"null"
]
}
},
"additionalProperties": false
},
"Pydocstyle": {
"type": "object",
"properties": {

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.208"
version = "0.0.211"
edition = "2021"
[dependencies]

View File

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

View File

@@ -574,6 +574,53 @@ pub fn followed_by_multi_statement_line(stmt: &Stmt, locator: &SourceCodeLocator
match_trailing_content(stmt, locator)
}
#[derive(Default)]
/// A simple representation of a call's positional and keyword arguments.
pub struct SimpleCallArgs<'a> {
pub args: Vec<&'a Expr>,
pub kwargs: FxHashMap<&'a str, &'a Expr>,
}
impl<'a> SimpleCallArgs<'a> {
pub fn new(args: &'a Vec<Expr>, keywords: &'a Vec<Keyword>) -> Self {
let mut result = SimpleCallArgs::default();
for arg in args {
match &arg.node {
ExprKind::Starred { .. } => {
break;
}
_ => {
result.args.push(arg);
}
}
}
for keyword in keywords {
if let Some(arg) = &keyword.node.arg {
result.kwargs.insert(arg, &keyword.node.value);
}
}
result
}
/// Get the argument with the given name or position.
/// If the argument is not found with either name or position, return
/// `None`.
pub fn get_argument(&self, name: &'a str, position: Option<usize>) -> Option<&'a Expr> {
if let Some(kwarg) = self.kwargs.get(name) {
return Some(kwarg);
}
if let Some(position) = position {
if position < self.args.len() {
return Some(self.args[position]);
}
}
None
}
}
#[cfg(test)]
mod tests {
use anyhow::Result;

View File

@@ -1,11 +1,17 @@
use anyhow::{bail, Result};
use itertools::Itertools;
use libcst_native::{
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
};
use rustpython_parser::ast::{ExcepthandlerKind, Location, Stmt, StmtKind};
use crate::ast::helpers;
use crate::ast::helpers::to_absolute;
use crate::ast::types::Range;
use crate::ast::whitespace::LinesWithTrailingNewline;
use crate::autofix::Fix;
use crate::cst::helpers::compose_module_path;
use crate::cst::matchers::match_module;
use crate::source_code_locator::SourceCodeLocator;
/// Determine if a body contains only a single statement, taking into account
@@ -185,6 +191,107 @@ pub fn delete_stmt(
}
}
/// Generate a `Fix` to remove any unused imports from an `import` statement.
pub fn remove_unused_imports<'a>(
unused_imports: impl Iterator<Item = &'a str>,
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
locator: &SourceCodeLocator,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
let mut tree = match_module(&module_text)?;
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
bail!("Expected Statement::Simple");
};
let (aliases, import_module) = match body.body.first_mut() {
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
Some(SmallStatement::ImportFrom(import_body)) => {
if let ImportNames::Aliases(names) = &mut import_body.names {
(names, import_body.module.as_ref())
} else if let ImportNames::Star(..) = &import_body.names {
// Special-case: if the import is a `from ... import *`, then we delete the
// entire statement.
let mut found_star = false;
for unused_import in unused_imports {
let full_name = match import_body.module.as_ref() {
Some(module_name) => format!("{}.*", compose_module_path(module_name),),
None => "*".to_string(),
};
if unused_import == full_name {
found_star = true;
} else {
bail!(
"Expected \"*\" for unused import (got: \"{}\")",
unused_import
);
}
}
if !found_star {
bail!("Expected \'*\' for unused import");
}
return delete_stmt(stmt, parent, deleted, locator);
} else {
bail!("Expected: ImportNames::Aliases | ImportNames::Star");
}
}
_ => bail!("Expected: SmallStatement::ImportFrom | SmallStatement::Import"),
};
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
for unused_import in unused_imports {
let alias_index = aliases.iter().position(|alias| {
let full_name = match import_module {
Some(module_name) => format!(
"{}.{}",
compose_module_path(module_name),
compose_module_path(&alias.name)
),
None => compose_module_path(&alias.name),
};
full_name == unused_import
});
if let Some(index) = alias_index {
aliases.remove(index);
}
}
// But avoid destroying any trailing comments.
if let Some(alias) = aliases.last_mut() {
let has_comment = if let Some(comma) = &alias.comma {
match &comma.whitespace_after {
ParenthesizableWhitespace::SimpleWhitespace(_) => false,
ParenthesizableWhitespace::ParenthesizedWhitespace(whitespace) => {
whitespace.first_line.comment.is_some()
}
}
} else {
false
};
if !has_comment {
alias.comma = trailing_comma;
}
}
if aliases.is_empty() {
delete_stmt(stmt, parent, deleted, locator)
} else {
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
stmt.location,
stmt.end_location.unwrap(),
))
}
}
#[cfg(test)]
mod tests {
use anyhow::Result;

View File

@@ -6,7 +6,7 @@ use itertools::Itertools;
use log::error;
use nohash_hasher::IntMap;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Located, Location};
use rustpython_ast::{Comprehension, Located, Location};
use rustpython_common::cformat::{CFormatError, CFormatErrorType};
use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
@@ -37,7 +37,7 @@ use crate::source_code_locator::SourceCodeLocator;
use crate::source_code_style::SourceCodeStyleDetector;
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
autofix, docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez,
flake8_debugger, flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pie, flake8_print, flake8_pytest_style, flake8_return, flake8_simplify,
@@ -151,7 +151,7 @@ impl<'a> Checker<'a> {
in_subscript: false,
seen_import_boundary: false,
futures_allowed: true,
annotations_future_enabled: false,
annotations_future_enabled: path.extension().map_or(false, |ext| ext == "pyi"),
except_handlers: vec![],
// Check-specific state.
flake8_bugbear_seen: vec![],
@@ -470,7 +470,7 @@ where
if self.settings.enabled.contains(&CheckCode::S107) {
self.add_checks(
flake8_bandit::plugins::hardcoded_password_default(args).into_iter(),
flake8_bandit::checks::hardcoded_password_default(args).into_iter(),
);
}
@@ -890,14 +890,19 @@ where
}
}
if let Some("__future__") = module.as_deref() {
if self.settings.enabled.contains(&CheckCode::UP010) {
if self.settings.enabled.contains(&CheckCode::UP010) {
if let Some("__future__") = module.as_deref() {
pyupgrade::plugins::unnecessary_future_import(self, stmt, names);
}
}
if self.settings.enabled.contains(&CheckCode::UP026) {
pyupgrade::plugins::rewrite_mock_import(self, stmt);
}
if self.settings.enabled.contains(&CheckCode::UP029) {
if let Some(module) = module.as_deref() {
pyupgrade::plugins::unnecessary_builtin_import(self, stmt, module, names);
}
}
if self.settings.enabled.contains(&CheckCode::TID251) {
if let Some(module) = module {
@@ -1172,6 +1177,9 @@ where
if self.settings.enabled.contains(&CheckCode::F634) {
pyflakes::plugins::if_tuple(self, stmt, test);
}
if self.settings.enabled.contains(&CheckCode::SIM102) {
flake8_simplify::plugins::nested_if_statements(self, stmt);
}
}
StmtKind::Assert { test, msg } => {
if self.settings.enabled.contains(&CheckCode::F631) {
@@ -1186,7 +1194,7 @@ where
);
}
if self.settings.enabled.contains(&CheckCode::S101) {
self.add_check(flake8_bandit::plugins::assert_used(stmt));
self.add_check(flake8_bandit::checks::assert_used(stmt));
}
if self.settings.enabled.contains(&CheckCode::PT015) {
if let Some(check) = flake8_pytest_style::plugins::assert_falsy(stmt, test) {
@@ -1208,6 +1216,9 @@ where
if self.settings.enabled.contains(&CheckCode::PT012) {
flake8_pytest_style::plugins::complex_raises(self, stmt, items, body);
}
if self.settings.enabled.contains(&CheckCode::SIM117) {
flake8_simplify::plugins::multiple_with_statements(self, stmt);
}
}
StmtKind::While { body, orelse, .. } => {
if self.settings.enabled.contains(&CheckCode::B023) {
@@ -1247,7 +1258,13 @@ where
flake8_simplify::plugins::key_in_dict_for(self, target, iter);
}
}
StmtKind::Try { handlers, .. } => {
StmtKind::Try {
body,
handlers,
orelse,
finalbody,
..
} => {
if self.settings.enabled.contains(&CheckCode::F707) {
if let Some(check) =
pyflakes::checks::default_except_not_last(handlers, self.locator)
@@ -1272,6 +1289,16 @@ where
.into_iter(),
);
}
if self.settings.enabled.contains(&CheckCode::SIM105) {
flake8_simplify::plugins::use_contextlib_suppress(
self, stmt, handlers, orelse, finalbody,
);
}
if self.settings.enabled.contains(&CheckCode::SIM107) {
flake8_simplify::plugins::return_in_try_except_finally(
self, body, handlers, finalbody,
);
}
}
StmtKind::Assign { targets, value, .. } => {
if self.settings.enabled.contains(&CheckCode::E731) {
@@ -1286,7 +1313,7 @@ where
if self.settings.enabled.contains(&CheckCode::S105) {
if let Some(check) =
flake8_bandit::plugins::assign_hardcoded_password_string(value, targets)
flake8_bandit::checks::assign_hardcoded_password_string(value, targets)
{
self.add_check(check);
}
@@ -1357,6 +1384,9 @@ where
body,
&Documentable::Function,
);
if self.settings.enabled.contains(&CheckCode::UP028) {
pyupgrade::plugins::rewrite_yield_from(self, stmt);
}
let scope = transition_scope(&self.visible_scope, stmt, &Documentable::Function);
self.definitions
.push((definition, scope.visibility.clone()));
@@ -1851,13 +1881,24 @@ where
// flake8-bandit
if self.settings.enabled.contains(&CheckCode::S102) {
if let Some(check) = flake8_bandit::plugins::exec_used(expr, func) {
if let Some(check) = flake8_bandit::checks::exec_used(expr, func) {
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::S103) {
if let Some(check) = flake8_bandit::checks::bad_file_permissions(
func,
args,
keywords,
&self.from_imports,
&self.import_aliases,
) {
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::S106) {
self.add_checks(
flake8_bandit::plugins::hardcoded_password_func_arg(keywords).into_iter(),
flake8_bandit::checks::hardcoded_password_func_arg(keywords).into_iter(),
);
}
@@ -2452,7 +2493,7 @@ where
if self.settings.enabled.contains(&CheckCode::S105) {
self.add_checks(
flake8_bandit::plugins::compare_to_hardcoded_password_string(
flake8_bandit::checks::compare_to_hardcoded_password_string(
left,
comparators,
)
@@ -2497,13 +2538,22 @@ where
));
}
if self.settings.enabled.contains(&CheckCode::S104) {
if let Some(check) = flake8_bandit::plugins::hardcoded_bind_all_interfaces(
if let Some(check) = flake8_bandit::checks::hardcoded_bind_all_interfaces(
value,
&Range::from_located(expr),
) {
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::S108) {
if let Some(check) = flake8_bandit::checks::hardcoded_tmp_directory(
expr,
value,
&self.settings.flake8_bandit.hardcoded_tmp_directory,
) {
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::UP025) {
pyupgrade::plugins::rewrite_unicode_literal(self, expr, kind);
}
@@ -2575,6 +2625,12 @@ where
if self.settings.enabled.contains(&CheckCode::PLR1701) {
pylint::plugins::merge_isinstance(self, expr, op, values);
}
if self.settings.enabled.contains(&CheckCode::SIM220) {
flake8_simplify::plugins::a_and_not_a(self, expr);
}
if self.settings.enabled.contains(&CheckCode::SIM221) {
flake8_simplify::plugins::a_or_not_a(self, expr);
}
if self.settings.enabled.contains(&CheckCode::SIM222) {
flake8_simplify::plugins::or_true(self, expr);
}
@@ -2922,6 +2978,17 @@ where
}
}
fn visit_comprehension(&mut self, comprehension: &'b Comprehension) {
if self.settings.enabled.contains(&CheckCode::SIM118) {
flake8_simplify::plugins::key_in_dict_for(
self,
&comprehension.target,
&comprehension.iter,
);
}
visitor::walk_comprehension(self, comprehension);
}
fn visit_arguments(&mut self, arguments: &'b Arguments) {
if self.settings.enabled.contains(&CheckCode::B006) {
flake8_bugbear::plugins::mutable_argument_default(self, arguments);
@@ -2994,6 +3061,19 @@ where
if self.settings.enabled.contains(&CheckCode::PIE790) {
flake8_pie::plugins::no_unnecessary_pass(self, body);
}
if self.settings.enabled.contains(&CheckCode::SIM110)
|| self.settings.enabled.contains(&CheckCode::SIM111)
{
for (stmt, sibling) in body.iter().tuple_windows() {
if matches!(stmt.node, StmtKind::For { .. })
&& matches!(sibling.node, StmtKind::Return { .. })
{
flake8_simplify::plugins::convert_loop_to_any_all(self, stmt, sibling);
}
}
}
visitor::walk_body(self, body);
}
}
@@ -3851,8 +3931,10 @@ impl<'a> Checker<'a> {
let fix = if !ignore_init && self.patch(&CheckCode::F401) {
let deleted: Vec<&Stmt> =
self.deletions.iter().map(|node| node.0).collect();
match pyflakes::fixes::remove_unused_imports(
&unused_imports,
match autofix::helpers::remove_unused_imports(
unused_imports
.iter()
.map(|(full_name, _)| full_name.as_str()),
child,
parent,
&deleted,
@@ -3876,7 +3958,7 @@ impl<'a> Checker<'a> {
let multiple = unused_imports.len() > 1;
for (full_name, range) in unused_imports {
let mut check = Check::new(
CheckKind::UnusedImport(full_name.clone(), ignore_init, multiple),
CheckKind::UnusedImport(full_name.to_string(), ignore_init, multiple),
*range,
);
if matches!(child.node, StmtKind::ImportFrom { .. })

View File

@@ -57,7 +57,7 @@ pub fn check_lines(
}
if enforce_line_too_long {
if let Some(check) = line_too_long(index, line, settings.line_length) {
if let Some(check) = line_too_long(index, line, settings) {
checks.push(check);
}
}

View File

@@ -31,7 +31,7 @@ pub fn commented_out_code(
let line = locator.slice_source_code_range(&Range::new(location, end_location));
// Verify that the comment is on its own line, and that it contains code.
if is_standalone_comment(&line) && comment_contains_code(&line) {
if is_standalone_comment(&line) && comment_contains_code(&line, &settings.task_tags[..]) {
let mut check = Check::new(CheckKind::CommentedOutCode, Range::new(start, end));
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(&CheckCode::ERA001)

View File

@@ -4,7 +4,7 @@ use regex::Regex;
static ALLOWLIST_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"^(?i)(?:pylint|pyright|noqa|nosec|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)|TODO|FIXME|XXX)"
r"^(?i)(?:pylint|pyright|noqa|nosec|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?))"
).unwrap()
});
static BRACKET_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[()\[\]{}\s]+$").unwrap());
@@ -30,7 +30,7 @@ static PARTIAL_DICTIONARY_REGEX: Lazy<Regex> =
static PRINT_RETURN_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(print|return)\b\s*").unwrap());
/// Returns `true` if a comment contains Python code.
pub fn comment_contains_code(line: &str) -> bool {
pub fn comment_contains_code(line: &str, task_tags: &[String]) -> bool {
let line = if let Some(line) = line.trim().strip_prefix('#') {
line.trim()
} else {
@@ -47,6 +47,12 @@ pub fn comment_contains_code(line: &str) -> bool {
return false;
}
if let Some(first) = line.split(&[' ', ':']).next() {
if task_tags.iter().any(|tag| tag == first) {
return false;
}
}
if CODING_COMMENT_REGEX.is_match(line) {
return false;
}
@@ -97,142 +103,166 @@ mod tests {
#[test]
fn comment_contains_code_basic() {
assert!(comment_contains_code("# x = 1"));
assert!(comment_contains_code("#from foo import eradicate"));
assert!(comment_contains_code("#import eradicate"));
assert!(comment_contains_code(r#"#"key": value,"#));
assert!(comment_contains_code(r#"#"key": "value","#));
assert!(comment_contains_code(r#"#"key": 1 + 1,"#));
assert!(comment_contains_code("#'key': 1 + 1,"));
assert!(comment_contains_code(r#"#"key": {"#));
assert!(comment_contains_code("#}"));
assert!(comment_contains_code("#} )]"));
assert!(comment_contains_code("# x = 1", &[]));
assert!(comment_contains_code("#from foo import eradicate", &[]));
assert!(comment_contains_code("#import eradicate", &[]));
assert!(comment_contains_code(r#"#"key": value,"#, &[]));
assert!(comment_contains_code(r#"#"key": "value","#, &[]));
assert!(comment_contains_code(r#"#"key": 1 + 1,"#, &[]));
assert!(comment_contains_code("#'key': 1 + 1,", &[]));
assert!(comment_contains_code(r#"#"key": {"#, &[]));
assert!(comment_contains_code("#}", &[]));
assert!(comment_contains_code("#} )]", &[]));
assert!(!comment_contains_code("#"));
assert!(!comment_contains_code("# This is a (real) comment."));
assert!(!comment_contains_code("# 123"));
assert!(!comment_contains_code("# 123.1"));
assert!(!comment_contains_code("# 1, 2, 3"));
assert!(!comment_contains_code("x = 1 # x = 1"));
assert!(!comment_contains_code("#", &[]));
assert!(!comment_contains_code("# This is a (real) comment.", &[]));
assert!(!comment_contains_code("# 123", &[]));
assert!(!comment_contains_code("# 123.1", &[]));
assert!(!comment_contains_code("# 1, 2, 3", &[]));
assert!(!comment_contains_code("x = 1 # x = 1", &[]));
assert!(!comment_contains_code(
"# pylint: disable=redefined-outer-name"
"# pylint: disable=redefined-outer-name",
&[]
),);
assert!(!comment_contains_code(
"# Issue #999: This is not code",
&[]
));
assert!(!comment_contains_code("# Issue #999: This is not code"));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#},"));
assert!(!comment_contains_code("#},", &[]));
}
#[test]
fn comment_contains_code_with_print() {
assert!(comment_contains_code("#print"));
assert!(comment_contains_code("#print(1)"));
assert!(comment_contains_code("#print 1"));
assert!(comment_contains_code("#print", &[]));
assert!(comment_contains_code("#print(1)", &[]));
assert!(comment_contains_code("#print 1", &[]));
assert!(!comment_contains_code("#to print"));
assert!(!comment_contains_code("#to print", &[]));
}
#[test]
fn comment_contains_code_with_return() {
assert!(comment_contains_code("#return x"));
assert!(comment_contains_code("#return x", &[]));
assert!(!comment_contains_code("#to print"));
assert!(!comment_contains_code("#to print", &[]));
}
#[test]
fn comment_contains_code_with_multiline() {
assert!(comment_contains_code("#else:"));
assert!(comment_contains_code("# else : "));
assert!(comment_contains_code(r#"# "foo %d" % \\"#));
assert!(comment_contains_code("#elif True:"));
assert!(comment_contains_code("#x = foo("));
assert!(comment_contains_code("#except Exception:"));
assert!(comment_contains_code("#else:", &[]));
assert!(comment_contains_code("# else : ", &[]));
assert!(comment_contains_code(r#"# "foo %d" % \\"#, &[]));
assert!(comment_contains_code("#elif True:", &[]));
assert!(comment_contains_code("#x = foo(", &[]));
assert!(comment_contains_code("#except Exception:", &[]));
assert!(!comment_contains_code("# this is = to that :("));
assert!(!comment_contains_code("#else"));
assert!(!comment_contains_code("#or else:"));
assert!(!comment_contains_code("#else True:"));
assert!(!comment_contains_code("# this is = to that :(", &[]));
assert!(!comment_contains_code("#else", &[]));
assert!(!comment_contains_code("#or else:", &[]));
assert!(!comment_contains_code("#else True:", &[]));
// Unpacking assignments
assert!(comment_contains_code(
"# user_content_type, _ = TimelineEvent.objects.using(db_alias).get_or_create("
"# user_content_type, _ = TimelineEvent.objects.using(db_alias).get_or_create(",
&[]
),);
assert!(comment_contains_code(
"# (user_content_type, _) = TimelineEvent.objects.using(db_alias).get_or_create(",
&[]
),);
assert!(comment_contains_code(
"# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create(",
&[]
));
assert!(comment_contains_code(
"# (user_content_type, _) = TimelineEvent.objects.using(db_alias).get_or_create("
"# app_label=\"core\", model=\"user\"",
&[]
));
assert!(comment_contains_code(
"# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create("
));
assert!(comment_contains_code(
"# app_label=\"core\", model=\"user\""
));
assert!(comment_contains_code("# )"));
assert!(comment_contains_code("# )", &[]));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#def foo():"));
assert!(!comment_contains_code("#def foo():", &[]));
}
#[test]
fn comment_contains_code_with_sentences() {
assert!(!comment_contains_code("#code is good"));
assert!(!comment_contains_code("#code is good", &[]));
}
#[test]
fn comment_contains_code_with_encoding() {
assert!(comment_contains_code("# codings=utf-8"));
assert!(comment_contains_code("# codings=utf-8", &[]));
assert!(!comment_contains_code("# coding=utf-8"));
assert!(!comment_contains_code("#coding= utf-8"));
assert!(!comment_contains_code("# coding: utf-8"));
assert!(!comment_contains_code("# encoding: utf8"));
assert!(!comment_contains_code("# coding=utf-8", &[]));
assert!(!comment_contains_code("#coding= utf-8", &[]));
assert!(!comment_contains_code("# coding: utf-8", &[]));
assert!(!comment_contains_code("# encoding: utf8", &[]));
}
#[test]
fn comment_contains_code_with_default_allowlist() {
assert!(!comment_contains_code("# pylint: disable=A0123"));
assert!(!comment_contains_code("# pylint:disable=A0123"));
assert!(!comment_contains_code("# pylint: disable = A0123"));
assert!(!comment_contains_code("# pylint:disable = A0123"));
assert!(!comment_contains_code("# pyright: reportErrorName=true"));
assert!(!comment_contains_code("# noqa"));
assert!(!comment_contains_code("# NOQA"));
assert!(!comment_contains_code("# noqa: A123"));
assert!(!comment_contains_code("# noqa:A123"));
assert!(!comment_contains_code("# nosec"));
assert!(!comment_contains_code("# fmt: on"));
assert!(!comment_contains_code("# fmt: off"));
assert!(!comment_contains_code("# fmt:on"));
assert!(!comment_contains_code("# fmt:off"));
assert!(!comment_contains_code("# isort: on"));
assert!(!comment_contains_code("# isort:on"));
assert!(!comment_contains_code("# isort: off"));
assert!(!comment_contains_code("# isort:off"));
assert!(!comment_contains_code("# isort: skip"));
assert!(!comment_contains_code("# isort:skip"));
assert!(!comment_contains_code("# isort: skip_file"));
assert!(!comment_contains_code("# isort:skip_file"));
assert!(!comment_contains_code("# isort: split"));
assert!(!comment_contains_code("# isort:split"));
assert!(!comment_contains_code("# isort: dont-add-imports"));
assert!(!comment_contains_code("# isort:dont-add-imports"));
assert!(!comment_contains_code("# pylint: disable=A0123", &[]));
assert!(!comment_contains_code("# pylint:disable=A0123", &[]));
assert!(!comment_contains_code("# pylint: disable = A0123", &[]));
assert!(!comment_contains_code("# pylint:disable = A0123", &[]));
assert!(!comment_contains_code(
"# isort: dont-add-imports: [\"import os\"]"
"# pyright: reportErrorName=true",
&[]
));
assert!(!comment_contains_code("# noqa", &[]));
assert!(!comment_contains_code("# NOQA", &[]));
assert!(!comment_contains_code("# noqa: A123", &[]));
assert!(!comment_contains_code("# noqa:A123", &[]));
assert!(!comment_contains_code("# nosec", &[]));
assert!(!comment_contains_code("# fmt: on", &[]));
assert!(!comment_contains_code("# fmt: off", &[]));
assert!(!comment_contains_code("# fmt:on", &[]));
assert!(!comment_contains_code("# fmt:off", &[]));
assert!(!comment_contains_code("# isort: on", &[]));
assert!(!comment_contains_code("# isort:on", &[]));
assert!(!comment_contains_code("# isort: off", &[]));
assert!(!comment_contains_code("# isort:off", &[]));
assert!(!comment_contains_code("# isort: skip", &[]));
assert!(!comment_contains_code("# isort:skip", &[]));
assert!(!comment_contains_code("# isort: skip_file", &[]));
assert!(!comment_contains_code("# isort:skip_file", &[]));
assert!(!comment_contains_code("# isort: split", &[]));
assert!(!comment_contains_code("# isort:split", &[]));
assert!(!comment_contains_code("# isort: dont-add-imports", &[]));
assert!(!comment_contains_code("# isort:dont-add-imports", &[]));
assert!(!comment_contains_code(
"# isort: dont-add-imports: [\"import os\"]",
&[]
));
assert!(!comment_contains_code(
"# isort:dont-add-imports: [\"import os\"]"
"# isort:dont-add-imports: [\"import os\"]",
&[]
));
assert!(!comment_contains_code(
"# isort: dont-add-imports:[\"import os\"]"
"# isort: dont-add-imports:[\"import os\"]",
&[]
));
assert!(!comment_contains_code(
"# isort:dont-add-imports:[\"import os\"]"
"# isort:dont-add-imports:[\"import os\"]",
&[]
));
assert!(!comment_contains_code("# type: ignore", &[]));
assert!(!comment_contains_code("# type:ignore", &[]));
assert!(!comment_contains_code("# type: ignore[import]", &[]));
assert!(!comment_contains_code("# type:ignore[import]", &[]));
assert!(!comment_contains_code(
"# TODO: Do that",
&["TODO".to_string()]
));
assert!(!comment_contains_code(
"# FIXME: Fix that",
&["FIXME".to_string()]
));
assert!(!comment_contains_code(
"# XXX: What ever",
&["XXX".to_string()]
));
assert!(!comment_contains_code("# type: ignore"));
assert!(!comment_contains_code("# type:ignore"));
assert!(!comment_contains_code("# type: ignore[import]"));
assert!(!comment_contains_code("# type:ignore[import]"));
assert!(!comment_contains_code("# TODO: Do that"));
assert!(!comment_contains_code("# FIXME: Fix that"));
assert!(!comment_contains_code("# XXX: What ever"));
}
}

View File

@@ -0,0 +1,108 @@
use num_traits::ToPrimitive;
use once_cell::sync::Lazy;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Constant, Expr, ExprKind, Keyword, Operator};
use crate::ast::helpers::{compose_call_path, match_module_member, SimpleCallArgs};
use crate::ast::types::Range;
use crate::registry::{Check, CheckKind};
const WRITE_WORLD: u16 = 0o2;
const EXECUTE_GROUP: u16 = 0o10;
static PYSTAT_MAPPING: Lazy<FxHashMap<&'static str, u16>> = Lazy::new(|| {
FxHashMap::from_iter([
("stat.ST_MODE", 0o0),
("stat.S_IFDOOR", 0o0),
("stat.S_IFPORT", 0o0),
("stat.ST_INO", 0o1),
("stat.S_IXOTH", 0o1),
("stat.UF_NODUMP", 0o1),
("stat.ST_DEV", 0o2),
("stat.S_IWOTH", 0o2),
("stat.UF_IMMUTABLE", 0o2),
("stat.ST_NLINK", 0o3),
("stat.ST_UID", 0o4),
("stat.S_IROTH", 0o4),
("stat.UF_APPEND", 0o4),
("stat.ST_GID", 0o5),
("stat.ST_SIZE", 0o6),
("stat.ST_ATIME", 0o7),
("stat.S_IRWXO", 0o7),
("stat.ST_MTIME", 0o10),
("stat.S_IXGRP", 0o10),
("stat.UF_OPAQUE", 0o10),
("stat.ST_CTIME", 0o11),
("stat.S_IWGRP", 0o20),
("stat.UF_NOUNLINK", 0o20),
("stat.S_IRGRP", 0o40),
("stat.UF_COMPRESSED", 0o40),
("stat.S_IRWXG", 0o70),
("stat.S_IEXEC", 0o100),
("stat.S_IXUSR", 0o100),
("stat.S_IWRITE", 0o200),
("stat.S_IWUSR", 0o200),
("stat.S_IREAD", 0o400),
("stat.S_IRUSR", 0o400),
("stat.S_IRWXU", 0o700),
("stat.S_ISVTX", 0o1000),
("stat.S_ISGID", 0o2000),
("stat.S_ENFMT", 0o2000),
("stat.S_ISUID", 0o4000),
])
});
fn get_int_value(expr: &Expr) -> Option<u16> {
match &expr.node {
ExprKind::Constant {
value: Constant::Int(value),
..
} => value.to_u16(),
ExprKind::Attribute { .. } => {
if let Some(path) = compose_call_path(expr) {
PYSTAT_MAPPING.get(path.as_str()).copied()
} else {
None
}
}
ExprKind::BinOp { left, op, right } => {
if let (Some(left_value), Some(right_value)) =
(get_int_value(left), get_int_value(right))
{
match op {
Operator::BitAnd => Some(left_value & right_value),
Operator::BitOr => Some(left_value | right_value),
Operator::BitXor => Some(left_value ^ right_value),
_ => None,
}
} else {
None
}
}
_ => None,
}
}
/// S103
pub fn bad_file_permissions(
func: &Expr,
args: &Vec<Expr>,
keywords: &Vec<Keyword>,
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
import_aliases: &FxHashMap<&str, &str>,
) -> Option<Check> {
if match_module_member(func, "os", "chmod", from_imports, import_aliases) {
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(mode_arg) = call_args.get_argument("mode", Some(1)) {
if let Some(int_value) = get_int_value(mode_arg) {
if (int_value & WRITE_WORLD > 0) || (int_value & EXECUTE_GROUP > 0) {
return Some(Check::new(
CheckKind::BadFilePermissions(int_value),
Range::from_located(mode_arg),
));
}
}
}
}
None
}

View File

@@ -0,0 +1,16 @@
use rustpython_ast::Expr;
use crate::ast::types::Range;
use crate::registry::{Check, CheckKind};
/// S108
pub fn hardcoded_tmp_directory(expr: &Expr, value: &str, prefixes: &[String]) -> Option<Check> {
if prefixes.iter().any(|prefix| value.starts_with(prefix)) {
Some(Check::new(
CheckKind::HardcodedTempFile(value.to_string()),
Range::from_located(expr),
))
} else {
None
}
}

View File

@@ -1,4 +1,5 @@
pub use assert_used::assert_used;
pub use bad_file_permissions::bad_file_permissions;
pub use exec_used::exec_used;
pub use hardcoded_bind_all_interfaces::hardcoded_bind_all_interfaces;
pub use hardcoded_password_default::hardcoded_password_default;
@@ -6,10 +7,13 @@ pub use hardcoded_password_func_arg::hardcoded_password_func_arg;
pub use hardcoded_password_string::{
assign_hardcoded_password_string, compare_to_hardcoded_password_string,
};
pub use hardcoded_tmp_directory::hardcoded_tmp_directory;
mod assert_used;
mod bad_file_permissions;
mod exec_used;
mod hardcoded_bind_all_interfaces;
mod hardcoded_password_default;
mod hardcoded_password_func_arg;
mod hardcoded_password_string;
mod hardcoded_tmp_directory;

View File

@@ -1,9 +1,9 @@
pub mod checks;
mod helpers;
pub mod plugins;
pub mod settings;
#[cfg(test)]
mod tests {
use std::convert::AsRef;
use std::path::Path;
use anyhow::Result;
@@ -11,23 +11,45 @@ mod tests {
use crate::linter::test_path;
use crate::registry::CheckCode;
use crate::settings;
use crate::{flake8_bandit, Settings};
#[test_case(CheckCode::S101, Path::new("S101.py"); "S101")]
#[test_case(CheckCode::S102, Path::new("S102.py"); "S102")]
#[test_case(CheckCode::S103, Path::new("S103.py"); "S103")]
#[test_case(CheckCode::S104, Path::new("S104.py"); "S104")]
#[test_case(CheckCode::S105, Path::new("S105.py"); "S105")]
#[test_case(CheckCode::S106, Path::new("S106.py"); "S106")]
#[test_case(CheckCode::S107, Path::new("S107.py"); "S107")]
#[test_case(CheckCode::S108, Path::new("S108.py"); "S108")]
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/flake8_bandit")
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
&Settings::for_rule(check_code),
)?;
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test]
fn check_hardcoded_tmp_additional_dirs() -> Result<()> {
let checks = test_path(
Path::new("./resources/test/fixtures/flake8_bandit/S108.py"),
&Settings {
flake8_bandit: flake8_bandit::settings::Settings {
hardcoded_tmp_directory: vec![
"/tmp".to_string(),
"/var/tmp".to_string(),
"/dev/shm".to_string(),
"/foo".to_string(),
],
},
..Settings::for_rule(CheckCode::S108)
},
)?;
insta::assert_yaml_snapshot!("S108_extend", checks);
Ok(())
}
}

View File

@@ -0,0 +1,77 @@
//! Settings for the `flake8-bandit` plugin.
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
fn default_tmp_dirs() -> Vec<String> {
["/tmp", "/var/tmp", "/dev/shm"]
.map(std::string::ToString::to_string)
.to_vec()
}
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "Flake8BanditOptions"
)]
pub struct Options {
#[option(
default = "[\"/tmp\", \"/var/tmp\", \"/dev/shm\"]",
value_type = "Vec<String>",
example = "hardcoded-tmp-directory = [\"/foo/bar\"]"
)]
/// A list of directories to consider temporary.
pub hardcoded_tmp_directory: Option<Vec<String>>,
#[option(
default = "[]",
value_type = "Vec<String>",
example = "extend-hardcoded-tmp-directory = [\"/foo/bar\"]"
)]
/// A list of directories to consider temporary, in addition to those
/// specified by `hardcoded-tmp-directory`.
pub hardcoded_tmp_directory_extend: Option<Vec<String>>,
}
#[derive(Debug, Hash)]
pub struct Settings {
pub hardcoded_tmp_directory: Vec<String>,
}
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
hardcoded_tmp_directory: options
.hardcoded_tmp_directory
.unwrap_or_else(default_tmp_dirs)
.into_iter()
.chain(
options
.hardcoded_tmp_directory_extend
.unwrap_or_default()
.into_iter(),
)
.collect(),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
hardcoded_tmp_directory: Some(settings.hardcoded_tmp_directory),
hardcoded_tmp_directory_extend: None,
}
}
}
impl Default for Settings {
fn default() -> Self {
Self {
hardcoded_tmp_directory: default_tmp_dirs(),
}
}
}

View File

@@ -0,0 +1,135 @@
---
source: src/flake8_bandit/mod.rs
expression: checks
---
- kind:
BadFilePermissions: 151
location:
row: 6
column: 24
end_location:
row: 6
column: 29
fix: ~
parent: ~
- kind:
BadFilePermissions: 7
location:
row: 7
column: 24
end_location:
row: 7
column: 27
fix: ~
parent: ~
- kind:
BadFilePermissions: 511
location:
row: 9
column: 24
end_location:
row: 9
column: 29
fix: ~
parent: ~
- kind:
BadFilePermissions: 504
location:
row: 10
column: 24
end_location:
row: 10
column: 29
fix: ~
parent: ~
- kind:
BadFilePermissions: 510
location:
row: 11
column: 24
end_location:
row: 11
column: 29
fix: ~
parent: ~
- kind:
BadFilePermissions: 511
location:
row: 13
column: 22
end_location:
row: 13
column: 25
fix: ~
parent: ~
- kind:
BadFilePermissions: 511
location:
row: 14
column: 23
end_location:
row: 14
column: 28
fix: ~
parent: ~
- kind:
BadFilePermissions: 511
location:
row: 15
column: 24
end_location:
row: 15
column: 29
fix: ~
parent: ~
- kind:
BadFilePermissions: 511
location:
row: 17
column: 18
end_location:
row: 17
column: 23
fix: ~
parent: ~
- kind:
BadFilePermissions: 511
location:
row: 18
column: 18
end_location:
row: 18
column: 36
fix: ~
parent: ~
- kind:
BadFilePermissions: 511
location:
row: 19
column: 18
end_location:
row: 19
column: 60
fix: ~
parent: ~
- kind:
BadFilePermissions: 8
location:
row: 20
column: 26
end_location:
row: 20
column: 38
fix: ~
parent: ~
- kind:
BadFilePermissions: 2
location:
row: 22
column: 24
end_location:
row: 22
column: 36
fix: ~
parent: ~

View File

@@ -0,0 +1,35 @@
---
source: src/flake8_bandit/mod.rs
expression: checks
---
- kind:
HardcodedTempFile: /tmp/abc
location:
row: 5
column: 10
end_location:
row: 5
column: 20
fix: ~
parent: ~
- kind:
HardcodedTempFile: /var/tmp/123
location:
row: 8
column: 10
end_location:
row: 8
column: 24
fix: ~
parent: ~
- kind:
HardcodedTempFile: /dev/shm/unit/test
location:
row: 11
column: 10
end_location:
row: 11
column: 30
fix: ~
parent: ~

View File

@@ -0,0 +1,45 @@
---
source: src/flake8_bandit/mod.rs
expression: checks
---
- kind:
HardcodedTempFile: /tmp/abc
location:
row: 5
column: 10
end_location:
row: 5
column: 20
fix: ~
parent: ~
- kind:
HardcodedTempFile: /var/tmp/123
location:
row: 8
column: 10
end_location:
row: 8
column: 24
fix: ~
parent: ~
- kind:
HardcodedTempFile: /dev/shm/unit/test
location:
row: 11
column: 10
end_location:
row: 11
column: 30
fix: ~
parent: ~
- kind:
HardcodedTempFile: /foo/bar
location:
row: 15
column: 10
end_location:
row: 15
column: 20
fix: ~
parent: ~

View File

@@ -1,3 +1,4 @@
use log::error;
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
use crate::ast::types::Range;
@@ -53,13 +54,16 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option
checker.style.line_ending(),
);
generator.unparse_stmt(&assertion_error(msg));
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
stmt.location,
stmt.end_location.unwrap(),
));
}
match generator.generate() {
Ok(content) => {
check.amend(Fix::replacement(
content,
stmt.location,
stmt.end_location.unwrap(),
));
}
Err(e) => error!("Failed to rewrite `assert False`: {e}"),
};
}
checker.add_check(check);
}

View File

@@ -1,4 +1,5 @@
use itertools::Itertools;
use log::error;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location};
@@ -64,12 +65,15 @@ fn duplicate_handler_exceptions<'a>(
} else {
generator.unparse_expr(&type_pattern(unique_elts), 0);
}
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
match generator.generate() {
Ok(content) => {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
Err(e) => error!("Failed to remove duplicate exceptions: {e}"),
}
}
checker.add_check(check);

View File

@@ -1,3 +1,4 @@
use log::error;
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location};
use crate::ast::types::Range;
@@ -52,12 +53,15 @@ pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
checker.style.line_ending(),
);
generator.unparse_expr(&attribute(obj, value), 0);
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
match generator.generate() {
Ok(content) => {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
Err(e) => error!("Failed to rewrite `getattr`: {e}"),
}
}
checker.add_check(check);

View File

@@ -1,3 +1,4 @@
use log::error;
use rustpython_ast::{Excepthandler, ExcepthandlerKind, ExprKind};
use crate::ast::types::Range;
@@ -29,12 +30,15 @@ pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[E
checker.style.line_ending(),
);
generator.unparse_expr(elt, 0);
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
type_.location,
type_.end_location.unwrap(),
));
match generator.generate() {
Ok(content) => {
check.amend(Fix::replacement(
content,
type_.location,
type_.end_location.unwrap(),
));
}
Err(e) => error!("Failed to remove redundant tuple: {e}"),
}
}
checker.add_check(check);

View File

@@ -1,7 +1,9 @@
use log::error;
use rustc_hash::FxHashSet;
use rustpython_ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::autofix::helpers::delete_stmt;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::registry::{Check, CheckCode, CheckKind};
@@ -27,10 +29,14 @@ pub fn no_unnecessary_pass(checker: &mut Checker, body: &[Stmt]) {
let mut check =
Check::new(CheckKind::NoUnnecessaryPass, Range::from_located(pass_stmt));
if checker.patch(&CheckCode::PIE790) {
check.amend(Fix::deletion(
pass_stmt.location,
pass_stmt.end_location.unwrap(),
));
match delete_stmt(pass_stmt, None, &[], checker.locator) {
Ok(fix) => {
check.amend(fix);
}
Err(e) => {
error!("Failed to delete `pass` statement: {}", e);
}
}
}
checker.add_check(check);
}

View File

@@ -13,10 +13,10 @@ expression: checks
content: ""
location:
row: 4
column: 4
column: 0
end_location:
row: 4
column: 8
row: 5
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
@@ -29,138 +29,138 @@ expression: checks
content: ""
location:
row: 9
column: 4
column: 0
end_location:
row: 9
column: 8
row: 10
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 16
row: 14
column: 4
end_location:
row: 16
row: 14
column: 8
fix:
content: ""
location:
row: 16
row: 14
column: 4
end_location:
row: 16
column: 8
row: 14
column: 10
parent: ~
- kind: NoUnnecessaryPass
location:
row: 23
row: 21
column: 4
end_location:
row: 23
row: 21
column: 8
fix:
content: ""
location:
row: 23
column: 4
row: 21
column: 0
end_location:
row: 23
column: 8
row: 22
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 30
row: 28
column: 4
end_location:
row: 30
row: 28
column: 8
fix:
content: ""
location:
row: 30
column: 4
row: 28
column: 0
end_location:
row: 30
column: 8
row: 29
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 37
row: 35
column: 4
end_location:
row: 37
row: 35
column: 8
fix:
content: ""
location:
row: 37
column: 4
row: 35
column: 0
end_location:
row: 37
column: 8
row: 36
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 45
row: 42
column: 4
end_location:
row: 45
row: 42
column: 8
fix:
content: ""
location:
row: 45
column: 4
row: 42
column: 0
end_location:
row: 45
column: 8
row: 43
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 53
row: 50
column: 4
end_location:
row: 53
row: 50
column: 8
fix:
content: ""
location:
row: 53
column: 4
row: 50
column: 0
end_location:
row: 53
column: 8
row: 51
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 60
row: 58
column: 4
end_location:
row: 60
row: 58
column: 8
fix:
content: ""
location:
row: 60
column: 4
row: 58
column: 0
end_location:
row: 60
column: 8
row: 59
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 69
row: 65
column: 4
end_location:
row: 69
row: 65
column: 8
fix:
content: ""
location:
row: 69
column: 4
row: 65
column: 0
end_location:
row: 69
column: 8
row: 66
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
@@ -173,42 +173,42 @@ expression: checks
content: ""
location:
row: 74
column: 4
column: 0
end_location:
row: 74
column: 8
row: 75
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 78
row: 79
column: 4
end_location:
row: 78
row: 79
column: 8
fix:
content: ""
location:
row: 78
column: 4
row: 79
column: 0
end_location:
row: 78
column: 8
row: 80
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 82
row: 83
column: 4
end_location:
row: 82
row: 83
column: 8
fix:
content: ""
location:
row: 82
column: 4
row: 83
column: 0
end_location:
row: 82
column: 8
row: 84
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
@@ -221,25 +221,41 @@ expression: checks
content: ""
location:
row: 87
column: 4
column: 0
end_location:
row: 87
column: 8
row: 88
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 91
row: 92
column: 4
end_location:
row: 91
row: 92
column: 8
fix:
content: ""
location:
row: 91
column: 4
row: 92
column: 0
end_location:
row: 91
column: 8
row: 93
column: 0
parent: ~
- kind: NoUnnecessaryPass
location:
row: 96
column: 4
end_location:
row: 96
column: 8
fix:
content: ""
location:
row: 96
column: 0
end_location:
row: 97
column: 0
parent: ~

View File

@@ -1,6 +1,7 @@
use rustpython_ast::{Expr, Keyword};
use super::helpers::{is_empty_or_null_string, is_pytest_fail, SimpleCallArgs};
use super::helpers::{is_empty_or_null_string, is_pytest_fail};
use crate::ast::helpers::SimpleCallArgs;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::{Check, CheckKind};

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt, StmtKind};
use rustpython_ast::{Arguments, Expr, ExprKind, Location, Stmt, StmtKind};
use super::helpers::{
get_mark_decorators, get_mark_name, is_abstractmethod_decorator, is_pytest_fixture,
@@ -171,14 +171,25 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo
}
if checker.settings.enabled.contains(&CheckCode::PT022) {
if let Some(last_statement) = body.last() {
if let StmtKind::Expr { value, .. } = &last_statement.node {
if let Some(stmt) = body.last() {
if let StmtKind::Expr { value, .. } = &stmt.node {
if let ExprKind::Yield { .. } = value.node {
if visitor.yield_statements.len() == 1 {
checker.add_check(Check::new(
let mut check = Check::new(
CheckKind::UselessYieldFixture(func_name.to_string()),
Range::from_located(last_statement),
));
Range::from_located(stmt),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
"return".to_string(),
stmt.location,
Location::new(
stmt.location.row(),
stmt.location.column() + "yield".len(),
),
));
}
checker.add_check(check);
}
}
}

View File

@@ -1,5 +1,4 @@
use num_traits::identities::Zero;
use rustc_hash::FxHashMap;
use rustpython_ast::{Constant, Expr, ExprKind, Keyword};
use crate::ast::helpers::{collect_call_paths, compose_call_path, match_module_member};
@@ -7,50 +6,6 @@ use crate::checkers::ast::Checker;
const ITERABLE_INITIALIZERS: &[&str] = &["dict", "frozenset", "list", "tuple", "set"];
#[derive(Default)]
pub struct SimpleCallArgs<'a> {
pub args: Vec<&'a Expr>,
pub kwargs: FxHashMap<&'a str, &'a Expr>,
}
impl<'a> SimpleCallArgs<'a> {
pub fn new(args: &'a Vec<Expr>, keywords: &'a Vec<Keyword>) -> Self {
let mut result = SimpleCallArgs::default();
for arg in args {
match &arg.node {
ExprKind::Starred { .. } => {
break;
}
_ => {
result.args.push(arg);
}
}
}
for keyword in keywords {
if let Some(arg) = &keyword.node.arg {
result.kwargs.insert(arg, &keyword.node.value);
}
}
result
}
pub fn get_argument(&self, name: &'a str, position: Option<usize>) -> Option<&'a Expr> {
let kwarg = self.kwargs.get(name);
if let Some(kwarg) = kwarg {
return Some(kwarg);
}
if let Some(position) = position {
if position < self.args.len() {
return Some(self.args[position]);
}
}
None
}
}
pub fn get_mark_decorators(decorators: &[Expr]) -> Vec<&Expr> {
decorators
.iter()

View File

@@ -1,11 +1,14 @@
use rustpython_ast::{Constant, Expr, ExprKind};
use log::error;
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind};
use super::helpers::is_pytest_parametrize;
use crate::ast::helpers::create_expr;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::flake8_pytest_style::types;
use crate::registry::{Check, CheckCode, CheckKind};
use crate::source_code_generator::SourceCodeGenerator;
fn get_parametrize_decorator<'a>(checker: &Checker, decorators: &'a [Expr]) -> Option<&'a Expr> {
decorators
@@ -13,6 +16,59 @@ fn get_parametrize_decorator<'a>(checker: &Checker, decorators: &'a [Expr]) -> O
.find(|decorator| is_pytest_parametrize(decorator, checker))
}
fn elts_to_csv(elts: &[Expr], checker: &Checker) -> Option<String> {
let all_literals = elts.iter().all(|e| {
matches!(
e.node,
ExprKind::Constant {
value: Constant::Str(_),
..
}
)
});
if !all_literals {
return None;
}
let mut generator = SourceCodeGenerator::new(
checker.style.indentation(),
checker.style.quote(),
checker.style.line_ending(),
);
generator.unparse_expr(
&create_expr(ExprKind::Constant {
value: Constant::Str(elts.iter().fold(String::new(), |mut acc, elt| {
if let ExprKind::Constant {
value: Constant::Str(ref s),
..
} = elt.node
{
if !acc.is_empty() {
acc.push(',');
}
acc.push_str(s);
}
acc
})),
kind: None,
}),
0,
);
match generator.generate() {
Ok(s) => Some(s),
Err(e) => {
error!(
"Failed to generate CSV string from sequence of names: {}",
e
);
None
}
}
}
/// PT006
fn check_names(checker: &mut Checker, expr: &Expr) {
let names_type = checker.settings.flake8_pytest_style.parametrize_names_type;
@@ -22,7 +78,19 @@ fn check_names(checker: &mut Checker, expr: &Expr) {
value: Constant::Str(string),
..
} => {
let names = string.split(',').collect::<Vec<&str>>();
// Match the following pytest code:
// [x.strip() for x in argnames.split(",") if x.strip()]
let names = string
.split(',')
.filter_map(|s| {
let trimmed = s.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed)
}
})
.collect::<Vec<&str>>();
if names.len() > 1 {
match names_type {
@@ -32,11 +100,39 @@ fn check_names(checker: &mut Checker, expr: &Expr) {
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
strings_to_python_tuple(&names),
expr.location,
expr.end_location.unwrap(),
));
let mut generator = SourceCodeGenerator::new(
checker.style.indentation(),
checker.style.quote(),
checker.style.line_ending(),
);
generator.unparse_expr(
&create_expr(ExprKind::Tuple {
elts: names
.iter()
.map(|&name| {
create_expr(ExprKind::Constant {
value: Constant::Str(name.to_string()),
kind: None,
})
})
.collect(),
ctx: ExprContext::Load,
}),
1,
);
match generator.generate() {
Ok(content) => {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
Err(e) => error!(
"Failed to fix wrong name(s) type in \
`@pytest.mark.parametrize`: {e}"
),
};
}
checker.add_check(check);
}
@@ -46,11 +142,39 @@ fn check_names(checker: &mut Checker, expr: &Expr) {
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
strings_to_python_list(&names),
expr.location,
expr.end_location.unwrap(),
));
let mut generator = SourceCodeGenerator::new(
checker.style.indentation(),
checker.style.quote(),
checker.style.line_ending(),
);
generator.unparse_expr(
&create_expr(ExprKind::List {
elts: names
.iter()
.map(|&name| {
create_expr(ExprKind::Constant {
value: Constant::Str(name.to_string()),
kind: None,
})
})
.collect(),
ctx: ExprContext::Load,
}),
0,
);
match generator.generate() {
Ok(content) => {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
Err(e) => error!(
"Failed to fix wrong name(s) type in \
`@pytest.mark.parametrize`: {e}"
),
};
}
checker.add_check(check);
}
@@ -63,11 +187,60 @@ fn check_names(checker: &mut Checker, expr: &Expr) {
if let Some(first) = elts.first() {
handle_single_name(checker, expr, first);
}
} else if names_type != types::ParametrizeNameType::Tuple {
checker.add_check(Check::new(
CheckKind::ParametrizeNamesWrongType(names_type),
Range::from_located(expr),
));
} else {
match names_type {
types::ParametrizeNameType::Tuple => {}
types::ParametrizeNameType::List => {
let mut check = Check::new(
CheckKind::ParametrizeNamesWrongType(names_type),
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
let mut generator = SourceCodeGenerator::new(
checker.style.indentation(),
checker.style.quote(),
checker.style.line_ending(),
);
generator.unparse_expr(
&create_expr(ExprKind::List {
elts: elts.clone(),
ctx: ExprContext::Load,
}),
0,
);
match generator.generate() {
Ok(content) => {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
Err(e) => error!(
"Failed to fix wrong name(s) type in \
`@pytest.mark.parametrize`: {e}"
),
};
}
checker.add_check(check);
}
types::ParametrizeNameType::CSV => {
let mut check = Check::new(
CheckKind::ParametrizeNamesWrongType(names_type),
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
if let Some(content) = elts_to_csv(elts, checker) {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
}
checker.add_check(check);
}
}
};
}
ExprKind::List { elts, .. } => {
@@ -75,11 +248,60 @@ fn check_names(checker: &mut Checker, expr: &Expr) {
if let Some(first) = elts.first() {
handle_single_name(checker, expr, first);
}
} else if names_type != types::ParametrizeNameType::List {
checker.add_check(Check::new(
CheckKind::ParametrizeNamesWrongType(names_type),
Range::from_located(expr),
));
} else {
match names_type {
types::ParametrizeNameType::List => {}
types::ParametrizeNameType::Tuple => {
let mut check = Check::new(
CheckKind::ParametrizeNamesWrongType(names_type),
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
let mut generator = SourceCodeGenerator::new(
checker.style.indentation(),
checker.style.quote(),
checker.style.line_ending(),
);
generator.unparse_expr(
&create_expr(ExprKind::Tuple {
elts: elts.clone(),
ctx: ExprContext::Load,
}),
1, // so tuple is generated with parentheses
);
match generator.generate() {
Ok(content) => {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
Err(e) => error!(
"Failed to fix wrong name(s) type in \
`@pytest.mark.parametrize`: {e}"
),
};
}
checker.add_check(check);
}
types::ParametrizeNameType::CSV => {
let mut check = Check::new(
CheckKind::ParametrizeNamesWrongType(names_type),
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
if let Some(content) = elts_to_csv(elts, checker) {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
}
checker.add_check(check);
}
}
};
}
_ => {}
@@ -123,42 +345,28 @@ fn handle_single_name(checker: &mut Checker, expr: &Expr, value: &Expr) {
CheckKind::ParametrizeNamesWrongType(types::ParametrizeNameType::CSV),
Range::from_located(expr),
);
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &value.node
{
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
format!("\"{string}\""),
expr.location,
expr.end_location.unwrap(),
));
}
if checker.patch(check.kind.code()) {
let mut generator = SourceCodeGenerator::new(
checker.style.indentation(),
checker.style.quote(),
checker.style.line_ending(),
);
generator.unparse_expr(&create_expr(value.node.clone()), 0);
match generator.generate() {
Ok(content) => {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
Err(e) => error!("Failed to fix wrong name(s) type in `@pytest.mark.parametrize`: {e}"),
};
}
checker.add_check(check);
}
fn strings_to_python_tuple(strings: &[&str]) -> String {
let result = strings
.iter()
.map(|s| format!("\"{s}\""))
.collect::<Vec<String>>()
.join(", ");
format!("({result})")
}
fn strings_to_python_list(strings: &[&str]) -> String {
let result = strings
.iter()
.map(|s| format!("\"{s}\""))
.collect::<Vec<String>>()
.join(", ");
format!("[{result}]")
}
fn handle_value_rows(
checker: &mut Checker,
elts: &[Expr],
@@ -193,13 +401,14 @@ pub fn parametrize(checker: &mut Checker, decorators: &[Expr]) {
if let Some(decorator) = decorator {
if let ExprKind::Call { args, .. } = &decorator.node {
if checker.settings.enabled.contains(&CheckCode::PT006) {
let first = args.first().unwrap();
check_names(checker, first);
if let Some(arg) = args.get(0) {
check_names(checker, arg);
}
}
if checker.settings.enabled.contains(&CheckCode::PT007) {
let second = args.get(1).unwrap();
check_values(checker, second);
if let Some(arg) = args.get(1) {
check_values(checker, arg);
}
}
}
}

View File

@@ -1,8 +1,7 @@
use rustc_hash::FxHashSet;
use rustpython_ast::{Expr, ExprKind, Keyword};
use super::helpers::SimpleCallArgs;
use crate::ast::helpers::{collect_arg_names, compose_call_path};
use crate::ast::helpers::{collect_arg_names, compose_call_path, SimpleCallArgs};
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;

View File

@@ -5,55 +5,89 @@ expression: checks
- kind:
ParametrizeNamesWrongType: csv
location:
row: 14
row: 24
column: 25
end_location:
row: 14
row: 24
column: 45
fix: ~
fix:
content: "\"param1,param2\""
location:
row: 24
column: 25
end_location:
row: 24
column: 45
parent: ~
- kind:
ParametrizeNamesWrongType: csv
location:
row: 19
row: 29
column: 25
end_location:
row: 19
row: 29
column: 36
fix:
content: "\"param1\""
location:
row: 19
row: 29
column: 25
end_location:
row: 19
row: 29
column: 36
parent: ~
- kind:
ParametrizeNamesWrongType: csv
location:
row: 24
row: 34
column: 25
end_location:
row: 24
row: 34
column: 45
fix:
content: "\"param1,param2\""
location:
row: 34
column: 25
end_location:
row: 34
column: 45
parent: ~
- kind:
ParametrizeNamesWrongType: csv
location:
row: 39
column: 25
end_location:
row: 39
column: 35
fix:
content: "\"param1\""
location:
row: 39
column: 25
end_location:
row: 39
column: 35
parent: ~
- kind:
ParametrizeNamesWrongType: csv
location:
row: 44
column: 25
end_location:
row: 44
column: 50
fix: ~
parent: ~
- kind:
ParametrizeNamesWrongType: csv
location:
row: 29
row: 49
column: 25
end_location:
row: 29
column: 35
fix:
content: "\"param1\""
location:
row: 29
column: 25
end_location:
row: 29
column: 35
row: 49
column: 46
fix: ~
parent: ~

View File

@@ -20,47 +20,122 @@ expression: checks
column: 40
parent: ~
- kind:
ParametrizeNamesWrongType: csv
ParametrizeNamesWrongType: tuple
location:
row: 14
column: 25
end_location:
row: 14
column: 56
fix:
content: "(\"param1\", \"param2\")"
location:
row: 14
column: 25
end_location:
row: 14
column: 56
parent: ~
- kind:
ParametrizeNamesWrongType: tuple
location:
row: 19
column: 25
end_location:
row: 19
column: 36
column: 40
fix:
content: "\"param1\""
content: "(\"param1\", \"param2\")"
location:
row: 19
column: 25
end_location:
row: 19
column: 40
parent: ~
- kind:
ParametrizeNamesWrongType: csv
location:
row: 29
column: 25
end_location:
row: 29
column: 36
fix:
content: "\"param1\""
location:
row: 29
column: 25
end_location:
row: 29
column: 36
parent: ~
- kind:
ParametrizeNamesWrongType: tuple
location:
row: 24
row: 34
column: 25
end_location:
row: 24
row: 34
column: 45
fix: ~
fix:
content: "(\"param1\", \"param2\")"
location:
row: 34
column: 25
end_location:
row: 34
column: 45
parent: ~
- kind:
ParametrizeNamesWrongType: csv
location:
row: 29
row: 39
column: 25
end_location:
row: 29
row: 39
column: 35
fix:
content: "\"param1\""
location:
row: 29
row: 39
column: 25
end_location:
row: 29
row: 39
column: 35
parent: ~
- kind:
ParametrizeNamesWrongType: tuple
location:
row: 44
column: 25
end_location:
row: 44
column: 50
fix:
content: "(some_expr, another_expr)"
location:
row: 44
column: 25
end_location:
row: 44
column: 50
parent: ~
- kind:
ParametrizeNamesWrongType: tuple
location:
row: 49
column: 25
end_location:
row: 49
column: 46
fix:
content: "(some_expr, \"param2\")"
location:
row: 49
column: 25
end_location:
row: 49
column: 46
parent: ~

View File

@@ -26,41 +26,82 @@ expression: checks
column: 25
end_location:
row: 14
column: 45
fix: ~
column: 56
fix:
content: "[\"param1\", \"param2\"]"
location:
row: 14
column: 25
end_location:
row: 14
column: 56
parent: ~
- kind:
ParametrizeNamesWrongType: csv
ParametrizeNamesWrongType: list
location:
row: 19
column: 25
end_location:
row: 19
column: 36
column: 40
fix:
content: "\"param1\""
content: "[\"param1\", \"param2\"]"
location:
row: 19
column: 25
end_location:
row: 19
column: 40
parent: ~
- kind:
ParametrizeNamesWrongType: list
location:
row: 24
column: 25
end_location:
row: 24
column: 45
fix:
content: "[\"param1\", \"param2\"]"
location:
row: 24
column: 25
end_location:
row: 24
column: 45
parent: ~
- kind:
ParametrizeNamesWrongType: csv
location:
row: 29
column: 25
end_location:
row: 29
column: 36
fix:
content: "\"param1\""
location:
row: 29
column: 25
end_location:
row: 29
column: 36
parent: ~
- kind:
ParametrizeNamesWrongType: csv
location:
row: 29
row: 39
column: 25
end_location:
row: 29
row: 39
column: 35
fix:
content: "\"param1\""
location:
row: 29
row: 39
column: 25
end_location:
row: 29
row: 39
column: 35
parent: ~

View File

@@ -10,6 +10,13 @@ expression: checks
end_location:
row: 17
column: 18
fix: ~
fix:
content: return
location:
row: 17
column: 4
end_location:
row: 17
column: 9
parent: ~

View File

@@ -12,7 +12,15 @@ mod tests {
use crate::registry::CheckCode;
use crate::settings;
#[test_case(CheckCode::SIM102, Path::new("SIM102.py"); "SIM102")]
#[test_case(CheckCode::SIM105, Path::new("SIM105.py"); "SIM105")]
#[test_case(CheckCode::SIM107, Path::new("SIM107.py"); "SIM107")]
#[test_case(CheckCode::SIM110, Path::new("SIM110.py"); "SIM110")]
#[test_case(CheckCode::SIM111, Path::new("SIM111.py"); "SIM111")]
#[test_case(CheckCode::SIM117, Path::new("SIM117.py"); "SIM117")]
#[test_case(CheckCode::SIM118, Path::new("SIM118.py"); "SIM118")]
#[test_case(CheckCode::SIM220, Path::new("SIM220.py"); "SIM220")]
#[test_case(CheckCode::SIM221, Path::new("SIM221.py"); "SIM221")]
#[test_case(CheckCode::SIM222, Path::new("SIM222.py"); "SIM222")]
#[test_case(CheckCode::SIM223, Path::new("SIM223.py"); "SIM223")]
#[test_case(CheckCode::SIM300, Path::new("SIM300.py"); "SIM300")]

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