Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aafddae644 | ||
|
|
749df87de0 | ||
|
|
d67db33f22 | ||
|
|
f0a54716e5 | ||
|
|
c59e1ff0b5 | ||
|
|
ecf858cf16 | ||
|
|
8063aee006 | ||
|
|
f1fee5d240 | ||
|
|
d3155560df | ||
|
|
90bfc4ec4d | ||
|
|
b04a6a3f7c | ||
|
|
8ec14e7ee2 | ||
|
|
17c5cd7c42 | ||
|
|
7d8360a1de | ||
|
|
910ee523dd | ||
|
|
72e35a535e | ||
|
|
b4e1563517 | ||
|
|
5717cc97d7 | ||
|
|
2c89a19f76 | ||
|
|
82fea36bb3 | ||
|
|
63d63e8c12 |
5
.github/workflows/ci.yaml
vendored
5
.github/workflows/ci.yaml
vendored
@@ -6,6 +6,11 @@ on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
cargo_build:
|
||||
name: "cargo build"
|
||||
|
||||
3
.github/workflows/flake8-to-ruff.yaml
vendored
3
.github/workflows/flake8-to-ruff.yaml
vendored
@@ -10,6 +10,9 @@ env:
|
||||
PACKAGE_NAME: flake8-to-ruff
|
||||
CRATE_NAME: flake8_to_ruff
|
||||
PYTHON_VERSION: "3.7" # to build abi3 wheels
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
macos-x86_64:
|
||||
|
||||
3
.github/workflows/ruff.yaml
vendored
3
.github/workflows/ruff.yaml
vendored
@@ -12,6 +12,9 @@ concurrency:
|
||||
env:
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.7" # to build abi3 wheels
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
macos-x86_64:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.121
|
||||
rev: v0.0.125
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -930,7 +930,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.121-dev.0"
|
||||
version = "0.0.125-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
@@ -2238,7 +2238,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.121"
|
||||
version = "0.0.125"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
@@ -2287,7 +2287,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.121"
|
||||
version = "0.0.125"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.121"
|
||||
version = "0.0.125"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
400
LICENSE
400
LICENSE
@@ -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
123
README.md
@@ -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.
|
||||
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.121"
|
||||
version = "0.0.125"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.121"
|
||||
version = "0.0.125"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.121-dev.0"
|
||||
version = "0.0.125-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
2
resources/test/fixtures/E501.py
vendored
2
resources/test/fixtures/E501.py
vendored
@@ -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
|
||||
|
||||
|
||||
3
resources/test/fixtures/E711.py
vendored
3
resources/test/fixtures/E711.py
vendored
@@ -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
|
||||
|
||||
3
resources/test/fixtures/E712.py
vendored
3
resources/test/fixtures/E712.py
vendored
@@ -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
|
||||
|
||||
4
resources/test/fixtures/E731.py
vendored
4
resources/test/fixtures/E731.py
vendored
@@ -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
12
resources/test/fixtures/I252.py
vendored
Normal 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
|
||||
10
resources/test/fixtures/N805.py
vendored
10
resources/test/fixtures/N805.py
vendored
@@ -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):
|
||||
...
|
||||
|
||||
|
||||
15
resources/test/fixtures/U002.py
vendored
15
resources/test/fixtures/U002.py
vendored
@@ -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__)
|
||||
7
resources/test/fixtures/isort/add_newline_before_comments.py
vendored
Normal file
7
resources/test/fixtures/isort/add_newline_before_comments.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import os
|
||||
# This is a comment in the same section, so we need to add one newline.
|
||||
import sys
|
||||
import numpy as np
|
||||
# This is a comment, but it starts a new section, so we don't need to add a newline
|
||||
# before it.
|
||||
import leading_prefix
|
||||
25
resources/test/fixtures/isort/comments.py
vendored
Normal file
25
resources/test/fixtures/isort/comments.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Comment 1
|
||||
# Comment 2
|
||||
import D
|
||||
|
||||
# Comment 3a
|
||||
import C
|
||||
|
||||
# Comment 3b
|
||||
import C
|
||||
|
||||
import B # Comment 4
|
||||
|
||||
# Comment 5
|
||||
|
||||
# Comment 6
|
||||
from A import (
|
||||
a, # Comment 7
|
||||
b,
|
||||
c, # Comment 8
|
||||
)
|
||||
from A import (
|
||||
a, # Comment 9
|
||||
b, # Comment 10
|
||||
c, # Comment 11
|
||||
)
|
||||
4
resources/test/fixtures/isort/fit_line_length_comment.py
vendored
Normal file
4
resources/test/fixtures/isort/fit_line_length_comment.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import a
|
||||
# Don't take this comment into account when determining whether the next import can fit on one line.
|
||||
from b import c
|
||||
from d import e # Do take this comment into account when determining whether the next import can fit on one line.
|
||||
11
resources/test/fixtures/isort/preserve_comment_order.py
vendored
Normal file
11
resources/test/fixtures/isort/preserve_comment_order.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import io
|
||||
# Old MacDonald had a farm,
|
||||
# EIEIO
|
||||
# And on his farm he had a cow,
|
||||
# EIEIO
|
||||
# With a moo-moo here and a moo-moo there
|
||||
# Here a moo, there a moo, everywhere moo-moo
|
||||
# Old MacDonald had a farm,
|
||||
# EIEIO
|
||||
from errno import EIO
|
||||
import abc
|
||||
2
resources/test/fixtures/isort/type_comments.py
vendored
Normal file
2
resources/test/fixtures/isort/type_comments.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import A # type: ignore
|
||||
from B import C # type: ignore
|
||||
4
resources/test/fixtures/pyproject.toml
vendored
4
resources/test/fixtures/pyproject.toml
vendored
@@ -33,7 +33,11 @@ ignore-names = [
|
||||
]
|
||||
classmethod-decorators = [
|
||||
"classmethod",
|
||||
"pydantic.validator",
|
||||
]
|
||||
staticmethod-decorators = [
|
||||
"staticmethod",
|
||||
]
|
||||
|
||||
[tool.ruff.flake8-tidy-imports]
|
||||
ban-relative-imports = "parents"
|
||||
|
||||
@@ -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:]]))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.121"
|
||||
version = "0.0.125"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
@@ -86,7 +86,7 @@ pub enum BindingKind {
|
||||
Definition,
|
||||
Export(Vec<String>),
|
||||
FutureImportation,
|
||||
StarImportation,
|
||||
StarImportation(Option<usize>, Option<String>),
|
||||
Importation(String, String, BindingContext),
|
||||
FromImportation(String, String, BindingContext),
|
||||
SubmoduleImportation(String, String, BindingContext),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
375
src/check_ast.rs
375
src/check_ast.rs
@@ -37,7 +37,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;
|
||||
@@ -222,7 +223,7 @@ where
|
||||
for name in names {
|
||||
for scope in self.scopes.iter_mut().skip(GLOBAL_SCOPE_INDEX + 1) {
|
||||
scope.values.insert(
|
||||
name.to_string(),
|
||||
name,
|
||||
Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
used: Some((global_scope_id, Range::from_located(stmt))),
|
||||
@@ -309,6 +310,8 @@ where
|
||||
name,
|
||||
decorator_list,
|
||||
args,
|
||||
&self.from_imports,
|
||||
&self.import_aliases,
|
||||
&self.settings.pep8_naming,
|
||||
)
|
||||
{
|
||||
@@ -322,6 +325,8 @@ where
|
||||
name,
|
||||
decorator_list,
|
||||
args,
|
||||
&self.from_imports,
|
||||
&self.import_aliases,
|
||||
&self.settings.pep8_naming,
|
||||
) {
|
||||
self.add_check(check);
|
||||
@@ -397,7 +402,7 @@ where
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.add_binding(
|
||||
name.to_string(),
|
||||
name,
|
||||
Binding {
|
||||
kind: BindingKind::Definition,
|
||||
used: None,
|
||||
@@ -502,7 +507,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 +529,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 +662,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 +699,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 +713,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,7 +724,10 @@ 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),
|
||||
));
|
||||
}
|
||||
@@ -746,7 +751,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 +783,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 +877,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 +920,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 +942,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 +1038,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 +1151,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,12 +1446,6 @@ 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);
|
||||
}
|
||||
@@ -1521,16 +1536,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 +1874,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 +1889,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 +1908,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 +1923,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 +1967,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 +2032,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 +2068,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 +2078,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 +2106,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 +2183,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 +2226,112 @@ 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(..)) {
|
||||
pep8_naming::plugins::non_lowercase_variable_in_function(self, expr, parent, id)
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::N806) {
|
||||
if matches!(self.current_scope().kind, ScopeKind::Function(..)) {
|
||||
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 +2345,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 +2481,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 +2510,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 +2525,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 +2545,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 +2583,7 @@ impl<'a> Checker<'a> {
|
||||
.or_default()
|
||||
.push(full_name);
|
||||
}
|
||||
_ => {}
|
||||
_ => unreachable!("Already filtered on BindingKind."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
..
|
||||
|
||||
@@ -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![],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Settings for the `flake-quotes` plugin.
|
||||
//! Settings for the `flake8-quotes` plugin.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
26
src/flake8_tidy_imports/checks.rs
Normal file
26
src/flake8_tidy_imports/checks.rs
Normal 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
|
||||
}
|
||||
49
src/flake8_tidy_imports/mod.rs
Normal file
49
src/flake8_tidy_imports/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
37
src/flake8_tidy_imports/settings.rs
Normal file
37
src/flake8_tidy_imports/settings.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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: ~
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
14
src/fs.rs
14
src/fs.rs
@@ -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()));
|
||||
|
||||
@@ -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
|
||||
|
||||
40
src/isort/comments.rs
Normal file
40
src/isort/comments.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::Range;
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Comment<'a> {
|
||||
pub value: Cow<'a, str>,
|
||||
pub location: Location,
|
||||
pub end_location: Location,
|
||||
}
|
||||
|
||||
/// Collect all comments in an import block.
|
||||
pub fn collect_comments<'a>(range: &Range, locator: &'a SourceCodeLocator) -> Vec<Comment<'a>> {
|
||||
let contents = locator.slice_source_code_range(range);
|
||||
lexer::make_tokenizer(&contents)
|
||||
.flatten()
|
||||
.filter_map(|(start, tok, end)| {
|
||||
if matches!(tok, Tok::Comment) {
|
||||
let start = helpers::to_absolute(&start, &range.location);
|
||||
let end = helpers::to_absolute(&end, &range.location);
|
||||
Some(Comment {
|
||||
value: locator.slice_source_code_range(&Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
}),
|
||||
location: start,
|
||||
end_location: end,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
182
src/isort/format.rs
Normal file
182
src/isort/format.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
use crate::isort::types::{AliasData, CommentSet, ImportFromData, Importable};
|
||||
|
||||
// Hard-code four-space indentation for the imports themselves, to match Black.
|
||||
const INDENT: &str = " ";
|
||||
|
||||
// Guess a capacity to use for string allocation.
|
||||
const CAPACITY: usize = 200;
|
||||
|
||||
/// Add a plain import statement to the `RopeBuilder`.
|
||||
pub fn format_import(alias: &AliasData, comments: &CommentSet, is_first: bool) -> String {
|
||||
let mut output = String::with_capacity(CAPACITY);
|
||||
if !is_first && !comments.atop.is_empty() {
|
||||
output.push('\n');
|
||||
}
|
||||
for comment in &comments.atop {
|
||||
output.push_str(comment);
|
||||
output.push('\n');
|
||||
}
|
||||
if let Some(asname) = alias.asname {
|
||||
output.push_str("import ");
|
||||
output.push_str(alias.name);
|
||||
output.push_str(" as ");
|
||||
output.push_str(asname);
|
||||
} else {
|
||||
output.push_str("import ");
|
||||
output.push_str(alias.name);
|
||||
}
|
||||
for comment in &comments.inline {
|
||||
output.push_str(" ");
|
||||
output.push_str(comment);
|
||||
}
|
||||
output.push('\n');
|
||||
output
|
||||
}
|
||||
|
||||
/// Add an import-from statement to the `RopeBuilder`.
|
||||
pub fn format_import_from(
|
||||
import_from: &ImportFromData,
|
||||
comments: &CommentSet,
|
||||
aliases: &[(AliasData, CommentSet)],
|
||||
line_length: &usize,
|
||||
is_first: bool,
|
||||
) -> String {
|
||||
// We can only inline if: (1) none of the aliases have atop comments, and (3)
|
||||
// only the last alias (if any) has inline comments.
|
||||
if aliases
|
||||
.iter()
|
||||
.all(|(_, CommentSet { atop, .. })| atop.is_empty())
|
||||
&& aliases
|
||||
.iter()
|
||||
.rev()
|
||||
.skip(1)
|
||||
.all(|(_, CommentSet { inline, .. })| inline.is_empty())
|
||||
{
|
||||
let (single_line, import_length) =
|
||||
format_single_line(import_from, comments, aliases, is_first);
|
||||
if import_length <= *line_length {
|
||||
return single_line;
|
||||
}
|
||||
}
|
||||
|
||||
format_multi_line(import_from, comments, aliases, is_first)
|
||||
}
|
||||
|
||||
/// Format an import-from statement in single-line format.
|
||||
///
|
||||
/// This method assumes that the output source code is syntactically valid.
|
||||
fn format_single_line(
|
||||
import_from: &ImportFromData,
|
||||
comments: &CommentSet,
|
||||
aliases: &[(AliasData, CommentSet)],
|
||||
is_first: bool,
|
||||
) -> (String, usize) {
|
||||
let mut output = String::with_capacity(CAPACITY);
|
||||
let mut line_length = 0;
|
||||
|
||||
if !is_first && !comments.atop.is_empty() {
|
||||
output.push('\n');
|
||||
}
|
||||
for comment in &comments.atop {
|
||||
output.push_str(comment);
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
let module_name = import_from.module_name();
|
||||
output.push_str("from ");
|
||||
output.push_str(&module_name);
|
||||
output.push_str(" import ");
|
||||
line_length += 5 + module_name.len() + 8;
|
||||
|
||||
for (index, (AliasData { name, asname }, comments)) in aliases.iter().enumerate() {
|
||||
if let Some(asname) = asname {
|
||||
output.push_str(name);
|
||||
output.push_str(" as ");
|
||||
output.push_str(asname);
|
||||
line_length += name.len() + 4 + asname.len();
|
||||
} else {
|
||||
output.push_str(name);
|
||||
line_length += name.len();
|
||||
}
|
||||
if index < aliases.len() - 1 {
|
||||
output.push_str(", ");
|
||||
line_length += 2;
|
||||
}
|
||||
|
||||
for comment in &comments.inline {
|
||||
output.push(' ');
|
||||
output.push(' ');
|
||||
output.push_str(comment);
|
||||
line_length += 2 + comment.len();
|
||||
}
|
||||
}
|
||||
|
||||
for comment in &comments.inline {
|
||||
output.push(' ');
|
||||
output.push(' ');
|
||||
output.push_str(comment);
|
||||
line_length += 2 + comment.len();
|
||||
}
|
||||
|
||||
output.push('\n');
|
||||
|
||||
(output, line_length)
|
||||
}
|
||||
|
||||
/// Format an import-from statement in multi-line format.
|
||||
fn format_multi_line(
|
||||
import_from: &ImportFromData,
|
||||
comments: &CommentSet,
|
||||
aliases: &[(AliasData, CommentSet)],
|
||||
is_first: bool,
|
||||
) -> String {
|
||||
let mut output = String::with_capacity(CAPACITY);
|
||||
|
||||
if !is_first && !comments.atop.is_empty() {
|
||||
output.push('\n');
|
||||
}
|
||||
for comment in &comments.atop {
|
||||
output.push_str(comment);
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
output.push_str("from ");
|
||||
output.push_str(&import_from.module_name());
|
||||
output.push_str(" import ");
|
||||
output.push('(');
|
||||
for comment in &comments.inline {
|
||||
output.push(' ');
|
||||
output.push(' ');
|
||||
output.push_str(comment);
|
||||
}
|
||||
output.push('\n');
|
||||
|
||||
for (AliasData { name, asname }, comments) in aliases {
|
||||
for comment in &comments.atop {
|
||||
output.push_str(INDENT);
|
||||
output.push_str(comment);
|
||||
output.push('\n');
|
||||
}
|
||||
output.push_str(INDENT);
|
||||
if let Some(asname) = asname {
|
||||
output.push_str(name);
|
||||
output.push_str(" as ");
|
||||
output.push_str(asname);
|
||||
} else {
|
||||
output.push_str(name);
|
||||
}
|
||||
output.push(',');
|
||||
|
||||
for comment in &comments.inline {
|
||||
output.push(' ');
|
||||
output.push(' ');
|
||||
output.push_str(comment);
|
||||
}
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
output.push(')');
|
||||
output.push('\n');
|
||||
|
||||
output
|
||||
}
|
||||
391
src/isort/mod.rs
391
src/isort/mod.rs
@@ -1,64 +1,266 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use fnv::FnvHashMap;
|
||||
use itertools::Itertools;
|
||||
use ropey::RopeBuilder;
|
||||
use rustpython_ast::{Stmt, StmtKind};
|
||||
|
||||
use crate::isort::categorize::{categorize, ImportType};
|
||||
use crate::isort::comments::Comment;
|
||||
use crate::isort::sorting::{member_key, module_key};
|
||||
use crate::isort::types::{AliasData, ImportBlock, ImportFromData, Importable, OrderedImportBlock};
|
||||
use crate::isort::types::{
|
||||
AliasData, CommentSet, ImportBlock, ImportFromData, Importable, OrderedImportBlock,
|
||||
};
|
||||
|
||||
mod categorize;
|
||||
mod comments;
|
||||
pub mod format;
|
||||
pub mod plugins;
|
||||
pub mod settings;
|
||||
mod sorting;
|
||||
pub mod track;
|
||||
mod types;
|
||||
|
||||
// Hard-code four-space indentation for the imports themselves, to match Black.
|
||||
const INDENT: &str = " ";
|
||||
#[derive(Debug)]
|
||||
pub struct AnnotatedAliasData<'a> {
|
||||
pub name: &'a str,
|
||||
pub asname: Option<&'a String>,
|
||||
pub atop: Vec<Comment<'a>>,
|
||||
pub inline: Vec<Comment<'a>>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum AnnotatedImport<'a> {
|
||||
Import {
|
||||
names: Vec<AliasData<'a>>,
|
||||
atop: Vec<Comment<'a>>,
|
||||
inline: Vec<Comment<'a>>,
|
||||
},
|
||||
ImportFrom {
|
||||
module: Option<&'a String>,
|
||||
names: Vec<AnnotatedAliasData<'a>>,
|
||||
level: Option<&'a usize>,
|
||||
atop: Vec<Comment<'a>>,
|
||||
inline: Vec<Comment<'a>>,
|
||||
},
|
||||
}
|
||||
|
||||
fn normalize_imports<'a>(imports: &'a [&'a Stmt]) -> ImportBlock<'a> {
|
||||
let mut block: ImportBlock = Default::default();
|
||||
fn annotate_imports<'a>(
|
||||
imports: &'a [&'a Stmt],
|
||||
comments: Vec<Comment<'a>>,
|
||||
) -> Vec<AnnotatedImport<'a>> {
|
||||
let mut annotated = vec![];
|
||||
let mut comments_iter = comments.into_iter().peekable();
|
||||
for import in imports {
|
||||
match &import.node {
|
||||
StmtKind::Import { names } => {
|
||||
for name in names {
|
||||
block.import.insert(AliasData {
|
||||
name: &name.node.name,
|
||||
asname: &name.node.asname,
|
||||
});
|
||||
// Find comments above.
|
||||
let mut atop = vec![];
|
||||
while let Some(comment) =
|
||||
comments_iter.next_if(|comment| comment.location.row() < import.location.row())
|
||||
{
|
||||
atop.push(comment);
|
||||
}
|
||||
|
||||
// Find comments inline.
|
||||
let mut inline = vec![];
|
||||
while let Some(comment) = comments_iter.next_if(|comment| {
|
||||
comment.end_location.row() == import.end_location.unwrap().row()
|
||||
}) {
|
||||
inline.push(comment);
|
||||
}
|
||||
|
||||
annotated.push(AnnotatedImport::Import {
|
||||
names: names
|
||||
.iter()
|
||||
.map(|alias| AliasData {
|
||||
name: &alias.node.name,
|
||||
asname: alias.node.asname.as_ref(),
|
||||
})
|
||||
.collect(),
|
||||
atop,
|
||||
inline,
|
||||
});
|
||||
}
|
||||
StmtKind::ImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
} => {
|
||||
for name in names {
|
||||
if name.node.asname.is_none() {
|
||||
block
|
||||
// Find comments above.
|
||||
let mut atop = vec![];
|
||||
while let Some(comment) =
|
||||
comments_iter.next_if(|comment| comment.location.row() < import.location.row())
|
||||
{
|
||||
atop.push(comment);
|
||||
}
|
||||
|
||||
// Find comments inline.
|
||||
let mut inline = vec![];
|
||||
while let Some(comment) =
|
||||
comments_iter.next_if(|comment| comment.location.row() == import.location.row())
|
||||
{
|
||||
inline.push(comment);
|
||||
}
|
||||
|
||||
// Capture names.
|
||||
let mut aliases = vec![];
|
||||
for alias in names {
|
||||
// Find comments above.
|
||||
let mut alias_atop = vec![];
|
||||
while let Some(comment) = comments_iter
|
||||
.next_if(|comment| comment.location.row() < alias.location.row())
|
||||
{
|
||||
alias_atop.push(comment);
|
||||
}
|
||||
|
||||
// Find comments inline.
|
||||
let mut alias_inline = vec![];
|
||||
while let Some(comment) = comments_iter.next_if(|comment| {
|
||||
comment.end_location.row() == alias.end_location.unwrap().row()
|
||||
}) {
|
||||
alias_inline.push(comment);
|
||||
}
|
||||
|
||||
aliases.push(AnnotatedAliasData {
|
||||
name: &alias.node.name,
|
||||
asname: alias.node.asname.as_ref(),
|
||||
atop: alias_atop,
|
||||
inline: alias_inline,
|
||||
})
|
||||
}
|
||||
|
||||
annotated.push(AnnotatedImport::ImportFrom {
|
||||
module: module.as_ref(),
|
||||
names: aliases,
|
||||
level: level.as_ref(),
|
||||
atop,
|
||||
inline,
|
||||
});
|
||||
}
|
||||
_ => unreachable!("Expected StmtKind::Import | StmtKind::ImportFrom"),
|
||||
}
|
||||
}
|
||||
annotated
|
||||
}
|
||||
|
||||
fn normalize_imports(imports: Vec<AnnotatedImport>) -> ImportBlock {
|
||||
let mut block: ImportBlock = Default::default();
|
||||
for import in imports {
|
||||
match import {
|
||||
AnnotatedImport::Import {
|
||||
names,
|
||||
atop,
|
||||
inline,
|
||||
} => {
|
||||
// Associate the comments with the first alias (best effort).
|
||||
if let Some(name) = names.first() {
|
||||
let entry = block
|
||||
.import
|
||||
.entry(AliasData {
|
||||
name: name.name,
|
||||
asname: name.asname,
|
||||
})
|
||||
.or_default();
|
||||
for comment in atop {
|
||||
entry.atop.push(comment.value);
|
||||
}
|
||||
for comment in inline {
|
||||
entry.inline.push(comment.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Create an entry for every alias.
|
||||
for name in &names {
|
||||
block
|
||||
.import
|
||||
.entry(AliasData {
|
||||
name: name.name,
|
||||
asname: name.asname,
|
||||
})
|
||||
.or_default();
|
||||
}
|
||||
}
|
||||
AnnotatedImport::ImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
atop,
|
||||
inline,
|
||||
} => {
|
||||
// Associate the comments with the first alias (best effort).
|
||||
if let Some(alias) = names.first() {
|
||||
if alias.asname.is_none() {
|
||||
let entry = &mut block
|
||||
.import_from
|
||||
.entry(ImportFromData { module, level })
|
||||
.or_default()
|
||||
.insert(AliasData {
|
||||
name: &name.node.name,
|
||||
asname: &name.node.asname,
|
||||
});
|
||||
.0;
|
||||
for comment in atop {
|
||||
entry.atop.push(comment.value);
|
||||
}
|
||||
for comment in inline {
|
||||
entry.inline.push(comment.value);
|
||||
}
|
||||
} else {
|
||||
block.import_from_as.insert((
|
||||
ImportFromData { module, level },
|
||||
AliasData {
|
||||
name: &name.node.name,
|
||||
asname: &name.node.asname,
|
||||
},
|
||||
));
|
||||
let entry = &mut block
|
||||
.import_from_as
|
||||
.entry((
|
||||
ImportFromData { module, level },
|
||||
AliasData {
|
||||
name: alias.name,
|
||||
asname: alias.asname,
|
||||
},
|
||||
))
|
||||
.or_default();
|
||||
for comment in atop {
|
||||
entry.atop.push(comment.value);
|
||||
}
|
||||
for comment in inline {
|
||||
entry.inline.push(comment.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create an entry for every alias.
|
||||
for alias in names {
|
||||
if alias.asname.is_none() {
|
||||
let entry = block
|
||||
.import_from
|
||||
.entry(ImportFromData { module, level })
|
||||
.or_default()
|
||||
.1
|
||||
.entry(AliasData {
|
||||
name: alias.name,
|
||||
asname: alias.asname,
|
||||
})
|
||||
.or_default();
|
||||
for comment in alias.atop {
|
||||
entry.atop.push(comment.value);
|
||||
}
|
||||
for comment in alias.inline {
|
||||
entry.inline.push(comment.value);
|
||||
}
|
||||
} else {
|
||||
let entry = block
|
||||
.import_from_as
|
||||
.entry((
|
||||
ImportFromData { module, level },
|
||||
AliasData {
|
||||
name: alias.name,
|
||||
asname: alias.asname,
|
||||
},
|
||||
))
|
||||
.or_default();
|
||||
entry
|
||||
.atop
|
||||
.extend(alias.atop.into_iter().map(|comment| comment.value));
|
||||
for comment in alias.inline {
|
||||
entry.inline.push(comment.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Expected StmtKind::Import | StmtKind::ImportFrom"),
|
||||
}
|
||||
}
|
||||
block
|
||||
@@ -73,10 +275,10 @@ fn categorize_imports<'a>(
|
||||
) -> BTreeMap<ImportType, ImportBlock<'a>> {
|
||||
let mut block_by_type: BTreeMap<ImportType, ImportBlock> = Default::default();
|
||||
// Categorize `StmtKind::Import`.
|
||||
for alias in block.import {
|
||||
for (alias, comments) in block.import {
|
||||
let import_type = categorize(
|
||||
&alias.module_base(),
|
||||
&None,
|
||||
None,
|
||||
src,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
@@ -86,7 +288,7 @@ fn categorize_imports<'a>(
|
||||
.entry(import_type)
|
||||
.or_default()
|
||||
.import
|
||||
.insert(alias);
|
||||
.insert(alias, comments);
|
||||
}
|
||||
// Categorize `StmtKind::ImportFrom` (without re-export).
|
||||
for (import_from, aliases) in block.import_from {
|
||||
@@ -105,7 +307,7 @@ fn categorize_imports<'a>(
|
||||
.insert(import_from, aliases);
|
||||
}
|
||||
// Categorize `StmtKind::ImportFrom` (with re-export).
|
||||
for (import_from, alias) in block.import_from_as {
|
||||
for ((import_from, alias), comments) in block.import_from_as {
|
||||
let classification = categorize(
|
||||
&import_from.module_base(),
|
||||
import_from.level,
|
||||
@@ -118,7 +320,7 @@ fn categorize_imports<'a>(
|
||||
.entry(classification)
|
||||
.or_default()
|
||||
.import_from_as
|
||||
.insert((import_from, alias));
|
||||
.insert((import_from, alias), comments);
|
||||
}
|
||||
block_by_type
|
||||
}
|
||||
@@ -131,7 +333,7 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
|
||||
block
|
||||
.import
|
||||
.into_iter()
|
||||
.sorted_by_cached_key(|alias| module_key(alias.name, alias.asname)),
|
||||
.sorted_by_cached_key(|(alias, _)| module_key(alias.name, alias.asname)),
|
||||
);
|
||||
|
||||
// Sort `StmtKind::ImportFrom`.
|
||||
@@ -145,29 +347,47 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
|
||||
block
|
||||
.import_from_as
|
||||
.into_iter()
|
||||
.map(|(import_from, alias)| (import_from, FnvHashSet::from_iter([alias]))),
|
||||
.map(|((import_from, alias), comments)| {
|
||||
(
|
||||
import_from,
|
||||
(
|
||||
CommentSet {
|
||||
atop: comments.atop,
|
||||
inline: Default::default(),
|
||||
},
|
||||
FnvHashMap::from_iter([(
|
||||
alias,
|
||||
CommentSet {
|
||||
atop: Default::default(),
|
||||
inline: comments.inline,
|
||||
},
|
||||
)]),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.map(|(import_from, aliases)| {
|
||||
.map(|(import_from, (comments, aliases))| {
|
||||
// Within each `StmtKind::ImportFrom`, sort the members.
|
||||
(
|
||||
import_from,
|
||||
comments,
|
||||
aliases
|
||||
.into_iter()
|
||||
.sorted_by_cached_key(|alias| member_key(alias.name, alias.asname))
|
||||
.collect::<Vec<AliasData>>(),
|
||||
.sorted_by_cached_key(|(alias, _)| member_key(alias.name, alias.asname))
|
||||
.collect::<Vec<(AliasData, CommentSet)>>(),
|
||||
)
|
||||
})
|
||||
.sorted_by_cached_key(|(import_from, aliases)| {
|
||||
.sorted_by_cached_key(|(import_from, _, aliases)| {
|
||||
// Sort each `StmtKind::ImportFrom` by module key, breaking ties based on
|
||||
// members.
|
||||
(
|
||||
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)),
|
||||
.map(|(alias, _)| member_key(alias.name, alias.asname)),
|
||||
)
|
||||
}),
|
||||
);
|
||||
@@ -176,15 +396,18 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
|
||||
}
|
||||
|
||||
pub fn format_imports(
|
||||
block: Vec<&Stmt>,
|
||||
block: &[&Stmt],
|
||||
comments: Vec<Comment>,
|
||||
line_length: &usize,
|
||||
src: &[PathBuf],
|
||||
known_first_party: &BTreeSet<String>,
|
||||
known_third_party: &BTreeSet<String>,
|
||||
extra_standard_library: &BTreeSet<String>,
|
||||
) -> String {
|
||||
let block = annotate_imports(block, comments);
|
||||
|
||||
// Normalize imports (i.e., deduplicate, aggregate `from` imports).
|
||||
let block = normalize_imports(&block);
|
||||
let block = normalize_imports(block);
|
||||
|
||||
// Categorize by type (e.g., first-party vs. third-party).
|
||||
let block_by_type = categorize_imports(
|
||||
@@ -195,81 +418,38 @@ pub fn format_imports(
|
||||
extra_standard_library,
|
||||
);
|
||||
|
||||
// Generate replacement source code.
|
||||
let mut output = RopeBuilder::new();
|
||||
let mut first_block = true;
|
||||
|
||||
// Generate replacement source code.
|
||||
let mut is_first_block = true;
|
||||
for import_block in block_by_type.into_values() {
|
||||
let import_block = sort_imports(import_block);
|
||||
|
||||
// Add a blank line between every section.
|
||||
if !first_block {
|
||||
if !is_first_block {
|
||||
output.append("\n");
|
||||
} else {
|
||||
first_block = false;
|
||||
is_first_block = false;
|
||||
}
|
||||
|
||||
let mut is_first_statement = true;
|
||||
|
||||
// Format `StmtKind::Import` statements.
|
||||
for AliasData { name, asname } in import_block.import.iter() {
|
||||
if let Some(asname) = asname {
|
||||
output.append(&format!("import {} as {}\n", name, asname));
|
||||
} else {
|
||||
output.append(&format!("import {}\n", name));
|
||||
}
|
||||
for (alias, comments) in import_block.import.iter() {
|
||||
output.append(&format::format_import(alias, comments, is_first_statement));
|
||||
is_first_statement = false;
|
||||
}
|
||||
|
||||
// Format `StmtKind::ImportFrom` statements.
|
||||
for (import_from, aliases) in import_block.import_from.iter() {
|
||||
let prelude: String = format!("from {} import ", import_from.module_name());
|
||||
let members: Vec<String> = aliases
|
||||
.iter()
|
||||
.map(|AliasData { name, asname }| {
|
||||
if let Some(asname) = asname {
|
||||
format!("{} as {}", name, asname)
|
||||
} else {
|
||||
name.to_string()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Can we fit the import on a single line?
|
||||
let expected_len: usize =
|
||||
// `from base import `
|
||||
prelude.len()
|
||||
// `member( as alias)?`
|
||||
+ members.iter().map(|part| part.len()).sum::<usize>()
|
||||
// `, `
|
||||
+ 2 * (members.len() - 1);
|
||||
|
||||
if expected_len <= *line_length {
|
||||
// `from base import `
|
||||
output.append(&prelude);
|
||||
// `member( as alias)?(, )?`
|
||||
for (index, part) in members.into_iter().enumerate() {
|
||||
if index > 0 {
|
||||
output.append(", ");
|
||||
}
|
||||
output.append(&part);
|
||||
}
|
||||
// `\n`
|
||||
output.append("\n");
|
||||
} else {
|
||||
// `from base import (\n`
|
||||
output.append(&prelude);
|
||||
output.append("(");
|
||||
output.append("\n");
|
||||
|
||||
// ` member( as alias)?,\n`
|
||||
for part in members {
|
||||
output.append(INDENT);
|
||||
output.append(&part);
|
||||
output.append(",");
|
||||
output.append("\n");
|
||||
}
|
||||
|
||||
// `)\n`
|
||||
output.append(")");
|
||||
output.append("\n");
|
||||
}
|
||||
for (import_from, comments, aliases) in import_block.import_from.iter() {
|
||||
output.append(&format::format_import_from(
|
||||
import_from,
|
||||
comments,
|
||||
aliases,
|
||||
line_length,
|
||||
is_first_statement,
|
||||
));
|
||||
is_first_statement = false;
|
||||
}
|
||||
}
|
||||
output.finish().to_string()
|
||||
@@ -287,13 +467,17 @@ mod tests {
|
||||
use crate::linter::test_path;
|
||||
use crate::Settings;
|
||||
|
||||
#[test_case(Path::new("add_newline_before_comments.py"))]
|
||||
#[test_case(Path::new("combine_import_froms.py"))]
|
||||
#[test_case(Path::new("comments.py"))]
|
||||
#[test_case(Path::new("deduplicate_imports.py"))]
|
||||
#[test_case(Path::new("fit_line_length.py"))]
|
||||
#[test_case(Path::new("fit_line_length_comment.py"))]
|
||||
#[test_case(Path::new("import_from_after_import.py"))]
|
||||
#[test_case(Path::new("leading_prefix.py"))]
|
||||
#[test_case(Path::new("no_reorder_within_section.py"))]
|
||||
#[test_case(Path::new("order_by_type.py"))]
|
||||
#[test_case(Path::new("preserve_comment_order.py"))]
|
||||
#[test_case(Path::new("preserve_indentation.py"))]
|
||||
#[test_case(Path::new("reorder_within_section.py"))]
|
||||
#[test_case(Path::new("separate_first_party_imports.py"))]
|
||||
@@ -303,6 +487,7 @@ mod tests {
|
||||
#[test_case(Path::new("skip.py"))]
|
||||
#[test_case(Path::new("sort_similar_imports.py"))]
|
||||
#[test_case(Path::new("trailing_suffix.py"))]
|
||||
#[test_case(Path::new("type_comments.py"))]
|
||||
fn isort(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}", path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::{fixer, Fix};
|
||||
use crate::checks::CheckKind;
|
||||
use crate::docstrings::helpers::leading_space;
|
||||
use crate::isort::format_imports;
|
||||
use crate::isort::{comments, format_imports};
|
||||
use crate::{Check, Settings, SourceCodeLocator};
|
||||
|
||||
fn extract_range(body: &[&Stmt]) -> Range {
|
||||
@@ -44,7 +44,15 @@ fn match_trailing_content(body: &[&Stmt], locator: &SourceCodeLocator) -> bool {
|
||||
end_location: Location::new(end_location.row() + 1, 0),
|
||||
};
|
||||
let suffix = locator.slice_source_code_range(&range);
|
||||
suffix.chars().any(|char| !char.is_whitespace())
|
||||
for char in suffix.chars() {
|
||||
if char == '#' {
|
||||
return false;
|
||||
}
|
||||
if !char.is_whitespace() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// I001
|
||||
@@ -57,13 +65,23 @@ pub fn check_imports(
|
||||
let range = extract_range(&body);
|
||||
let indentation = extract_indentation(&body, locator);
|
||||
|
||||
// Extract comments. Take care to grab any inline comments from the last line.
|
||||
let comments = comments::collect_comments(
|
||||
&Range {
|
||||
location: range.location,
|
||||
end_location: Location::new(range.end_location.row() + 1, 0),
|
||||
},
|
||||
locator,
|
||||
);
|
||||
|
||||
// Special-cases: there's leading or trailing content in the import block.
|
||||
let has_leading_content = match_leading_content(&body, locator);
|
||||
let has_trailing_content = match_trailing_content(&body, locator);
|
||||
|
||||
// Generate the sorted import block.
|
||||
let expected = format_imports(
|
||||
body,
|
||||
&body,
|
||||
comments,
|
||||
&(settings.line_length - indentation.len()),
|
||||
&settings.src,
|
||||
&settings.isort.known_first_party,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import os\n\n# This is a comment in the same section, so we need to add one newline.\nimport sys\n\nimport numpy as np\n\n# This is a comment, but it starts a new section, so we don't need to add a newline\n# before it.\nimport leading_prefix\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
22
src/isort/snapshots/ruff__isort__tests__comments.py.snap
Normal file
22
src/isort/snapshots/ruff__isort__tests__comments.py.snap
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 26
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import B # Comment 4\n\n# Comment 3a\n# Comment 3b\nimport C\nimport D\n\n# Comment 5\n# Comment 6\nfrom A import (\n a, # Comment 7 # Comment 9\n b, # Comment 10\n c, # Comment 8 # Comment 11\n)\n"
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 26
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import a\n\n# Don't take this comment into account when determining whether the next import can fit on one line.\nfrom b import c\nfrom d import ( # Do take this comment into account when determining whether the next import can fit on one line.\n e,\n)\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import abc\nimport io\n\n# Old MacDonald had a farm,\n# EIEIO\n# And on his farm he had a cow,\n# EIEIO\n# With a moo-moo here and a moo-moo there\n# Here a moo, there a moo, everywhere moo-moo\n# Old MacDonald had a farm,\n# EIEIO\nfrom errno import EIO\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
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)]
|
||||
pub struct CommentSet<'a> {
|
||||
pub atop: Vec<Cow<'a, str>>,
|
||||
pub inline: Vec<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
pub trait Importable {
|
||||
@@ -29,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 {
|
||||
@@ -50,17 +51,24 @@ impl Importable for ImportFromData<'_> {
|
||||
pub struct ImportBlock<'a> {
|
||||
// Set of (name, asname), used to track regular imports.
|
||||
// Ex) `import module`
|
||||
pub import: FnvHashSet<AliasData<'a>>,
|
||||
pub import: FnvHashMap<AliasData<'a>, CommentSet<'a>>,
|
||||
// Map from (module, level) to `AliasData`, used to track 'from' imports.
|
||||
// Ex) `from module import member`
|
||||
pub import_from: FnvHashMap<ImportFromData<'a>, FnvHashSet<AliasData<'a>>>,
|
||||
pub import_from:
|
||||
FnvHashMap<ImportFromData<'a>, (CommentSet<'a>, FnvHashMap<AliasData<'a>, CommentSet<'a>>)>,
|
||||
// Set of (module, level, name, asname), used to track re-exported 'from' imports.
|
||||
// Ex) `from module import member as member`
|
||||
pub import_from_as: FnvHashSet<(ImportFromData<'a>, AliasData<'a>)>,
|
||||
pub import_from_as: FnvHashMap<(ImportFromData<'a>, AliasData<'a>), CommentSet<'a>>,
|
||||
}
|
||||
|
||||
type AliasDataWithComments<'a> = (AliasData<'a>, CommentSet<'a>);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct OrderedImportBlock<'a> {
|
||||
pub import: Vec<AliasData<'a>>,
|
||||
pub import_from: Vec<(ImportFromData<'a>, Vec<AliasData<'a>>)>,
|
||||
pub import: Vec<AliasDataWithComments<'a>>,
|
||||
pub import_from: Vec<(
|
||||
ImportFromData<'a>,
|
||||
CommentSet<'a>,
|
||||
Vec<AliasDataWithComments<'a>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")]
|
||||
|
||||
15
src/main.rs
15
src/main.rs
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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![];
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod checks;
|
||||
pub mod plugins;
|
||||
|
||||
203
src/pycodestyle/plugins.rs
Normal file
203
src/pycodestyle/plugins.rs
Normal 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());
|
||||
}
|
||||
@@ -64,9 +64,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()),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()]),
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user