Compare commits

...

25 Commits

Author SHA1 Message Date
Charlie Marsh
6a6f4651aa Bump version to 0.0.126 2022-11-17 17:19:19 -05:00
Charlie Marsh
66ae4db6cd Ignore globals when checking local variable names (#800) 2022-11-17 17:19:01 -05:00
Charlie Marsh
801c76037f Except BaseException from N818 checks (#798) 2022-11-17 15:04:42 -05:00
Charlie Marsh
ab825eb28d Fix D202 to remove line after docstring (#797) 2022-11-17 15:01:58 -05:00
Charlie Marsh
826ef7da67 Trigger N818 when parent ends in Error or Exception (#796) 2022-11-17 14:51:40 -05:00
Charlie Marsh
72f5393d3a Add flake8-tidy-imports to cache key 2022-11-17 14:46:45 -05:00
Charlie Marsh
6602f7f489 Trim dedented sections for arg detection (#793) 2022-11-17 12:55:55 -05:00
Charlie Marsh
aafddae644 Bump version to 0.0.125 2022-11-17 12:07:05 -05:00
Charlie Marsh
749df87de0 Tweak presentation of null-ls and efm docs (#791) 2022-11-17 12:04:11 -05:00
Eddie Bergman
d67db33f22 docs(integrations): neovim null-ls integration (#782) 2022-11-17 11:55:08 -05:00
Charlie Marsh
f0a54716e5 Implement flake8-tidy-imports (#789) 2022-11-17 11:44:06 -05:00
Harutaka Kawamura
c59e1ff0b5 Implement auto-fix for E711 and E712 (#784) 2022-11-17 11:43:44 -05:00
Jonathan Plasse
ecf858cf16 Add the tools identifier in the TOC (#779) 2022-11-17 11:34:32 -05:00
Jonathan Plasse
8063aee006 Remove unnecessary abspath rule (U002) (#781) 2022-11-17 11:29:42 -05:00
Anders Kaseorg
f1fee5d240 Propagate exit code through Python __main__ wrapper (#776) 2022-11-16 16:16:58 -05:00
Anders Kaseorg
d3155560df Fix find_and_parse_pyproject_toml test for #772 (#774) 2022-11-16 13:47:25 -05:00
Charlie Marsh
90bfc4ec4d Bump version to 0.0.124 2022-11-16 12:25:24 -05:00
Charlie Marsh
b04a6a3f7c Support arbitrary expression paths for class and static decorators (#772) 2022-11-16 12:24:46 -05:00
Charlie Marsh
8ec14e7ee2 Bump version to 0.0.123 2022-11-16 12:06:01 -05:00
Charlie Marsh
17c5cd7c42 Fix off-by-one in noqa map detection (#771)
Fix off-by-one in noqa
2022-11-16 12:00:10 -05:00
Charlie Marsh
7d8360a1de Change all &Option<> to Option<&> (#768) 2022-11-16 09:40:01 -05:00
Harutaka Kawamura
910ee523dd Fix E731 (#766) 2022-11-16 09:17:14 -05:00
Charlie Marsh
72e35a535e Run cargo fmt 2022-11-16 09:15:22 -05:00
Charlie Marsh
b4e1563517 Avoid allocations for binding values (#764) 2022-11-16 08:55:35 -05:00
Charlie Marsh
5717cc97d7 Add references to Flake8 licenses 2022-11-15 23:07:41 -05:00
74 changed files with 1891 additions and 646 deletions

View File

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

6
Cargo.lock generated
View File

@@ -930,7 +930,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.122-dev.0"
version = "0.0.126-dev.0"
dependencies = [
"anyhow",
"clap 4.0.22",
@@ -2238,7 +2238,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.122"
version = "0.0.126"
dependencies = [
"anyhow",
"assert_cmd",
@@ -2287,7 +2287,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.122"
version = "0.0.126"
dependencies = [
"anyhow",
"clap 4.0.22",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.122"
version = "0.0.126"
edition = "2021"
[lib]

400
LICENSE
View File

@@ -19,3 +19,403 @@ 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-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-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.
"""
- 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.
"""
- 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.
"""

123
README.md
View File

@@ -44,31 +44,32 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
## Table of Contents
1. [Installation and Usage](#installation-and-usage)
2. [Configuration](#configuration)
3. [Supported Rules](#supported-rules)
1. [Pyflakes](#pyflakes)
2. [pycodestyle](#pycodestyle)
3. [isort](#isort)
4. [pydocstyle](#pydocstyle)
5. [pyupgrade](#pyupgrade)
6. [pep8-naming](#pep8-naming)
7. [flake8-bandit](#flake8-bandit)
8. [flake8-comprehensions](#flake8-comprehensions)
9. [flake8-bugbear](#flake8-bugbear)
10. [flake8-builtins](#flake8-builtins)
11. [flake8-print](#flake8-print)
12. [flake8-quotes](#flake8-quotes)
13. [flake8-annotations](#flake8-annotations)
14. [flake8-2020](#flake8-2020)
15. [Ruff-specific rules](#ruff-specific-rules)
16. [Meta rules](#meta-rules)
5. [Editor Integrations](#editor-integrations)
6. [FAQ](#faq)
7. [Development](#development)
8. [Releases](#releases)
9. [Benchmarks](#benchmarks)
10. [License](#license)
11. [Contributing](#contributing)
1. [Configuration](#configuration)
1. [Supported Rules](#supported-rules)
1. [Pyflakes (F)](#pyflakes)
1. [pycodestyle (E)](#pycodestyle)
1. [isort (I)](#isort)
1. [pydocstyle (D)](#pydocstyle)
1. [pyupgrade (U)](#pyupgrade)
1. [pep8-naming (N)](#pep8-naming)
1. [flake8-bandit (S)](#flake8-bandit)
1. [flake8-comprehensions (C)](#flake8-comprehensions)
1. [flake8-bugbear (B)](#flake8-bugbear)
1. [flake8-builtins (A)](#flake8-builtins)
1. [flake8-tidy-imports (I25)](#flake8-tidy-imports)
1. [flake8-print (T)](#flake8-print)
1. [flake8-quotes (Q)](#flake8-quotes)
1. [flake8-annotations (ANN)](#flake8-annotations)
1. [flake8-2020 (YTT)](#flake8-2020)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules)
1. [Meta rules (M)](#meta-rules)
1. [Editor Integrations](#editor-integrations)
1. [FAQ](#faq)
1. [Development](#development)
1. [Releases](#releases)
1. [Benchmarks](#benchmarks)
1. [License](#license)
1. [Contributing](#contributing)
## Installation and Usage
@@ -356,8 +357,8 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
| ---- | ---- | ------- | --- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | |
| E501 | LineTooLong | Line too long (89 > 88 characters) | |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` | |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | 🛠 |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` | 🛠 |
| E713 | NotInTest | Test for membership should be `not in` | |
| E714 | NotIsTest | Test for object identity should be `is not` | |
| E721 | TypeComparison | Do not compare types, use `isinstance()` | |
@@ -437,7 +438,6 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | 🛠 |
| U002 | UnnecessaryAbspath | `abspath(__file__)` is unnecessary in Python 3.9 and later | 🛠 |
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` | 🛠 |
| U004 | UselessObjectInheritance | Class `...` inherits from object | 🛠 |
| U005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` instead | 🛠 |
@@ -549,6 +549,14 @@ For more, see [flake8-builtins](https://pypi.org/project/flake8-builtins/2.0.1/)
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | |
| A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin | |
### flake8-tidy-imports
For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/4.8.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| I252 | BannedRelativeImport | Relative imports are banned | |
### flake8-print
For more, see [flake8-print](https://pypi.org/project/flake8-print/5.0.0/) on PyPI.
@@ -643,8 +651,57 @@ Ruff should then appear as a runnable action:
Ruff is available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright) extension
for coc.nvim.
Ruff can also be integrated via [efm](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#efm)
in just a [few lines](https://github.com/JafarAbdi/myconfigs/blob/6f0b6b2450e92ec8fc50422928cd22005b919110/efm-langserver/config.yaml#L14-L20).
<details>
<summary>Ruff can also be integrated via <a href="https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#efm"><code>efm</code></a> in just a <a href="https://github.com/JafarAbdi/myconfigs/blob/6f0b6b2450e92ec8fc50422928cd22005b919110/efm-langserver/config.yaml#L14-L20">few lines</a>.</summary>
<br>
```yaml
tools:
python-ruff: &python-ruff
lint-command: 'ruff --config ~/myconfigs/linters/ruff.toml --quiet ${INPUT}'
lint-stdin: true
lint-formats:
- '%f:%l:%c: %m'
format-command: 'ruff --stdin-filename ${INPUT} --config ~/myconfigs/linters/ruff.toml --fix --exit-zero --quiet -'
format-stdin: true
```
</details>
<details>
<summary>For neovim users using <a href="https://github.com/jose-elias-alvarez/null-ls.nvim"><code>null-ls</code></a>, Ruff is already <a href="https://github.com/jose-elias-alvarez/null-ls.nvim">integrated</a>.</summary>
<br>
```lua
local null_ls = require("null-ls")
local methods = require("null-ls.methods")
local helpers = require("null-ls.helpers")
local function ruff_fix()
return helpers.make_builtin({
name = "ruff",
meta = {
url = "https://github.com/charliermarsh/ruff/",
description = "An extremely fast Python linter, written in Rust.",
},
method = methods.internal.FORMATTING,
filetypes = { "python" },
generator_opts = {
command = "ruff",
args = { "--fix", "-e", "-n", "--stdin-filename", "$FILENAME", "-" },
to_stdin = true
},
factory = helpers.formatter_factory
})
end
null_ls.setup({
sources = {
ruff_fix(),
null_ls.builtins.diagnostics.ruff,
}
})
```
</details>
### Language Server Protocol (Unofficial)
@@ -705,6 +762,7 @@ including:
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
@@ -712,7 +770,7 @@ including:
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (25/32)
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (15/34)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/33)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
@@ -730,6 +788,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
@@ -739,7 +798,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/)
Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (15/34).
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/33).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.

View File

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

View File

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

View File

@@ -3,9 +3,10 @@ use std::collections::{BTreeSet, HashMap};
use anyhow::Result;
use ruff::checks_gen::CheckCodePrefix;
use ruff::flake8_quotes::settings::Quote;
use ruff::flake8_tidy_imports::settings::Strictness;
use ruff::settings::options::Options;
use ruff::settings::pyproject::Pyproject;
use ruff::{flake8_annotations, flake8_bugbear, flake8_quotes, pep8_naming};
use ruff::{flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, pep8_naming};
use crate::plugin::Plugin;
use crate::{parser, plugin};
@@ -71,6 +72,7 @@ pub fn convert(
let mut flake8_annotations: flake8_annotations::settings::Options = Default::default();
let mut flake8_bugbear: flake8_bugbear::settings::Options = Default::default();
let mut flake8_quotes: flake8_quotes::settings::Options = Default::default();
let mut flake8_tidy_imports: flake8_tidy_imports::settings::Options = Default::default();
let mut pep8_naming: pep8_naming::settings::Options = Default::default();
for (key, value) in flake8 {
if let Some(value) = value {
@@ -172,6 +174,14 @@ pub fn convert(
pep8_naming.staticmethod_decorators =
Some(parser::parse_strings(value.as_ref()));
}
// flake8-tidy-imports
"ban-relative-imports" | "ban_relative_imports" => match value.trim() {
"true" => flake8_tidy_imports.ban_relative_imports = Some(Strictness::All),
"parents" => {
flake8_tidy_imports.ban_relative_imports = Some(Strictness::Parents)
}
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
// flake8-docstrings
"docstring-convention" => {
// No-op (handled above).
@@ -194,6 +204,9 @@ pub fn convert(
if flake8_quotes != Default::default() {
options.flake8_quotes = Some(flake8_quotes);
}
if flake8_tidy_imports != Default::default() {
options.flake8_tidy_imports = Some(flake8_tidy_imports);
}
if pep8_naming != Default::default() {
options.pep8_naming = Some(pep8_naming);
}
@@ -238,6 +251,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
});
@@ -272,6 +286,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
});
@@ -306,6 +321,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
});
@@ -340,6 +356,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
});
@@ -379,6 +396,7 @@ mod tests {
docstring_quotes: None,
avoid_escape: None,
}),
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
});
@@ -451,6 +469,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
});
@@ -491,6 +510,7 @@ mod tests {
docstring_quotes: None,
avoid_escape: None,
}),
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
});

View File

@@ -11,6 +11,7 @@ pub enum Plugin {
Flake8Builtins,
Flake8Comprehensions,
Flake8Docstrings,
Flake8TidyImports,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
@@ -28,6 +29,7 @@ impl FromStr for Plugin {
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
@@ -46,6 +48,7 @@ impl Plugin {
Plugin::Flake8Builtins => CheckCodePrefix::A,
Plugin::Flake8Comprehensions => CheckCodePrefix::C,
Plugin::Flake8Docstrings => CheckCodePrefix::D,
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
Plugin::Flake8Print => CheckCodePrefix::T,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
@@ -76,6 +79,7 @@ impl Plugin {
// Default to PEP8.
DocstringConvention::PEP8.select()
}
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
Plugin::Flake8Print => vec![CheckCodePrefix::T],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
@@ -315,6 +319,13 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
"allow-star-arg-any" | "allow_star_arg_any" => {
plugins.insert(Plugin::Flake8Annotations);
}
// flake8-tidy-imports
"ban-relative-imports" | "ban_relative_imports" => {
plugins.insert(Plugin::Flake8TidyImports);
}
"banned-modules" | "banned_modules" => {
plugins.insert(Plugin::Flake8TidyImports);
}
// pep8-naming
"ignore-names" | "ignore_names" => {
plugins.insert(Plugin::PEP8Naming);
@@ -342,6 +353,7 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
Plugin::Flake8Builtins,
Plugin::Flake8Comprehensions,
Plugin::Flake8Docstrings,
Plugin::Flake8TidyImports,
Plugin::Flake8Print,
Plugin::Flake8Quotes,
Plugin::Flake8Annotations,

View File

@@ -532,3 +532,37 @@ class Blah: # noqa: D203,D213
expect(os.path.normcase(__file__ if __file__[-1] != 'c' else __file__[:-1]),
'D100: Missing docstring in public module')
@expect('D201: No blank lines allowed before function docstring (found 1)')
@expect('D213: Multi-line docstring summary should start at the second line')
def multiline_leading_space():
"""Leading space.
More content.
"""
@expect('D202: No blank lines allowed after function docstring (found 1)')
@expect('D213: Multi-line docstring summary should start at the second line')
def multiline_trailing_space():
"""Leading space.
More content.
"""
pass
@expect('D201: No blank lines allowed before function docstring (found 1)')
@expect('D202: No blank lines allowed after function docstring (found 1)')
@expect('D213: Multi-line docstring summary should start at the second line')
def multiline_trailing_and_leading_space():
"""Trailing and leading space.
More content.
"""
pass

View File

@@ -5,7 +5,7 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
"""
_ = """Lorem ipsum dolor sit amet.
_ = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533

View File

@@ -23,6 +23,9 @@ if None != res[1]:
if None == res[1]:
pass
if x == None != None:
pass
#: Okay
if x not in y:
pass

View File

@@ -22,6 +22,9 @@ var = 1 if cond == True else -1 if cond == False else cond
if (True) == TrueElement or x == TrueElement:
pass
if res == True != False:
pass
#: Okay
if x not in y:
pass

View File

@@ -8,14 +8,14 @@ while False:
f = object()
#: E731
f.method = lambda: "Method"
f = {}
#: E731
f["a"] = lambda x: x ** 2
f = []
f.append(lambda x: x ** 2)
f = g = lambda x: x ** 2
lambda: "no-op"

12
resources/test/fixtures/I252.py vendored Normal file
View File

@@ -0,0 +1,12 @@
from . import sibling
from .sibling import example
from .. import parent
from ..parent import example
from ... import grandparent
from ...grandparent import example
import other
import other.example
from other import example

View File

@@ -1,5 +1,7 @@
from abc import ABCMeta
import pydantic
class Class:
def bad_method(this):
@@ -21,6 +23,14 @@ class Class:
def static_method(x):
return x
@pydantic.validator
def lower(cls, my_field: str) -> str:
pass
@pydantic.validator("my_field")
def lower(cls, my_field: str) -> str:
pass
def __init__(self):
...

View File

@@ -1,8 +1,12 @@
import collections
from collections import namedtuple
GLOBAL: str = "foo"
def f():
global GLOBAL
GLOBAL = "bar"
lower = 0
Camel = 0
CONSTANT = 0

View File

@@ -8,3 +8,11 @@ class AnotherError(Exception):
class C(Exception):
pass
class D(BaseException):
pass
class E(AnotherError):
pass

View File

@@ -1,15 +0,0 @@
from os.path import abspath
x = abspath(__file__)
import os
y = os.path.abspath(__file__)
from os import path
z = path.abspath(__file__)

View File

@@ -33,7 +33,11 @@ ignore-names = [
]
classmethod-decorators = [
"classmethod",
"pydantic.validator",
]
staticmethod-decorators = [
"staticmethod",
]
[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "parents"

View File

@@ -4,4 +4,4 @@ import sysconfig
if __name__ == "__main__":
ruff = os.path.join(sysconfig.get_path("scripts"), "ruff")
os.spawnv(os.P_WAIT, ruff, [ruff, *sys.argv[1:]])
sys.exit(os.spawnv(os.P_WAIT, ruff, [ruff, *sys.argv[1:]]))

View File

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

View File

@@ -225,6 +225,29 @@ pub fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
}
}
/// Format the module name for a relative import.
pub fn format_import_from(level: Option<&usize>, module: Option<&String>) -> String {
let mut module_name = String::with_capacity(16);
if let Some(level) = level {
for _ in 0..*level {
module_name.push('.');
}
}
if let Some(module) = module {
module_name.push_str(module);
}
module_name
}
/// Split a target string (like `typing.List`) into (`typing`, `List`).
pub fn to_module_and_member(target: &str) -> (&str, &str) {
if let Some(index) = target.rfind('.') {
(&target[..index], &target[index + 1..])
} else {
("", target)
}
}
/// Convert a location within a file (relative to `base`) to an absolute
/// position.
pub fn to_absolute(relative: &Location, base: &Location) -> Location {

View File

@@ -54,7 +54,7 @@ pub struct Scope<'a> {
pub id: usize,
pub kind: ScopeKind<'a>,
pub import_starred: bool,
pub values: FnvHashMap<String, Binding>,
pub values: FnvHashMap<&'a str, Binding>,
}
impl<'a> Scope<'a> {
@@ -79,14 +79,16 @@ pub enum BindingKind {
Annotation,
Argument,
Assignment,
// TODO(charlie): This seems to be a catch-all.
Binding,
LoopVar,
Global,
Builtin,
ClassDefinition,
Definition,
Export(Vec<String>),
FutureImportation,
StarImportation,
StarImportation(Option<usize>, Option<String>),
Importation(String, String, BindingContext),
FromImportation(String, String, BindingContext),
SubmoduleImportation(String, String, BindingContext),

View File

@@ -50,4 +50,15 @@ impl Fix {
applied: false,
}
}
pub fn dummy(location: Location) -> Self {
Self {
patch: Patch {
content: "".to_string(),
location,
end_location: location,
},
applied: false,
}
}
}

View File

@@ -19,8 +19,7 @@ use crate::ast::helpers::{
use crate::ast::operations::extract_all_names;
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, ImportKind, Range, Scope,
ScopeKind,
Binding, BindingContext, BindingKind, ClassScope, ImportKind, Range, Scope, ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{helpers, operations, visitor};
@@ -37,7 +36,8 @@ use crate::source_code_locator::SourceCodeLocator;
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_print, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade,
flake8_comprehensions, flake8_print, flake8_tidy_imports, pep8_naming, pycodestyle, pydocstyle,
pyflakes, pyupgrade,
};
const GLOBAL_SCOPE_INDEX: usize = 0;
@@ -215,21 +215,18 @@ where
match &stmt.node {
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
let global_scope_id = self.scopes[GLOBAL_SCOPE_INDEX].id;
let current_scope = self.current_scope();
let current_scope_id = current_scope.id;
if current_scope_id != global_scope_id {
let scope =
&mut self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if scope.id != global_scope_id {
for name in names {
for scope in self.scopes.iter_mut().skip(GLOBAL_SCOPE_INDEX + 1) {
scope.values.insert(
name.to_string(),
Binding {
kind: BindingKind::Assignment,
used: Some((global_scope_id, Range::from_located(stmt))),
range: Range::from_located(stmt),
},
);
}
scope.values.insert(
name,
Binding {
kind: BindingKind::Global,
used: Some((global_scope_id, Range::from_located(stmt))),
range: Range::from_located(stmt),
},
);
}
}
@@ -309,6 +306,8 @@ where
name,
decorator_list,
args,
&self.from_imports,
&self.import_aliases,
&self.settings.pep8_naming,
)
{
@@ -322,6 +321,8 @@ where
name,
decorator_list,
args,
&self.from_imports,
&self.import_aliases,
&self.settings.pep8_naming,
) {
self.add_check(check);
@@ -397,7 +398,7 @@ where
self.visit_expr(expr);
}
self.add_binding(
name.to_string(),
name,
Binding {
kind: BindingKind::Definition,
used: None,
@@ -502,7 +503,7 @@ where
let name = alias.node.name.split('.').next().unwrap();
let full_name = &alias.node.name;
self.add_binding(
name.to_string(),
name,
Binding {
kind: BindingKind::SubmoduleImportation(
name.to_string(),
@@ -524,7 +525,7 @@ where
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
let full_name = &alias.node.name;
self.add_binding(
name.to_string(),
name,
Binding {
kind: BindingKind::Importation(
name.to_string(),
@@ -657,7 +658,7 @@ where
if let Some("__future__") = module.as_deref() {
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
self.add_binding(
name.to_string(),
name,
Binding {
kind: BindingKind::FutureImportation,
// Always mark `__future__` imports as used.
@@ -694,16 +695,10 @@ where
));
}
} else if alias.node.name == "*" {
let module_name = format!(
"{}{}",
".".repeat(level.unwrap_or_default()),
module.clone().unwrap_or_else(|| "module".to_string()),
);
self.add_binding(
module_name.to_string(),
"*",
Binding {
kind: BindingKind::StarImportation,
kind: BindingKind::StarImportation(*level, module.clone()),
used: None,
range: Range::from_located(stmt),
},
@@ -714,7 +709,10 @@ where
[*(self.scope_stack.last().expect("No current scope found."))];
if !matches!(scope.kind, ScopeKind::Module) {
self.add_check(Check::new(
CheckKind::ImportStarNotPermitted(module_name.to_string()),
CheckKind::ImportStarNotPermitted(helpers::format_import_from(
level.as_ref(),
module.as_ref(),
)),
Range::from_located(stmt),
));
}
@@ -722,15 +720,16 @@ where
if self.settings.enabled.contains(&CheckCode::F403) {
self.add_check(Check::new(
CheckKind::ImportStarUsed(module_name.to_string()),
CheckKind::ImportStarUsed(helpers::format_import_from(
level.as_ref(),
module.as_ref(),
)),
Range::from_located(stmt),
));
}
let scope = &mut self.scopes[*(self
.scope_stack
.last_mut()
.expect("No current scope found."))];
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
scope.import_starred = true;
} else {
if let Some(asname) = &alias.node.asname {
@@ -746,7 +745,7 @@ where
Some(parent) => format!("{}.{}", parent, alias.node.name),
};
self.add_binding(
name.to_string(),
name,
Binding {
kind: BindingKind::FromImportation(
name.to_string(),
@@ -778,6 +777,16 @@ where
)
}
if self.settings.enabled.contains(&CheckCode::I252) {
if let Some(check) = flake8_tidy_imports::checks::banned_relative_import(
stmt,
level.as_ref(),
&self.settings.flake8_tidy_imports.ban_relative_imports,
) {
self.add_check(check);
}
}
if let Some(asname) = &alias.node.asname {
if self.settings.enabled.contains(&CheckCode::N811) {
if let Some(check) =
@@ -862,7 +871,12 @@ where
pyflakes::plugins::assert_tuple(self, stmt, test);
}
if self.settings.enabled.contains(&CheckCode::B011) {
flake8_bugbear::plugins::assert_false(self, stmt, test, msg);
flake8_bugbear::plugins::assert_false(
self,
stmt,
test,
msg.as_ref().map(|expr| expr.deref()),
);
}
if self.settings.enabled.contains(&CheckCode::S101) {
self.add_check(flake8_bandit::plugins::assert_used(stmt));
@@ -900,10 +914,12 @@ where
}
StmtKind::Assign { targets, value, .. } => {
if self.settings.enabled.contains(&CheckCode::E731) {
if let Some(check) =
pycodestyle::checks::do_not_assign_lambda(value, Range::from_located(stmt))
{
self.add_check(check);
if let [target] = &targets[..] {
if let Some(check) =
pycodestyle::checks::do_not_assign_lambda(target, value, stmt)
{
self.add_check(check);
}
}
}
if self.settings.enabled.contains(&CheckCode::U001) {
@@ -920,13 +936,12 @@ where
}
}
}
StmtKind::AnnAssign { value, .. } => {
StmtKind::AnnAssign { target, value, .. } => {
if self.settings.enabled.contains(&CheckCode::E731) {
if let Some(value) = value {
if let Some(check) = pycodestyle::checks::do_not_assign_lambda(
value,
Range::from_located(stmt),
) {
if let Some(check) =
pycodestyle::checks::do_not_assign_lambda(target, value, stmt)
{
self.add_check(check);
}
}
@@ -1017,7 +1032,7 @@ where
if let StmtKind::ClassDef { name, .. } = &stmt.node {
self.pop_scope();
self.add_binding(
name.to_string(),
name,
Binding {
kind: BindingKind::ClassDefinition,
used: None,
@@ -1130,7 +1145,7 @@ where
self.check_builtin_shadowing(id, Range::from_located(expr), true);
self.handle_node_store(expr, self.current_parent());
self.handle_node_store(id, expr, self.current_parent());
}
ExprContext::Del => self.handle_node_delete(expr),
}
@@ -1425,27 +1440,16 @@ where
}
// pyupgrade
if self.settings.enabled.contains(&CheckCode::U002)
&& self.settings.target_version >= PythonVersion::Py310
{
pyupgrade::plugins::unnecessary_abspath(self, expr, func, args);
}
if self.settings.enabled.contains(&CheckCode::U003) {
pyupgrade::plugins::type_of_primitive(self, expr, func, args);
}
if let ExprKind::Name { id, ctx } = &func.node {
if id == "locals" && matches!(ctx, ExprContext::Load) {
let scope = &mut self.scopes[*(self
.scope_stack
.last_mut()
.expect("No current scope found."))];
if matches!(
scope.kind,
ScopeKind::Function(FunctionScope { uses_locals: false })
) {
scope.kind = ScopeKind::Function(FunctionScope { uses_locals: true });
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if let ScopeKind::Function(inner) = &mut scope.kind {
inner.uses_locals = true;
}
}
}
@@ -1521,16 +1525,15 @@ where
let check_none_comparisons = self.settings.enabled.contains(&CheckCode::E711);
let check_true_false_comparisons = self.settings.enabled.contains(&CheckCode::E712);
if check_none_comparisons || check_true_false_comparisons {
self.add_checks(
pycodestyle::checks::literal_comparisons(
left,
ops,
comparators,
check_none_comparisons,
check_true_false_comparisons,
)
.into_iter(),
);
pycodestyle::plugins::literal_comparisons(
self,
expr,
left,
ops,
comparators,
check_none_comparisons,
check_true_false_comparisons,
)
}
if self.settings.enabled.contains(&CheckCode::F632) {
@@ -1860,8 +1863,9 @@ where
false,
);
if self.current_scope().values.contains_key(name) {
if self.current_scope().values.contains_key(&name.as_str()) {
self.handle_node_store(
name,
&Expr::new(
excepthandler.location,
excepthandler.end_location.unwrap(),
@@ -1874,8 +1878,9 @@ where
);
}
let definition = self.current_scope().values.get(name).cloned();
let definition = self.current_scope().values.get(&name.as_str()).cloned();
self.handle_node_store(
name,
&Expr::new(
excepthandler.location,
excepthandler.end_location.unwrap(),
@@ -1892,7 +1897,7 @@ where
if let Some(binding) = {
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
&scope.values.remove(name)
&scope.values.remove(&name.as_str())
} {
if binding.used.is_none() {
if self.settings.enabled.contains(&CheckCode::F841) {
@@ -1907,7 +1912,7 @@ where
if let Some(binding) = definition {
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
scope.values.insert(name.to_string(), binding);
scope.values.insert(name, binding);
}
}
None => walk_excepthandler(self, excepthandler),
@@ -1951,7 +1956,7 @@ where
// Bind, but intentionally avoid walking the annotation, as we handle it
// upstream.
self.add_binding(
arg.node.arg.to_string(),
&arg.node.arg,
Binding {
kind: BindingKind::Argument,
used: None,
@@ -2016,7 +2021,7 @@ fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> b
};
// Mark the sub-importation as used.
if let Some(binding) = scope.values.get_mut(&alias) {
if let Some(binding) = scope.values.get_mut(alias.as_str()) {
binding.used = Some((scope_id, Range::from_located(expr)));
}
true
@@ -2052,7 +2057,7 @@ impl<'a> Checker<'a> {
for builtin in BUILTINS {
scope.values.insert(
(*builtin).to_string(),
builtin,
Binding {
kind: BindingKind::Builtin,
range: Default::default(),
@@ -2062,7 +2067,7 @@ impl<'a> Checker<'a> {
}
for builtin in MAGIC_GLOBALS {
scope.values.insert(
(*builtin).to_string(),
builtin,
Binding {
kind: BindingKind::Builtin,
range: Default::default(),
@@ -2090,23 +2095,26 @@ impl<'a> Checker<'a> {
}
}
fn add_binding(&mut self, name: String, binding: Binding) {
fn add_binding<'b>(&mut self, name: &'b str, binding: Binding)
where
'b: 'a,
{
if self.settings.enabled.contains(&CheckCode::F402) {
let scope = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(existing) = scope.values.get(&name) {
if matches!(binding.kind, BindingKind::LoopVar)
&& matches!(
existing.kind,
BindingKind::Importation(_, _, _)
| BindingKind::FromImportation(_, _, _)
| BindingKind::SubmoduleImportation(_, _, _)
| BindingKind::StarImportation
BindingKind::Importation(..)
| BindingKind::FromImportation(..)
| BindingKind::SubmoduleImportation(..)
| BindingKind::StarImportation(..)
| BindingKind::FutureImportation
)
{
self.add_check(Check::new(
CheckKind::ImportShadowedByLoopVar(
name.clone(),
name.to_string(),
existing.range.location.row(),
),
binding.range,
@@ -2164,16 +2172,19 @@ impl<'a> Checker<'a> {
let mut from_list = vec![];
for scope_index in self.scope_stack.iter().rev() {
let scope = &self.scopes[*scope_index];
for (name, binding) in scope.values.iter() {
if matches!(binding.kind, BindingKind::StarImportation) {
from_list.push(name.to_string());
for binding in scope.values.values() {
if let BindingKind::StarImportation(level, module) = &binding.kind {
from_list.push(helpers::format_import_from(
level.as_ref(),
module.as_ref(),
));
}
}
}
from_list.sort();
self.add_check(Check::new(
CheckKind::ImportStarUsage(id.clone(), from_list),
CheckKind::ImportStarUsage(id.to_string(), from_list),
Range::from_located(expr),
));
}
@@ -2204,113 +2215,121 @@ impl<'a> Checker<'a> {
}
}
fn handle_node_store(&mut self, expr: &Expr, parent: &Stmt) {
if let ExprKind::Name { id, .. } = &expr.node {
if self.settings.enabled.contains(&CheckCode::F823) {
let scopes: Vec<&Scope> = self
.scope_stack
.iter()
.map(|index| &self.scopes[*index])
.collect();
if let Some(check) = pyflakes::checks::undefined_local(&scopes, id) {
self.add_check(check);
}
fn handle_node_store<'b>(&mut self, id: &'b str, expr: &Expr, parent: &Stmt)
where
'b: 'a,
{
if self.settings.enabled.contains(&CheckCode::F823) {
let scopes: Vec<&Scope> = self
.scope_stack
.iter()
.map(|index| &self.scopes[*index])
.collect();
if let Some(check) = pyflakes::checks::undefined_local(&scopes, id) {
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::N806) {
if matches!(self.current_scope().kind, ScopeKind::Function(..)) {
if self.settings.enabled.contains(&CheckCode::N806) {
if matches!(self.current_scope().kind, ScopeKind::Function(..)) {
// Ignore globals.
if !self
.current_scope()
.values
.get(id)
.map(|binding| matches!(binding.kind, BindingKind::Global))
.unwrap_or(false)
{
pep8_naming::plugins::non_lowercase_variable_in_function(self, expr, parent, id)
}
}
}
if self.settings.enabled.contains(&CheckCode::N815) {
if matches!(self.current_scope().kind, ScopeKind::Class(..)) {
pep8_naming::plugins::mixed_case_variable_in_class_scope(self, expr, parent, id)
}
if self.settings.enabled.contains(&CheckCode::N815) {
if matches!(self.current_scope().kind, ScopeKind::Class(..)) {
pep8_naming::plugins::mixed_case_variable_in_class_scope(self, expr, parent, id)
}
}
if self.settings.enabled.contains(&CheckCode::N816) {
if matches!(self.current_scope().kind, ScopeKind::Module) {
pep8_naming::plugins::mixed_case_variable_in_global_scope(
self, expr, parent, id,
)
}
}
if matches!(parent.node, StmtKind::AnnAssign { value: None, .. }) {
self.add_binding(
id.to_string(),
Binding {
kind: BindingKind::Annotation,
used: None,
range: Range::from_located(expr),
},
);
return;
}
// TODO(charlie): Include comprehensions here.
if matches!(
parent.node,
StmtKind::For { .. } | StmtKind::AsyncFor { .. }
) {
self.add_binding(
id.to_string(),
Binding {
kind: BindingKind::LoopVar,
used: None,
range: Range::from_located(expr),
},
);
return;
}
if operations::is_unpacking_assignment(parent) {
self.add_binding(
id.to_string(),
Binding {
kind: BindingKind::Binding,
used: None,
range: Range::from_located(expr),
},
);
return;
}
let current =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if id == "__all__"
&& matches!(current.kind, ScopeKind::Module)
&& matches!(
parent.node,
StmtKind::Assign { .. }
| StmtKind::AugAssign { .. }
| StmtKind::AnnAssign { .. }
)
{
self.add_binding(
id.to_string(),
Binding {
kind: BindingKind::Export(extract_all_names(parent, current)),
used: None,
range: Range::from_located(expr),
},
);
return;
if self.settings.enabled.contains(&CheckCode::N816) {
if matches!(self.current_scope().kind, ScopeKind::Module) {
pep8_naming::plugins::mixed_case_variable_in_global_scope(self, expr, parent, id)
}
}
if matches!(parent.node, StmtKind::AnnAssign { value: None, .. }) {
self.add_binding(
id.to_string(),
id,
Binding {
kind: BindingKind::Assignment,
kind: BindingKind::Annotation,
used: None,
range: Range::from_located(expr),
},
);
return;
}
// TODO(charlie): Include comprehensions here.
if matches!(
parent.node,
StmtKind::For { .. } | StmtKind::AsyncFor { .. }
) {
self.add_binding(
id,
Binding {
kind: BindingKind::LoopVar,
used: None,
range: Range::from_located(expr),
},
);
return;
}
if operations::is_unpacking_assignment(parent) {
self.add_binding(
id,
Binding {
kind: BindingKind::Binding,
used: None,
range: Range::from_located(expr),
},
);
return;
}
let current = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if id == "__all__"
&& matches!(current.kind, ScopeKind::Module)
&& matches!(
parent.node,
StmtKind::Assign { .. } | StmtKind::AugAssign { .. } | StmtKind::AnnAssign { .. }
)
{
self.add_binding(
id,
Binding {
kind: BindingKind::Export(extract_all_names(parent, current)),
used: None,
range: Range::from_located(expr),
},
);
return;
}
self.add_binding(
id,
Binding {
kind: BindingKind::Assignment,
used: None,
range: Range::from_located(expr),
},
);
}
fn handle_node_delete(&mut self, expr: &Expr) {
fn handle_node_delete<'b>(&mut self, expr: &'b Expr)
where
'b: 'a,
{
if let ExprKind::Name { id, .. } = &expr.node {
if operations::on_conditional_branch(
&mut self
@@ -2324,10 +2343,11 @@ impl<'a> Checker<'a> {
let scope =
&mut self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if scope.values.remove(id).is_none() && self.settings.enabled.contains(&CheckCode::F821)
if scope.values.remove(&id.as_str()).is_none()
&& self.settings.enabled.contains(&CheckCode::F821)
{
self.add_check(Check::new(
CheckKind::UndefinedName(id.clone()),
CheckKind::UndefinedName(id.to_string()),
Range::from_located(expr),
))
}
@@ -2459,16 +2479,19 @@ impl<'a> Checker<'a> {
let mut checks: Vec<Check> = vec![];
for scope in self.dead_scopes.iter().map(|index| &self.scopes[*index]) {
let all_binding = scope.values.get("__all__");
let all_names = all_binding.and_then(|binding| match &binding.kind {
BindingKind::Export(names) => Some(names),
_ => None,
});
let all_binding: Option<&Binding> = scope.values.get("__all__");
let all_names: Option<Vec<&str>> =
all_binding.and_then(|binding| match &binding.kind {
BindingKind::Export(names) => {
Some(names.iter().map(|name| name.as_str()).collect())
}
_ => None,
});
if self.settings.enabled.contains(&CheckCode::F822) {
if !scope.import_starred && !self.path.ends_with("__init__.py") {
if let Some(all_binding) = all_binding {
if let Some(names) = all_names {
if let Some(names) = &all_names {
for name in names {
if !scope.values.contains_key(name) {
checks.push(Check::new(
@@ -2485,11 +2508,14 @@ impl<'a> Checker<'a> {
if self.settings.enabled.contains(&CheckCode::F405) {
if scope.import_starred {
if let Some(all_binding) = all_binding {
if let Some(names) = all_names {
if let Some(names) = &all_names {
let mut from_list = vec![];
for (name, binding) in scope.values.iter() {
if matches!(binding.kind, BindingKind::StarImportation) {
from_list.push(name.to_string());
for binding in scope.values.values() {
if let BindingKind::StarImportation(level, module) = &binding.kind {
from_list.push(helpers::format_import_from(
level.as_ref(),
module.as_ref(),
));
}
}
from_list.sort();
@@ -2497,7 +2523,10 @@ impl<'a> Checker<'a> {
for name in names {
if !scope.values.contains_key(name) {
checks.push(Check::new(
CheckKind::ImportStarUsage(name.clone(), from_list.clone()),
CheckKind::ImportStarUsage(
name.to_string(),
from_list.clone(),
),
all_binding.range,
));
}
@@ -2514,8 +2543,18 @@ impl<'a> Checker<'a> {
BTreeMap::new();
for (name, binding) in scope.values.iter() {
if !matches!(
binding.kind,
BindingKind::Importation(..)
| BindingKind::SubmoduleImportation(..)
| BindingKind::FromImportation(..)
) {
continue;
}
let used = binding.used.is_some()
|| all_names
.as_ref()
.map(|names| names.contains(name))
.unwrap_or_default();
@@ -2542,7 +2581,7 @@ impl<'a> Checker<'a> {
.or_default()
.push(full_name);
}
_ => {}
_ => unreachable!("Already filtered on BindingKind."),
}
}
}

View File

@@ -58,10 +58,7 @@ pub fn check_lines(
// Grab the noqa (logical) line number for the current (physical) line.
// If there are newlines at the end of the file, they won't be represented in
// `noqa_line_for`, so fallback to the current line.
let noqa_lineno = noqa_line_for
.get(&lineno)
.map(|lineno| lineno - 1)
.unwrap_or(lineno);
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
// Enforce unnecessary coding comments (U009).
if enforce_unnecessary_coding_comment {
@@ -155,10 +152,7 @@ pub fn check_lines(
// want to raise W292 anyway).
if let Some(line) = lines.last() {
let lineno = lines.len() - 1;
let noqa_lineno = noqa_line_for
.get(&lineno)
.map(|lineno| lineno - 1)
.unwrap_or(lineno);
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
let noqa = noqa_directives
.entry(noqa_lineno)

View File

@@ -8,6 +8,7 @@ use strum_macros::{AsRefStr, EnumIter, EnumString};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::flake8_quotes::settings::Quote;
use crate::flake8_tidy_imports::settings::Strictness;
use crate::pyupgrade::types::Primitive;
#[derive(
@@ -119,6 +120,8 @@ pub enum CheckCode {
C415,
C416,
C417,
// flake8-tidy-imports
I252,
// flake8-print
T201,
T203,
@@ -152,7 +155,6 @@ pub enum CheckCode {
YTT303,
// pyupgrade
U001,
U002,
U003,
U004,
U005,
@@ -253,6 +255,7 @@ pub enum CheckCategory {
Flake8Comprehensions,
Flake8Bugbear,
Flake8Builtins,
Flake8TidyImports,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
@@ -271,6 +274,7 @@ impl CheckCategory {
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Bugbear => "flake8-bugbear",
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes",
CheckCategory::Flake8Annotations => "flake8-annotations",
@@ -297,6 +301,9 @@ impl CheckCategory {
CheckCategory::Flake8Comprehensions => {
Some("https://pypi.org/project/flake8-comprehensions/3.10.1/")
}
CheckCategory::Flake8TidyImports => {
Some("https://pypi.org/project/flake8-tidy-imports/4.8.0/")
}
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
CheckCategory::Flake8Annotations => {
@@ -424,6 +431,8 @@ pub enum CheckKind {
UnnecessarySubscriptReversal(String),
UnnecessaryComprehension(String),
UnnecessaryMap(String),
// flake8-tidy-imports
BannedRelativeImport(Strictness),
// flake8-print
PrintFound,
PPrintFound,
@@ -457,7 +466,6 @@ pub enum CheckKind {
SysVersionSlice1Referenced,
// pyupgrade
TypeOfPrimitive(Primitive),
UnnecessaryAbspath,
UselessMetaclassType,
DeprecatedUnittestAlias(String, String),
UselessObjectInheritance(String),
@@ -684,6 +692,8 @@ impl CheckCode {
}
CheckCode::C416 => CheckKind::UnnecessaryComprehension("(list|set)".to_string()),
CheckCode::C417 => CheckKind::UnnecessaryMap("(list|set|dict)".to_string()),
// flake8-tidy-imports
CheckCode::I252 => CheckKind::BannedRelativeImport(Strictness::All),
// flake8-print
CheckCode::T201 => CheckKind::PrintFound,
CheckCode::T203 => CheckKind::PPrintFound,
@@ -717,7 +727,6 @@ impl CheckCode {
CheckCode::YTT303 => CheckKind::SysVersionSlice1Referenced,
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
CheckCode::U003 => CheckKind::TypeOfPrimitive(Primitive::Str),
CheckCode::U004 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::U005 => CheckKind::DeprecatedUnittestAlias(
@@ -916,6 +925,7 @@ impl CheckCode {
CheckCode::C415 => CheckCategory::Flake8Comprehensions,
CheckCode::C416 => CheckCategory::Flake8Comprehensions,
CheckCode::C417 => CheckCategory::Flake8Comprehensions,
CheckCode::I252 => CheckCategory::Flake8TidyImports,
CheckCode::T201 => CheckCategory::Flake8Print,
CheckCode::T203 => CheckCategory::Flake8Print,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
@@ -944,7 +954,6 @@ impl CheckCode {
CheckCode::YTT302 => CheckCategory::Flake82020,
CheckCode::YTT303 => CheckCategory::Flake82020,
CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U002 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade,
CheckCode::U004 => CheckCategory::Pyupgrade,
CheckCode::U005 => CheckCategory::Pyupgrade,
@@ -1126,6 +1135,8 @@ impl CheckKind {
CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415,
CheckKind::UnnecessaryComprehension(..) => &CheckCode::C416,
CheckKind::UnnecessaryMap(_) => &CheckCode::C417,
// flake8-tidy-imports
CheckKind::BannedRelativeImport(_) => &CheckCode::I252,
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
@@ -1159,7 +1170,6 @@ impl CheckKind {
CheckKind::SysVersionSlice1Referenced => &CheckCode::YTT303,
// pyupgrade
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
CheckKind::UselessMetaclassType => &CheckCode::U001,
CheckKind::DeprecatedUnittestAlias(..) => &CheckCode::U005,
CheckKind::UsePEP585Annotation(_) => &CheckCode::U006,
@@ -1579,6 +1589,13 @@ impl CheckKind {
format!("Unnecessary `map` usage (rewrite using a `{obj_type}` comprehension)")
}
}
// flake8-tidy-imports
CheckKind::BannedRelativeImport(strictness) => match strictness {
Strictness::Parents => {
"Relative imports from parent modules are banned".to_string()
}
Strictness::All => "Relative imports are banned".to_string(),
},
// flake8-print
CheckKind::PrintFound => "`print` found".to_string(),
CheckKind::PPrintFound => "`pprint` found".to_string(),
@@ -1674,9 +1691,6 @@ impl CheckKind {
CheckKind::TypeOfPrimitive(primitive) => {
format!("Use `{}` instead of `type(...)`", primitive.builtin())
}
CheckKind::UnnecessaryAbspath => {
"`abspath(__file__)` is unnecessary in Python 3.9 and later".to_string()
}
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
CheckKind::DeprecatedUnittestAlias(alias, target) => {
format!("`{alias}` is deprecated, use `{target}` instead")
@@ -1986,7 +2000,6 @@ impl CheckKind {
| CheckKind::SectionUnderlineNotOverIndented(_)
| CheckKind::SuperCallWithParameters
| CheckKind::TypeOfPrimitive(_)
| CheckKind::UnnecessaryAbspath
| CheckKind::UnnecessaryCollectionCall(_)
| CheckKind::UnnecessaryComprehension(_)
| CheckKind::UnnecessaryEncodeUTF8

View File

@@ -215,6 +215,9 @@ pub enum CheckCodePrefix {
I0,
I00,
I001,
I2,
I25,
I252,
M,
M0,
M00,
@@ -269,7 +272,6 @@ pub enum CheckCodePrefix {
U0,
U00,
U001,
U002,
U003,
U004,
U005,
@@ -926,10 +928,13 @@ impl CheckCodePrefix {
CheckCodePrefix::F9 => vec![CheckCode::F901],
CheckCodePrefix::F90 => vec![CheckCode::F901],
CheckCodePrefix::F901 => vec![CheckCode::F901],
CheckCodePrefix::I => vec![CheckCode::I001],
CheckCodePrefix::I => vec![CheckCode::I252, CheckCode::I001],
CheckCodePrefix::I0 => vec![CheckCode::I001],
CheckCodePrefix::I00 => vec![CheckCode::I001],
CheckCodePrefix::I001 => vec![CheckCode::I001],
CheckCodePrefix::I2 => vec![CheckCode::I252],
CheckCodePrefix::I25 => vec![CheckCode::I252],
CheckCodePrefix::I252 => vec![CheckCode::I252],
CheckCodePrefix::M => vec![CheckCode::M001],
CheckCodePrefix::M0 => vec![CheckCode::M001],
CheckCodePrefix::M00 => vec![CheckCode::M001],
@@ -1067,7 +1072,6 @@ impl CheckCodePrefix {
CheckCodePrefix::T203 => vec![CheckCode::T203],
CheckCodePrefix::U => vec![
CheckCode::U001,
CheckCode::U002,
CheckCode::U003,
CheckCode::U004,
CheckCode::U005,
@@ -1081,7 +1085,6 @@ impl CheckCodePrefix {
],
CheckCodePrefix::U0 => vec![
CheckCode::U001,
CheckCode::U002,
CheckCode::U003,
CheckCode::U004,
CheckCode::U005,
@@ -1095,7 +1098,6 @@ impl CheckCodePrefix {
],
CheckCodePrefix::U00 => vec![
CheckCode::U001,
CheckCode::U002,
CheckCode::U003,
CheckCode::U004,
CheckCode::U005,
@@ -1105,7 +1107,6 @@ impl CheckCodePrefix {
CheckCode::U009,
],
CheckCodePrefix::U001 => vec![CheckCode::U001],
CheckCodePrefix::U002 => vec![CheckCode::U002],
CheckCodePrefix::U003 => vec![CheckCode::U003],
CheckCodePrefix::U004 => vec![CheckCode::U004],
CheckCodePrefix::U005 => vec![CheckCode::U005],
@@ -1377,6 +1378,9 @@ impl CheckCodePrefix {
CheckCodePrefix::I0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::I00 => PrefixSpecificity::Tens,
CheckCodePrefix::I001 => PrefixSpecificity::Explicit,
CheckCodePrefix::I2 => PrefixSpecificity::Hundreds,
CheckCodePrefix::I25 => PrefixSpecificity::Tens,
CheckCodePrefix::I252 => PrefixSpecificity::Explicit,
CheckCodePrefix::M => PrefixSpecificity::Category,
CheckCodePrefix::M0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::M00 => PrefixSpecificity::Tens,
@@ -1431,7 +1435,6 @@ impl CheckCodePrefix {
CheckCodePrefix::U0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::U00 => PrefixSpecificity::Tens,
CheckCodePrefix::U001 => PrefixSpecificity::Explicit,
CheckCodePrefix::U002 => PrefixSpecificity::Explicit,
CheckCodePrefix::U003 => PrefixSpecificity::Explicit,
CheckCodePrefix::U004 => PrefixSpecificity::Explicit,
CheckCodePrefix::U005 => PrefixSpecificity::Explicit,

View File

@@ -152,7 +152,7 @@ pub fn warn_on(
cli_ignore: &[CheckCodePrefix],
cli_extend_ignore: &[CheckCodePrefix],
pyproject_configuration: &Configuration,
pyproject_path: &Option<PathBuf>,
pyproject_path: Option<&PathBuf>,
) {
for code in codes {
if !cli_ignore.is_empty() {
@@ -192,7 +192,7 @@ pub fn warn_on(
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
pub fn collect_per_file_ignores(
pairs: Vec<PatternPrefixPair>,
project_root: &Option<PathBuf>,
project_root: Option<&PathBuf>,
) -> Vec<PerFileIgnore> {
let mut per_file_ignores: FnvHashMap<String, Vec<CheckCodePrefix>> = FnvHashMap::default();
for pair in pairs {

View File

@@ -1,3 +1,5 @@
use std::ops::Deref;
use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
@@ -11,7 +13,7 @@ use crate::{visibility, Check};
#[derive(Default)]
struct ReturnStatementVisitor<'a> {
returns: Vec<&'a Option<Box<Expr>>>,
returns: Vec<Option<&'a Expr>>,
}
impl<'a, 'b> Visitor<'b> for ReturnStatementVisitor<'a>
@@ -23,7 +25,9 @@ where
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// No recurse.
}
StmtKind::Return { value } => self.returns.push(value),
StmtKind::Return { value } => {
self.returns.push(value.as_ref().map(|expr| expr.deref()))
}
_ => visitor::walk_stmt(self, stmt),
}
}

View File

@@ -6,7 +6,7 @@ use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::code_gen::SourceGenerator;
fn assertion_error(msg: &Option<Box<Expr>>) -> Stmt {
fn assertion_error(msg: Option<&Expr>) -> Stmt {
Stmt::new(
Default::default(),
Default::default(),
@@ -24,7 +24,7 @@ fn assertion_error(msg: &Option<Box<Expr>>) -> Stmt {
},
)),
args: if let Some(msg) = msg {
vec![*msg.clone()]
vec![msg.clone()]
} else {
vec![]
},
@@ -36,7 +36,7 @@ fn assertion_error(msg: &Option<Box<Expr>>) -> Stmt {
)
}
pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: &Option<Box<Expr>>) {
pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option<&Expr>) {
if let ExprKind::Constant {
value: Constant::Bool(false),
..

View File

@@ -2,7 +2,7 @@ use fnv::{FnvHashMap, FnvHashSet};
use rustpython_ast::{Arguments, Constant, Expr, ExprKind};
use crate::ast::helpers::{
collect_call_paths, compose_call_path, dealias_call_path, match_call_path,
collect_call_paths, compose_call_path, dealias_call_path, match_call_path, to_module_and_member,
};
use crate::ast::types::Range;
use crate::ast::visitor;
@@ -104,14 +104,7 @@ pub fn function_call_argument_default(checker: &mut Checker, arguments: &Argumen
.flake8_bugbear
.extend_immutable_calls
.iter()
.map(|s| {
let s = s.as_str();
if let Some(index) = s.rfind('.') {
(&s[..index], &s[index + 1..])
} else {
("", s)
}
})
.map(|target| to_module_and_member(target))
.collect();
let mut visitor = ArgumentDefaultVisitor {
checks: vec![],

View File

@@ -1,4 +1,4 @@
//! Settings for the `flake-quotes` plugin.
//! Settings for the `flake8-quotes` plugin.
use serde::{Deserialize, Serialize};

View File

@@ -0,0 +1,26 @@
use rustpython_ast::Stmt;
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::flake8_tidy_imports::settings::Strictness;
pub fn banned_relative_import(
stmt: &Stmt,
level: Option<&usize>,
strictness: &Strictness,
) -> Option<Check> {
if let Some(level) = level {
if level
> &match strictness {
Strictness::All => 0,
Strictness::Parents => 1,
}
{
return Some(Check::new(
CheckKind::BannedRelativeImport(strictness.clone()),
Range::from_located(stmt),
));
}
}
None
}

View File

@@ -0,0 +1,49 @@
pub mod checks;
pub mod settings;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::flake8_tidy_imports::settings::Strictness;
use crate::linter::test_path;
use crate::{flake8_tidy_imports, Settings};
#[test]
fn ban_parent_imports() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/I252.py"),
&Settings {
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
ban_relative_imports: Strictness::Parents,
},
..Settings::for_rules(vec![CheckCode::I252])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn ban_all_imports() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/I252.py"),
&Settings {
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
ban_relative_imports: Strictness::All,
},
..Settings::for_rules(vec![CheckCode::I252])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -0,0 +1,37 @@
//! Settings for the `flake8-tidy-imports` plugin.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum Strictness {
Parents,
All,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
pub ban_relative_imports: Option<Strictness>,
}
#[derive(Debug, Hash)]
pub struct Settings {
pub ban_relative_imports: Strictness,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
ban_relative_imports: options.ban_relative_imports.unwrap_or(Strictness::Parents),
}
}
}
impl Default for Settings {
fn default() -> Self {
Self {
ban_relative_imports: Strictness::Parents,
}
}
}

View File

@@ -0,0 +1,59 @@
---
source: src/flake8_tidy_imports/mod.rs
expression: checks
---
- kind:
BannedRelativeImport: all
location:
row: 1
column: 0
end_location:
row: 1
column: 21
fix: ~
- kind:
BannedRelativeImport: all
location:
row: 2
column: 0
end_location:
row: 2
column: 28
fix: ~
- kind:
BannedRelativeImport: all
location:
row: 4
column: 0
end_location:
row: 4
column: 21
fix: ~
- kind:
BannedRelativeImport: all
location:
row: 5
column: 0
end_location:
row: 5
column: 28
fix: ~
- kind:
BannedRelativeImport: all
location:
row: 7
column: 0
end_location:
row: 7
column: 27
fix: ~
- kind:
BannedRelativeImport: all
location:
row: 8
column: 0
end_location:
row: 8
column: 34
fix: ~

View File

@@ -0,0 +1,41 @@
---
source: src/flake8_tidy_imports/mod.rs
expression: checks
---
- kind:
BannedRelativeImport: parents
location:
row: 4
column: 0
end_location:
row: 4
column: 21
fix: ~
- kind:
BannedRelativeImport: parents
location:
row: 5
column: 0
end_location:
row: 5
column: 28
fix: ~
- kind:
BannedRelativeImport: parents
location:
row: 7
column: 0
end_location:
row: 7
column: 27
fix: ~
- kind:
BannedRelativeImport: parents
location:
row: 8
column: 0
end_location:
row: 8
column: 34
fix: ~

View File

@@ -202,7 +202,7 @@ mod tests {
let path = Path::new("foo").absolutize_from(project_root).unwrap();
let exclude = vec![FilePattern::from_user(
"foo",
&Some(project_root.to_path_buf()),
Some(&project_root.to_path_buf()),
)];
let (file_path, file_basename) = extract_path_names(&path)?;
assert!(is_excluded(file_path, file_basename, exclude.iter()));
@@ -210,7 +210,7 @@ mod tests {
let path = Path::new("foo/bar").absolutize_from(project_root).unwrap();
let exclude = vec![FilePattern::from_user(
"bar",
&Some(project_root.to_path_buf()),
Some(&project_root.to_path_buf()),
)];
let (file_path, file_basename) = extract_path_names(&path)?;
assert!(is_excluded(file_path, file_basename, exclude.iter()));
@@ -220,7 +220,7 @@ mod tests {
.unwrap();
let exclude = vec![FilePattern::from_user(
"baz.py",
&Some(project_root.to_path_buf()),
Some(&project_root.to_path_buf()),
)];
let (file_path, file_basename) = extract_path_names(&path)?;
assert!(is_excluded(file_path, file_basename, exclude.iter()));
@@ -228,7 +228,7 @@ mod tests {
let path = Path::new("foo/bar").absolutize_from(project_root).unwrap();
let exclude = vec![FilePattern::from_user(
"foo/bar",
&Some(project_root.to_path_buf()),
Some(&project_root.to_path_buf()),
)];
let (file_path, file_basename) = extract_path_names(&path)?;
assert!(is_excluded(file_path, file_basename, exclude.iter()));
@@ -238,7 +238,7 @@ mod tests {
.unwrap();
let exclude = vec![FilePattern::from_user(
"foo/bar/baz.py",
&Some(project_root.to_path_buf()),
Some(&project_root.to_path_buf()),
)];
let (file_path, file_basename) = extract_path_names(&path)?;
assert!(is_excluded(file_path, file_basename, exclude.iter()));
@@ -248,7 +248,7 @@ mod tests {
.unwrap();
let exclude = vec![FilePattern::from_user(
"foo/bar/*.py",
&Some(project_root.to_path_buf()),
Some(&project_root.to_path_buf()),
)];
let (file_path, file_basename) = extract_path_names(&path)?;
assert!(is_excluded(file_path, file_basename, exclude.iter()));
@@ -258,7 +258,7 @@ mod tests {
.unwrap();
let exclude = vec![FilePattern::from_user(
"baz",
&Some(project_root.to_path_buf()),
Some(&project_root.to_path_buf()),
)];
let (file_path, file_basename) = extract_path_names(&path)?;
assert!(!is_excluded(file_path, file_basename, exclude.iter()));

View File

@@ -15,13 +15,13 @@ pub enum ImportType {
pub fn categorize(
module_base: &str,
level: &Option<usize>,
level: Option<&usize>,
src: &[PathBuf],
known_first_party: &BTreeSet<String>,
known_third_party: &BTreeSet<String>,
extra_standard_library: &BTreeSet<String>,
) -> ImportType {
if level.map(|level| level > 0).unwrap_or(false) {
if level.map(|level| *level > 0).unwrap_or(false) {
ImportType::LocalFolder
} else if known_first_party.contains(module_base) {
ImportType::FirstParty

View File

@@ -25,7 +25,7 @@ mod types;
#[derive(Debug)]
pub struct AnnotatedAliasData<'a> {
pub name: &'a str,
pub asname: &'a Option<String>,
pub asname: Option<&'a String>,
pub atop: Vec<Comment<'a>>,
pub inline: Vec<Comment<'a>>,
}
@@ -37,9 +37,9 @@ pub enum AnnotatedImport<'a> {
inline: Vec<Comment<'a>>,
},
ImportFrom {
module: &'a Option<String>,
module: Option<&'a String>,
names: Vec<AnnotatedAliasData<'a>>,
level: &'a Option<usize>,
level: Option<&'a usize>,
atop: Vec<Comment<'a>>,
inline: Vec<Comment<'a>>,
},
@@ -75,7 +75,7 @@ fn annotate_imports<'a>(
.iter()
.map(|alias| AliasData {
name: &alias.node.name,
asname: &alias.node.asname,
asname: alias.node.asname.as_ref(),
})
.collect(),
atop,
@@ -124,16 +124,16 @@ fn annotate_imports<'a>(
aliases.push(AnnotatedAliasData {
name: &alias.node.name,
asname: &alias.node.asname,
asname: alias.node.asname.as_ref(),
atop: alias_atop,
inline: alias_inline,
})
}
annotated.push(AnnotatedImport::ImportFrom {
module,
module: module.as_ref(),
names: aliases,
level,
level: level.as_ref(),
atop,
inline,
});
@@ -278,7 +278,7 @@ fn categorize_imports<'a>(
for (alias, comments) in block.import {
let import_type = categorize(
&alias.module_base(),
&None,
None,
src,
known_first_party,
known_third_party,
@@ -384,7 +384,7 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
import_from
.module
.as_ref()
.map(|module| module_key(module, &None)),
.map(|module| module_key(module, None)),
aliases
.first()
.map(|(alias, _)| member_key(alias.name, alias.asname)),

View File

@@ -10,15 +10,15 @@ pub enum Prefix {
pub fn module_key<'a>(
name: &'a str,
asname: &'a Option<String>,
) -> (String, &'a str, &'a Option<String>) {
asname: Option<&'a String>,
) -> (String, &'a str, Option<&'a String>) {
(name.to_lowercase(), name, asname)
}
pub fn member_key<'a>(
name: &'a str,
asname: &'a Option<String>,
) -> (Prefix, String, &'a Option<String>) {
asname: Option<&'a String>,
) -> (Prefix, String, Option<&'a String>) {
(
if name.len() > 1 && string::is_upper(name) {
// Ex) `CONSTANT`

View File

@@ -2,16 +2,18 @@ use std::borrow::Cow;
use fnv::FnvHashMap;
use crate::ast;
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct ImportFromData<'a> {
pub module: &'a Option<String>,
pub level: &'a Option<usize>,
pub module: Option<&'a String>,
pub level: Option<&'a usize>,
}
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct AliasData<'a> {
pub name: &'a str,
pub asname: &'a Option<String>,
pub asname: Option<&'a String>,
}
#[derive(Debug, Default)]
@@ -37,16 +39,7 @@ impl Importable for AliasData<'_> {
impl Importable for ImportFromData<'_> {
fn module_name(&self) -> String {
let mut module_name = String::new();
if let Some(level) = self.level {
if level > &0 {
module_name.push_str(&".".repeat(*level));
}
}
if let Some(module) = self.module {
module_name.push_str(module);
}
module_name
ast::helpers::format_import_from(self.level, self.module)
}
fn module_base(&self) -> String {

View File

@@ -35,6 +35,7 @@ mod flake8_builtins;
mod flake8_comprehensions;
mod flake8_print;
pub mod flake8_quotes;
pub mod flake8_tidy_imports;
pub mod fs;
mod isort;
mod lex;
@@ -64,14 +65,16 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
Some(path) => debug!("Found project root at: {:?}", path),
None => debug!("Unable to identify project root; assuming current directory..."),
};
let pyproject = pyproject::find_pyproject_toml(&project_root);
let pyproject = pyproject::find_pyproject_toml(project_root.as_ref());
match &pyproject {
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
let settings =
Settings::from_configuration(Configuration::from_pyproject(&pyproject, &project_root)?);
let settings = Settings::from_configuration(Configuration::from_pyproject(
pyproject.as_ref(),
project_root.as_ref(),
)?);
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(contents);

View File

@@ -487,7 +487,6 @@ mod tests {
#[test_case(CheckCode::T201, Path::new("T201.py"); "T201")]
#[test_case(CheckCode::T203, Path::new("T203.py"); "T203")]
#[test_case(CheckCode::U001, Path::new("U001.py"); "U001")]
#[test_case(CheckCode::U002, Path::new("U002.py"); "U002")]
#[test_case(CheckCode::U003, Path::new("U003.py"); "U003")]
#[test_case(CheckCode::U004, Path::new("U004.py"); "U004")]
#[test_case(CheckCode::U005, Path::new("U005.py"); "U005")]

View File

@@ -208,7 +208,7 @@ fn inner_main() -> Result<ExitCode> {
};
let pyproject = cli
.config
.or_else(|| pyproject::find_pyproject_toml(&project_root));
.or_else(|| pyproject::find_pyproject_toml(project_root.as_ref()));
match &pyproject {
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
None => debug!("Unable to find pyproject.toml; using default settings..."),
@@ -218,15 +218,16 @@ fn inner_main() -> Result<ExitCode> {
let exclude: Vec<FilePattern> = cli
.exclude
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.map(|path| FilePattern::from_user(path, project_root.as_ref()))
.collect();
let extend_exclude: Vec<FilePattern> = cli
.extend_exclude
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.map(|path| FilePattern::from_user(path, project_root.as_ref()))
.collect();
let mut configuration = Configuration::from_pyproject(&pyproject, &project_root)?;
let mut configuration =
Configuration::from_pyproject(pyproject.as_ref(), project_root.as_ref())?;
if !exclude.is_empty() {
configuration.exclude = exclude;
}
@@ -235,7 +236,7 @@ fn inner_main() -> Result<ExitCode> {
}
if !cli.per_file_ignores.is_empty() {
configuration.per_file_ignores =
collect_per_file_ignores(cli.per_file_ignores, &project_root);
collect_per_file_ignores(cli.per_file_ignores, project_root.as_ref());
}
if !cli.select.is_empty() {
warn_on(
@@ -244,7 +245,7 @@ fn inner_main() -> Result<ExitCode> {
&cli.ignore,
&cli.extend_ignore,
&configuration,
&pyproject,
pyproject.as_ref(),
);
configuration.select = cli.select;
}
@@ -255,7 +256,7 @@ fn inner_main() -> Result<ExitCode> {
&cli.ignore,
&cli.extend_ignore,
&configuration,
&pyproject,
pyproject.as_ref(),
);
configuration.extend_select = cli.extend_select;
}

View File

@@ -72,10 +72,7 @@ fn add_noqa_inner(
// Grab the noqa (logical) line number for the current (physical) line.
// If there are newlines at the end of the file, they won't be represented in
// `noqa_line_for`, so fallback to the current line.
let noqa_lineno = noqa_line_for
.get(&lineno)
.map(|lineno| lineno - 1)
.unwrap_or(lineno);
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
if !codes.is_empty() {
let matches = matches_by_line.entry(noqa_lineno).or_default();

View File

@@ -1,3 +1,4 @@
use fnv::{FnvHashMap, FnvHashSet};
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
use crate::ast::types::{Range, Scope, ScopeKind};
@@ -58,10 +59,19 @@ pub fn invalid_first_argument_name_for_class_method(
name: &str,
decorator_list: &[Expr],
args: &Arguments,
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
import_aliases: &FnvHashMap<&str, &str>,
settings: &Settings,
) -> Option<Check> {
if matches!(
helpers::function_type(scope, name, decorator_list, settings),
helpers::function_type(
scope,
name,
decorator_list,
from_imports,
import_aliases,
settings,
),
FunctionType::ClassMethod
) {
if let Some(arg) = args.args.first() {
@@ -82,10 +92,19 @@ pub fn invalid_first_argument_name_for_method(
name: &str,
decorator_list: &[Expr],
args: &Arguments,
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
import_aliases: &FnvHashMap<&str, &str>,
settings: &Settings,
) -> Option<Check> {
if matches!(
helpers::function_type(scope, name, decorator_list, settings),
helpers::function_type(
scope,
name,
decorator_list,
from_imports,
import_aliases,
settings,
),
FunctionType::Method
) {
if let Some(arg) = args.args.first() {
@@ -205,7 +224,7 @@ pub fn error_suffix_on_exception_name(
) -> Option<Check> {
if bases.iter().any(|base| {
if let ExprKind::Name { id, .. } = &base.node {
id == "Exception"
id == "Exception" || id.ends_with("Error")
} else {
false
}

View File

@@ -1,14 +1,16 @@
use fnv::{FnvHashMap, FnvHashSet};
use itertools::Itertools;
use rustpython_ast::{Expr, ExprKind, Stmt, StmtKind};
use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::ast::helpers::{collect_call_paths, match_call_path, match_name_or_attr};
use crate::ast::helpers::{
collect_call_paths, dealias_call_path, match_call_path, to_module_and_member,
};
use crate::ast::types::{Scope, ScopeKind};
use crate::pep8_naming::settings::Settings;
use crate::python::string::{is_lower, is_upper};
const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"];
const METACLASS_BASES: [&str; 2] = ["type", "ABCMeta"];
const METACLASS_BASES: [(&str, &str); 2] = [("", "type"), ("abc", "ABCMeta")];
pub enum FunctionType {
Function,
@@ -22,35 +24,39 @@ pub fn function_type(
scope: &Scope,
name: &str,
decorator_list: &[Expr],
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
import_aliases: &FnvHashMap<&str, &str>,
settings: &Settings,
) -> FunctionType {
if let ScopeKind::Class(scope) = &scope.kind {
// Special-case class method, like `__new__`.
if CLASS_METHODS.contains(&name)
// The class itself extends a known metaclass, so all methods are class methods.
|| scope.bases.iter().any(|expr| {
METACLASS_BASES
.iter()
.any(|target| match_name_or_attr(expr, target))
// The class itself extends a known metaclass, so all methods are class methods.
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
METACLASS_BASES.iter().any(|(module, member)| {
match_call_path(&call_path, module, member, from_imports)
})
})
// The method is decorated with a class method decorator (like `@classmethod`).
|| decorator_list.iter().any(|expr| {
if let ExprKind::Name { id, .. } = &expr.node {
settings.classmethod_decorators.contains(id)
} else {
false
}
}) {
// The method is decorated with a class method decorator (like `@classmethod`).
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
settings.classmethod_decorators.iter().any(|decorator| {
let (module, member) = to_module_and_member(decorator);
match_call_path(&call_path, module, member, from_imports)
})
})
{
FunctionType::ClassMethod
} else if decorator_list.iter().any(|expr| {
if let ExprKind::Name { id, .. } = &expr.node {
settings.staticmethod_decorators.contains(id)
} else {
false
}
}) {
// The method is decorated with a static method decorator (like
// `@staticmethod`).
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
settings.staticmethod_decorators.iter().any(|decorator| {
let (module, member) = to_module_and_member(decorator);
match_call_path(&call_path, module, member, from_imports)
})
}) {
FunctionType::StaticMethod
} else {
// It's an instance method.

View File

@@ -1,9 +1,9 @@
use itertools::izip;
use rustpython_ast::Location;
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind, Unaryop};
use rustpython_parser::ast::{Cmpop, Expr, ExprKind, Stmt, Unaryop};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind, RejectedCmpop};
use crate::checks::{Check, CheckKind};
use crate::source_code_locator::SourceCodeLocator;
fn is_ambiguous_name(name: &str) -> bool {
@@ -47,12 +47,16 @@ pub fn ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
}
/// E731
pub fn do_not_assign_lambda(value: &Expr, location: Range) -> Option<Check> {
if let ExprKind::Lambda { .. } = &value.node {
Some(Check::new(CheckKind::DoNotAssignLambda, location))
} else {
None
pub fn do_not_assign_lambda(target: &Expr, value: &Expr, stmt: &Stmt) -> Option<Check> {
if let ExprKind::Name { .. } = &target.node {
if let ExprKind::Lambda { .. } = &value.node {
return Some(Check::new(
CheckKind::DoNotAssignLambda,
Range::from_located(stmt),
));
}
}
None
}
/// E713, E714
@@ -93,114 +97,6 @@ pub fn not_tests(
checks
}
/// E711, E712
pub fn literal_comparisons(
left: &Expr,
ops: &[Cmpop],
comparators: &[Expr],
check_none_comparisons: bool,
check_true_false_comparisons: bool,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
let op = ops.first().unwrap();
let comparator = left;
// Check `left`.
if check_none_comparisons
&& matches!(
comparator.node,
ExprKind::Constant {
value: Constant::None,
kind: None
}
)
{
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::Eq),
Range::from_located(comparator),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::NotEq),
Range::from_located(comparator),
));
}
}
if check_true_false_comparisons {
if let ExprKind::Constant {
value: Constant::Bool(value),
kind: None,
} = comparator.node
{
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
Range::from_located(comparator),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
Range::from_located(comparator),
));
}
}
}
// Check each comparator in order.
for (op, comparator) in izip!(ops, comparators) {
if check_none_comparisons
&& matches!(
comparator.node,
ExprKind::Constant {
value: Constant::None,
kind: None
}
)
{
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::Eq),
Range::from_located(comparator),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::NotEq),
Range::from_located(comparator),
));
}
}
if check_true_false_comparisons {
if let ExprKind::Constant {
value: Constant::Bool(value),
kind: None,
} = comparator.node
{
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
Range::from_located(comparator),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
Range::from_located(comparator),
));
}
}
}
}
checks
}
/// E721
pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];

View File

@@ -1 +1,2 @@
pub mod checks;
pub mod plugins;

203
src/pycodestyle/plugins.rs Normal file
View File

@@ -0,0 +1,203 @@
use fnv::FnvHashMap;
use itertools::izip;
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind, RejectedCmpop};
use crate::code_gen::SourceGenerator;
fn compare(left: &Expr, ops: &[Cmpop], comparators: &[Expr]) -> Option<String> {
let cmp = Expr::new(
Default::default(),
Default::default(),
ExprKind::Compare {
left: Box::new(left.clone()),
ops: ops.to_vec(),
comparators: comparators.to_vec(),
},
);
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&cmp, 0) {
if let Ok(content) = generator.generate() {
return Some(content);
}
}
None
}
/// E711, E712
pub fn literal_comparisons(
checker: &mut Checker,
expr: &Expr,
left: &Expr,
ops: &[Cmpop],
comparators: &[Expr],
check_none_comparisons: bool,
check_true_false_comparisons: bool,
) {
// Mapping from (bad operator index) to (replacement operator). As we iterate
// through the list of operators, we apply "dummy" fixes for each error,
// then replace the entire expression at the end with one "real" fix, to
// avoid conflicts.
let mut bad_ops: FnvHashMap<usize, Cmpop> = FnvHashMap::default();
let mut checks: Vec<Check> = vec![];
let op = ops.first().unwrap();
let comparator = left;
// Check `left`.
if check_none_comparisons
&& matches!(
comparator.node,
ExprKind::Constant {
value: Constant::None,
kind: None
}
)
{
if matches!(op, Cmpop::Eq) {
let mut check = Check::new(
CheckKind::NoneComparison(RejectedCmpop::Eq),
Range::from_located(comparator),
);
if checker.patch() {
// Dummy replacement
check.amend(Fix::dummy(expr.location));
bad_ops.insert(0, Cmpop::Is);
}
checks.push(check);
}
if matches!(op, Cmpop::NotEq) {
let mut check = Check::new(
CheckKind::NoneComparison(RejectedCmpop::NotEq),
Range::from_located(comparator),
);
if checker.patch() {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(0, Cmpop::IsNot);
}
checks.push(check);
}
}
if check_true_false_comparisons {
if let ExprKind::Constant {
value: Constant::Bool(value),
kind: None,
} = comparator.node
{
if matches!(op, Cmpop::Eq) {
let mut check = Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
Range::from_located(comparator),
);
if checker.patch() {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(0, Cmpop::Is);
}
checks.push(check);
}
if matches!(op, Cmpop::NotEq) {
let mut check = Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
Range::from_located(comparator),
);
if checker.patch() {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(0, Cmpop::IsNot);
}
checks.push(check);
}
}
}
// Check each comparator in order.
for (idx, (op, comparator)) in izip!(ops, comparators).enumerate() {
if check_none_comparisons
&& matches!(
comparator.node,
ExprKind::Constant {
value: Constant::None,
kind: None
}
)
{
if matches!(op, Cmpop::Eq) {
let mut check = Check::new(
CheckKind::NoneComparison(RejectedCmpop::Eq),
Range::from_located(comparator),
);
if checker.patch() {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(idx, Cmpop::Is);
}
checks.push(check);
}
if matches!(op, Cmpop::NotEq) {
let mut check = Check::new(
CheckKind::NoneComparison(RejectedCmpop::NotEq),
Range::from_located(comparator),
);
if checker.patch() {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(idx, Cmpop::IsNot);
}
checks.push(check);
}
}
if check_true_false_comparisons {
if let ExprKind::Constant {
value: Constant::Bool(value),
kind: None,
} = comparator.node
{
if matches!(op, Cmpop::Eq) {
let mut check = Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
Range::from_located(comparator),
);
if checker.patch() {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(idx, Cmpop::Is);
}
checks.push(check);
}
if matches!(op, Cmpop::NotEq) {
let mut check = Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
Range::from_located(comparator),
);
if checker.patch() {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(idx, Cmpop::IsNot);
}
checks.push(check);
}
}
}
}
if !bad_ops.is_empty() {
// Replace the entire comparison expression.
let ops = ops
.iter()
.enumerate()
.map(|(idx, op)| bad_ops.get(&idx).unwrap_or(op))
.cloned()
.collect::<Vec<_>>();
if let Some(content) = compare(left, &ops, comparators) {
if let Some(check) = checks.last_mut() {
check.fix = Some(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
}
}
checker.add_checks(checks.into_iter());
}

View File

@@ -223,8 +223,11 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
if checker.patch() {
// Delete the blank line after the docstring.
check.amend(Fix::deletion(
Location::new(docstring.location.row() + 1, 0),
Location::new(docstring.location.row() + 1 + blank_lines_after, 0),
Location::new(docstring.end_location.unwrap().row() + 1, 0),
Location::new(
docstring.end_location.unwrap().row() + 1 + blank_lines_after,
0,
),
));
}
checker.add_check(check);
@@ -1359,7 +1362,10 @@ static GOOGLE_ARGS_REGEX: Lazy<Regex> =
fn args_section(checker: &mut Checker, definition: &Definition, context: &SectionContext) {
let mut args_sections: Vec<String> = vec![];
for line in textwrap::dedent(&context.following_lines.join("\n")).lines() {
for line in textwrap::dedent(&context.following_lines.join("\n"))
.trim()
.lines()
{
if line
.chars()
.next()

View File

@@ -55,7 +55,10 @@ pub fn unused_variables(scope: &Scope, dummy_variable_rgx: &Regex) -> Vec<Check>
if matches!(
scope.kind,
ScopeKind::Function(FunctionScope { uses_locals: true })
ScopeKind::Function(FunctionScope {
uses_locals: true,
..
})
) {
return checks;
}
@@ -64,9 +67,9 @@ pub fn unused_variables(scope: &Scope, dummy_variable_rgx: &Regex) -> Vec<Check>
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Assignment)
&& !dummy_variable_rgx.is_match(name)
&& name != "__tracebackhide__"
&& name != "__traceback_info__"
&& name != "__traceback_supplement__"
&& name != &"__tracebackhide__"
&& name != &"__traceback_info__"
&& name != &"__traceback_supplement__"
{
checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),

View File

@@ -90,32 +90,12 @@ pub fn useless_metaclass_type(targets: &[Expr], value: &Expr, location: Range) -
None
}
/// U002
pub fn unnecessary_abspath(func: &Expr, args: &[Expr], location: Range) -> Option<Check> {
// Validate the arguments.
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &args[0].node {
if id == "__file__" {
match &func.node {
ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => {
if id == "abspath" {
return Some(Check::new(CheckKind::UnnecessaryAbspath, location));
}
}
_ => {}
}
}
}
}
None
}
/// U004
pub fn useless_object_inheritance(name: &str, bases: &[Expr], scope: &Scope) -> Option<Check> {
for expr in bases {
if let ExprKind::Name { id, .. } = &expr.node {
if id == "object" {
match scope.values.get(id) {
match scope.values.get(&id.as_str()) {
None
| Some(Binding {
kind: BindingKind::Builtin,

View File

@@ -1,7 +1,6 @@
pub use deprecated_unittest_alias::deprecated_unittest_alias;
pub use super_call_with_parameters::super_call_with_parameters;
pub use type_of_primitive::type_of_primitive;
pub use unnecessary_abspath::unnecessary_abspath;
pub use unnecessary_encode_utf8::unnecessary_encode_utf8;
pub use unnecessary_future_import::unnecessary_future_import;
pub use unnecessary_lru_cache_params::unnecessary_lru_cache_params;
@@ -13,7 +12,6 @@ pub use useless_object_inheritance::useless_object_inheritance;
mod deprecated_unittest_alias;
mod super_call_with_parameters;
mod type_of_primitive;
mod unnecessary_abspath;
mod unnecessary_encode_utf8;
mod unnecessary_future_import;
mod unnecessary_lru_cache_params;

View File

@@ -1,20 +0,0 @@
use rustpython_ast::Expr;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::pyupgrade::checks;
/// U002
pub fn unnecessary_abspath(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let Some(mut check) = checks::unnecessary_abspath(func, args, Range::from_located(expr)) {
if checker.patch() {
check.amend(Fix::replacement(
"__file__".to_string(),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}
}

View File

@@ -12,7 +12,9 @@ use regex::Regex;
use crate::checks_gen::CheckCodePrefix;
use crate::settings::pyproject::load_options;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::{flake8_annotations, flake8_bugbear, flake8_quotes, fs, isort, pep8_naming};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, pep8_naming,
};
#[derive(Debug)]
pub struct Configuration {
@@ -32,6 +34,7 @@ pub struct Configuration {
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_bugbear: flake8_bugbear::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings,
pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings,
pub isort: isort::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
}
@@ -65,8 +68,8 @@ static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
impl Configuration {
pub fn from_pyproject(
pyproject: &Option<PathBuf>,
project_root: &Option<PathBuf>,
pyproject: Option<&PathBuf>,
project_root: Option<&PathBuf>,
) -> Result<Self> {
let options = load_options(pyproject)?;
Ok(Configuration {
@@ -142,6 +145,10 @@ impl Configuration {
.flake8_quotes
.map(flake8_quotes::settings::Settings::from_options)
.unwrap_or_default(),
flake8_tidy_imports: options
.flake8_tidy_imports
.map(flake8_tidy_imports::settings::Settings::from_options)
.unwrap_or_default(),
isort: options
.isort
.map(isort::settings::Settings::from_options)

View File

@@ -13,7 +13,9 @@ use crate::checks::CheckCode;
use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
use crate::settings::configuration::Configuration;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::{flake8_annotations, flake8_bugbear, flake8_quotes, isort, pep8_naming};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, pep8_naming,
};
pub mod configuration;
pub mod options;
@@ -35,6 +37,7 @@ pub struct Settings {
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_bugbear: flake8_bugbear::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings,
pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings,
pub isort: isort::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
}
@@ -54,6 +57,7 @@ impl Settings {
flake8_annotations: config.flake8_annotations,
flake8_bugbear: config.flake8_bugbear,
flake8_quotes: config.flake8_quotes,
flake8_tidy_imports: config.flake8_tidy_imports,
isort: config.isort,
line_length: config.line_length,
pep8_naming: config.pep8_naming,
@@ -76,6 +80,7 @@ impl Settings {
flake8_annotations: Default::default(),
flake8_bugbear: Default::default(),
flake8_quotes: Default::default(),
flake8_tidy_imports: Default::default(),
isort: Default::default(),
pep8_naming: Default::default(),
}
@@ -94,6 +99,7 @@ impl Settings {
flake8_annotations: Default::default(),
flake8_bugbear: Default::default(),
flake8_quotes: Default::default(),
flake8_tidy_imports: Default::default(),
isort: Default::default(),
pep8_naming: Default::default(),
}
@@ -116,6 +122,7 @@ impl Hash for Settings {
self.flake8_annotations.hash(state);
self.flake8_bugbear.hash(state);
self.flake8_quotes.hash(state);
self.flake8_tidy_imports.hash(state);
self.isort.hash(state);
self.pep8_naming.hash(state);
}

View File

@@ -5,7 +5,9 @@ use serde::{Deserialize, Serialize};
use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::PythonVersion;
use crate::{flake8_annotations, flake8_bugbear, flake8_quotes, isort, pep8_naming};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, pep8_naming,
};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
@@ -25,6 +27,7 @@ pub struct Options {
pub flake8_annotations: Option<flake8_annotations::settings::Options>,
pub flake8_bugbear: Option<flake8_bugbear::settings::Options>,
pub flake8_quotes: Option<flake8_quotes::settings::Options>,
pub flake8_tidy_imports: Option<flake8_tidy_imports::settings::Options>,
pub isort: Option<isort::settings::Options>,
pub pep8_naming: Option<pep8_naming::settings::Options>,
// Tables are required to go last.

View File

@@ -36,7 +36,7 @@ fn parse_pyproject_toml(path: &Path) -> Result<Pyproject> {
toml::from_str(&contents).map_err(|e| e.into())
}
pub fn find_pyproject_toml(path: &Option<PathBuf>) -> Option<PathBuf> {
pub fn find_pyproject_toml(path: Option<&PathBuf>) -> Option<PathBuf> {
if let Some(path) = path {
let path_pyproject_toml = path.join("pyproject.toml");
if path_pyproject_toml.is_file() {
@@ -80,7 +80,7 @@ pub fn find_project_root(sources: &[PathBuf]) -> Option<PathBuf> {
None
}
pub fn load_options(pyproject: &Option<PathBuf>) -> Result<Options> {
pub fn load_options(pyproject: Option<&PathBuf>) -> Result<Options> {
match pyproject {
Some(pyproject) => Ok(parse_pyproject_toml(pyproject)?
.tool
@@ -105,11 +105,12 @@ mod tests {
use crate::checks_gen::CheckCodePrefix;
use crate::flake8_quotes::settings::Quote;
use crate::flake8_tidy_imports::settings::Strictness;
use crate::settings::pyproject::{
find_project_root, find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools,
};
use crate::settings::types::PatternPrefixPair;
use crate::{flake8_bugbear, flake8_quotes, pep8_naming};
use crate::{flake8_bugbear, flake8_quotes, flake8_tidy_imports, pep8_naming};
#[test]
fn deserialize() -> Result<()> {
@@ -148,6 +149,7 @@ mod tests {
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
})
@@ -180,6 +182,7 @@ line-length = 79
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
})
@@ -212,6 +215,7 @@ exclude = ["foo.py"]
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
})
@@ -244,6 +248,7 @@ select = ["E501"]
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
})
@@ -277,6 +282,7 @@ ignore = ["E501"]
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
isort: None,
pep8_naming: None,
})
@@ -323,7 +329,7 @@ other-attribute = 1
assert_eq!(project_root, cwd.join("resources/test/fixtures"));
let path =
find_pyproject_toml(&Some(project_root)).expect("Unable to find pyproject.toml.");
find_pyproject_toml(Some(&project_root)).expect("Unable to find pyproject.toml.");
assert_eq!(path, cwd.join("resources/test/fixtures/pyproject.toml"));
let pyproject = parse_pyproject_toml(&path)?;
@@ -366,6 +372,9 @@ other-attribute = 1
docstring_quotes: Some(Quote::Double),
avoid_escape: Some(true),
}),
flake8_tidy_imports: Some(flake8_tidy_imports::settings::Options {
ban_relative_imports: Some(Strictness::Parents)
}),
isort: None,
pep8_naming: Some(pep8_naming::settings::Options {
ignore_names: Some(vec![
@@ -382,7 +391,10 @@ other-attribute = 1
"longMessage".to_string(),
"maxDiff".to_string(),
]),
classmethod_decorators: Some(vec!["classmethod".to_string()]),
classmethod_decorators: Some(vec![
"classmethod".to_string(),
"pydantic.validator".to_string()
]),
staticmethod_decorators: Some(vec!["staticmethod".to_string()]),
}),
}

View File

@@ -51,7 +51,7 @@ pub enum FilePattern {
}
impl FilePattern {
pub fn from_user(pattern: &str, project_root: &Option<PathBuf>) -> Self {
pub fn from_user(pattern: &str, project_root: Option<&PathBuf>) -> Self {
let path = Path::new(pattern);
let absolute_path = match project_root {
Some(project_root) => fs::normalize_path_to(path, project_root),
@@ -79,7 +79,7 @@ impl PerFileIgnore {
pub fn new(
pattern: &str,
prefixes: &[CheckCodePrefix],
project_root: &Option<PathBuf>,
project_root: Option<&PathBuf>,
) -> Self {
let pattern = FilePattern::from_user(pattern, project_root);
let codes = BTreeSet::from_iter(prefixes.iter().flat_map(|prefix| prefix.codes()));

View File

@@ -7,7 +7,9 @@ use regex::Regex;
use crate::checks::CheckCode;
use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::{FilePattern, PythonVersion};
use crate::{flake8_annotations, flake8_quotes, isort, pep8_naming, Configuration};
use crate::{
flake8_annotations, flake8_quotes, flake8_tidy_imports, isort, pep8_naming, Configuration,
};
/// Struct to render user-facing exclusion patterns.
#[derive(Debug)]
@@ -50,6 +52,7 @@ pub struct UserConfiguration {
// Plugins
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings,
pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings,
pub isort: isort::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
// Non-settings exposed to the user
@@ -95,6 +98,7 @@ impl UserConfiguration {
target_version: configuration.target_version,
flake8_annotations: configuration.flake8_annotations,
flake8_quotes: configuration.flake8_quotes,
flake8_tidy_imports: configuration.flake8_tidy_imports,
isort: configuration.isort,
pep8_naming: configuration.pep8_naming,
project_root,

View File

@@ -38,4 +38,40 @@ expression: checks
row: 146
column: 0
applied: false
- kind:
NoBlankLineBeforeFunction: 1
location:
row: 541
column: 4
end_location:
row: 544
column: 7
fix:
patch:
content: ""
location:
row: 540
column: 0
end_location:
row: 541
column: 0
applied: false
- kind:
NoBlankLineBeforeFunction: 1
location:
row: 563
column: 4
end_location:
row: 566
column: 7
fix:
patch:
content: ""
location:
row: 562
column: 0
end_location:
row: 563
column: 0
applied: false

View File

@@ -38,4 +38,40 @@ expression: checks
row: 148
column: 0
applied: false
- kind:
NoBlankLineAfterFunction: 1
location:
row: 550
column: 4
end_location:
row: 553
column: 7
fix:
patch:
content: ""
location:
row: 554
column: 0
end_location:
row: 555
column: 0
applied: false
- kind:
NoBlankLineAfterFunction: 1
location:
row: 563
column: 4
end_location:
row: 566
column: 7
fix:
patch:
content: ""
location:
row: 567
column: 0
end_location:
row: 568
column: 0
applied: false

View File

@@ -130,4 +130,28 @@ expression: checks
row: 527
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 541
column: 4
end_location:
row: 544
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 550
column: 4
end_location:
row: 553
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 563
column: 4
end_location:
row: 566
column: 7
fix: ~

View File

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 2
column: 14
fix: ~
fix:
patch:
content: res is None
location:
row: 2
column: 3
end_location:
row: 2
column: 14
applied: false
- kind:
NoneComparison: NotEq
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 5
column: 14
fix: ~
fix:
patch:
content: res is not None
location:
row: 5
column: 3
end_location:
row: 5
column: 14
applied: false
- kind:
NoneComparison: Eq
location:
@@ -28,7 +46,16 @@ expression: checks
end_location:
row: 8
column: 7
fix: ~
fix:
patch:
content: None is res
location:
row: 8
column: 3
end_location:
row: 8
column: 14
applied: false
- kind:
NoneComparison: NotEq
location:
@@ -37,7 +64,16 @@ expression: checks
end_location:
row: 11
column: 7
fix: ~
fix:
patch:
content: None is not res
location:
row: 11
column: 3
end_location:
row: 11
column: 14
applied: false
- kind:
NoneComparison: Eq
location:
@@ -46,7 +82,16 @@ expression: checks
end_location:
row: 14
column: 17
fix: ~
fix:
patch:
content: "res[1] is None"
location:
row: 14
column: 3
end_location:
row: 14
column: 17
applied: false
- kind:
NoneComparison: NotEq
location:
@@ -55,7 +100,16 @@ expression: checks
end_location:
row: 17
column: 17
fix: ~
fix:
patch:
content: "res[1] is not None"
location:
row: 17
column: 3
end_location:
row: 17
column: 17
applied: false
- kind:
NoneComparison: NotEq
location:
@@ -64,7 +118,16 @@ expression: checks
end_location:
row: 20
column: 7
fix: ~
fix:
patch:
content: "None is not res[1]"
location:
row: 20
column: 3
end_location:
row: 20
column: 17
applied: false
- kind:
NoneComparison: Eq
location:
@@ -73,5 +136,50 @@ expression: checks
end_location:
row: 23
column: 7
fix: ~
fix:
patch:
content: "None is res[1]"
location:
row: 23
column: 3
end_location:
row: 23
column: 17
applied: false
- kind:
NoneComparison: Eq
location:
row: 26
column: 8
end_location:
row: 26
column: 12
fix:
patch:
content: ""
location:
row: 26
column: 3
end_location:
row: 26
column: 3
applied: false
- kind:
NoneComparison: NotEq
location:
row: 26
column: 16
end_location:
row: 26
column: 20
fix:
patch:
content: x is None is not None
location:
row: 26
column: 3
end_location:
row: 26
column: 20
applied: false

View File

@@ -1,5 +1,6 @@
---
source: src/linter.rs
assertion_line: 531
expression: checks
---
- kind:
@@ -12,7 +13,16 @@ expression: checks
end_location:
row: 2
column: 14
fix: ~
fix:
patch:
content: res is True
location:
row: 2
column: 3
end_location:
row: 2
column: 14
applied: false
- kind:
TrueFalseComparison:
- false
@@ -23,7 +33,16 @@ expression: checks
end_location:
row: 5
column: 15
fix: ~
fix:
patch:
content: res is not False
location:
row: 5
column: 3
end_location:
row: 5
column: 15
applied: false
- kind:
TrueFalseComparison:
- true
@@ -34,7 +53,16 @@ expression: checks
end_location:
row: 8
column: 7
fix: ~
fix:
patch:
content: True is not res
location:
row: 8
column: 3
end_location:
row: 8
column: 14
applied: false
- kind:
TrueFalseComparison:
- false
@@ -45,7 +73,16 @@ expression: checks
end_location:
row: 11
column: 8
fix: ~
fix:
patch:
content: False is res
location:
row: 11
column: 3
end_location:
row: 11
column: 15
applied: false
- kind:
TrueFalseComparison:
- true
@@ -56,7 +93,16 @@ expression: checks
end_location:
row: 14
column: 17
fix: ~
fix:
patch:
content: "res[1] is True"
location:
row: 14
column: 3
end_location:
row: 14
column: 17
applied: false
- kind:
TrueFalseComparison:
- false
@@ -67,7 +113,16 @@ expression: checks
end_location:
row: 17
column: 18
fix: ~
fix:
patch:
content: "res[1] is not False"
location:
row: 17
column: 3
end_location:
row: 17
column: 18
applied: false
- kind:
TrueFalseComparison:
- true
@@ -78,7 +133,16 @@ expression: checks
end_location:
row: 20
column: 23
fix: ~
fix:
patch:
content: cond is True
location:
row: 20
column: 11
end_location:
row: 20
column: 23
applied: false
- kind:
TrueFalseComparison:
- false
@@ -89,7 +153,16 @@ expression: checks
end_location:
row: 20
column: 48
fix: ~
fix:
patch:
content: cond is False
location:
row: 20
column: 35
end_location:
row: 20
column: 48
applied: false
- kind:
TrueFalseComparison:
- true
@@ -100,5 +173,54 @@ expression: checks
end_location:
row: 22
column: 8
fix: ~
fix:
patch:
content: True is TrueElement
location:
row: 22
column: 3
end_location:
row: 22
column: 24
applied: false
- kind:
TrueFalseComparison:
- true
- Eq
location:
row: 25
column: 10
end_location:
row: 25
column: 14
fix:
patch:
content: ""
location:
row: 25
column: 3
end_location:
row: 25
column: 3
applied: false
- kind:
TrueFalseComparison:
- false
- NotEq
location:
row: 25
column: 18
end_location:
row: 25
column: 23
fix:
patch:
content: res is True is not False
location:
row: 25
column: 3
end_location:
row: 25
column: 23
applied: false

View File

@@ -26,20 +26,4 @@ expression: checks
row: 7
column: 29
fix: ~
- kind: DoNotAssignLambda
location:
row: 12
column: 0
end_location:
row: 12
column: 27
fix: ~
- kind: DoNotAssignLambda
location:
row: 16
column: 0
end_location:
row: 16
column: 25
fix: ~

View File

@@ -4,18 +4,34 @@ expression: checks
---
- kind: InvalidFirstArgumentNameForMethod
location:
row: 5
row: 7
column: 19
end_location:
row: 5
row: 7
column: 23
fix: ~
- kind: InvalidFirstArgumentNameForMethod
location:
row: 10
row: 12
column: 29
end_location:
row: 10
row: 12
column: 33
fix: ~
- kind: InvalidFirstArgumentNameForMethod
location:
row: 27
column: 14
end_location:
row: 27
column: 17
fix: ~
- kind: InvalidFirstArgumentNameForMethod
location:
row: 31
column: 14
end_location:
row: 31
column: 17
fix: ~

View File

@@ -5,19 +5,19 @@ expression: checks
- kind:
NonLowercaseVariableInFunction: Camel
location:
row: 7
row: 11
column: 4
end_location:
row: 7
row: 11
column: 9
fix: ~
- kind:
NonLowercaseVariableInFunction: CONSTANT
location:
row: 8
row: 12
column: 4
end_location:
row: 8
row: 12
column: 12
fix: ~

View File

@@ -8,7 +8,16 @@ expression: checks
row: 9
column: 0
end_location:
row: 11
row: 13
column: 0
fix: ~
- kind:
ErrorSuffixOnExceptionName: E
location:
row: 17
column: 0
end_location:
row: 19
column: 0
fix: ~

View File

@@ -1,56 +0,0 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryAbspath
location:
row: 3
column: 4
end_location:
row: 3
column: 21
fix:
patch:
content: __file__
location:
row: 3
column: 4
end_location:
row: 3
column: 21
applied: false
- kind: UnnecessaryAbspath
location:
row: 9
column: 4
end_location:
row: 9
column: 29
fix:
patch:
content: __file__
location:
row: 9
column: 4
end_location:
row: 9
column: 29
applied: false
- kind: UnnecessaryAbspath
location:
row: 15
column: 4
end_location:
row: 15
column: 26
fix:
patch:
content: __file__
location:
row: 15
column: 4
end_location:
row: 15
column: 26
applied: false