Implement `flake8-return` plugin (#1016)

This commit is contained in:
Charlie Marsh 2022-12-03 20:12:11 -05:00 committed by GitHub
parent e66b786229
commit 291727a27d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 2134 additions and 86 deletions

25
LICENSE
View File

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

168
README.md
View File

@ -71,24 +71,25 @@ of [Conda](https://docs.conda.io/en/latest/):
1. [Supported Rules](#supported-rules)
1. [Pyflakes (F)](#pyflakes)
1. [pycodestyle (E, W)](#pycodestyle)
1. [mccabe (C90)](#mccabe)
1. [isort (I)](#isort)
1. [pydocstyle (D)](#pydocstyle)
1. [pyupgrade (U)](#pyupgrade)
1. [pep8-naming (N)](#pep8-naming)
1. [eradicate (ERA)](#eradicate)
1. [flake8-2020 (YTT)](#flake8-2020)
1. [flake8-annotations (ANN)](#flake8-annotations)
1. [flake8-bandit (S)](#flake8-bandit)
1. [flake8-comprehensions (C4)](#flake8-comprehensions)
1. [flake8-blind-except (BLE)](#flake8-blind-except)
1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap)
1. [flake8-bugbear (B)](#flake8-bugbear)
1. [flake8-builtins (A)](#flake8-builtins)
1. [flake8-comprehensions (C4)](#flake8-comprehensions)
1. [flake8-debugger (T10)](#flake8-debugger)
1. [flake8-tidy-imports (I25)](#flake8-tidy-imports)
1. [flake8-print (T20)](#flake8-print)
1. [flake8-quotes (Q)](#flake8-quotes)
1. [flake8-annotations (ANN)](#flake8-annotations)
1. [flake8-2020 (YTT)](#flake8-2020)
1. [flake8-blind-except (BLE)](#flake8-blind-except)
1. [mccabe (C90)](#mccabe)
1. [flake8-return (ET)](#flake8-return)
1. [flake8-tidy-imports (I25)](#flake8-tidy-imports)
1. [eradicate (ERA)](#eradicate)
1. [pygrep-hooks (PGH)](#pygrep-hooks)
1. [Pylint (PL)](#pylint)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules)
@ -450,6 +451,14 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
| W292 | NoNewLineAtEndOfFile | No newline at end of file | |
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | |
### mccabe
For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C901 | FunctionIsTooComplex | `...` is too complex (10) | |
### isort
For more, see [isort](https://pypi.org/project/isort/5.10.1/) on PyPI.
@ -552,13 +561,40 @@ For more, see [pep8-naming](https://pypi.org/project/pep8-naming/0.13.2/) on PyP
| N817 | CamelcaseImportedAsAcronym | Camelcase `...` imported as acronym `...` | |
| N818 | ErrorSuffixOnExceptionName | Exception name `...` should be named with an Error suffix | |
### eradicate
### flake8-2020
For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
For more, see [flake8-2020](https://pypi.org/project/flake8-2020/1.7.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ERA001 | CommentedOutCode | Found commented-out code | 🛠 |
| YTT101 | SysVersionSlice3Referenced | `sys.version[:3]` referenced (python3.10), use `sys.version_info` | |
| YTT102 | SysVersion2Referenced | `sys.version[2]` referenced (python3.10), use `sys.version_info` | |
| YTT103 | SysVersionCmpStr3 | `sys.version` compared to string (python3.10), use `sys.version_info` | |
| YTT201 | SysVersionInfo0Eq3Referenced | `sys.version_info[0] == 3` referenced (python4), use `>=` | |
| YTT202 | SixPY3Referenced | `six.PY3` referenced (python4), use `not six.PY2` | |
| YTT203 | SysVersionInfo1CmpInt | `sys.version_info[1]` compared to integer (python4), compare `sys.version_info` to tuple | |
| YTT204 | SysVersionInfoMinorCmpInt | `sys.version_info.minor` compared to integer (python4), compare `sys.version_info` to tuple | |
| YTT301 | SysVersion0Referenced | `sys.version[0]` referenced (python10), use `sys.version_info` | |
| YTT302 | SysVersionCmpStr10 | `sys.version` compared to string (python10), use `sys.version_info` | |
| YTT303 | SysVersionSlice1Referenced | `sys.version[:1]` referenced (python10), use `sys.version_info` | |
### flake8-annotations
For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2.9.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ANN001 | MissingTypeFunctionArgument | Missing type annotation for function argument `...` | |
| ANN002 | MissingTypeArgs | Missing type annotation for `*...` | |
| ANN003 | MissingTypeKwargs | Missing type annotation for `**...` | |
| ANN101 | MissingTypeSelf | Missing type annotation for `...` in method | |
| ANN102 | MissingTypeCls | Missing type annotation for `...` in classmethod | |
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function `...` | |
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function `...` | |
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method `...` | |
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod `...` | |
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
| ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | |
### flake8-bandit
@ -573,36 +609,13 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/4.1.1/) on
| S106 | HardcodedPasswordFuncArg | Possible hardcoded password: `"..."` | |
| S107 | HardcodedPasswordDefault | Possible hardcoded password: `"..."` | |
### flake8-comprehensions
### flake8-blind-except
For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/3.10.1/) on PyPI.
For more, see [flake8-blind-except](https://pypi.org/project/flake8-blind-except/0.2.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | 🛠 |
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | 🛠 |
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | 🛠 |
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | 🛠 |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | 🛠 |
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
### flake8-debugger
For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/4.1.2/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| T100 | Debugger | Import for `...` found | |
| BLE001 | BlindExcept | Blind except Exception: statement | |
### flake8-boolean-trap
@ -658,13 +671,36 @@ 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
### flake8-comprehensions
For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/4.8.0/) on PyPI.
For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/3.10.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| I252 | BannedRelativeImport | Relative imports are banned | |
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | 🛠 |
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | 🛠 |
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | 🛠 |
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | 🛠 |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | 🛠 |
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
### flake8-debugger
For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/4.1.2/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| T100 | Debugger | Import for `...` found | |
### flake8-print
@ -686,56 +722,36 @@ For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/3.3.1/) on
| Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | |
| Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | |
### flake8-annotations
### flake8-return
For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2.9.1/) on PyPI.
For more, see [flake8-return](https://pypi.org/project/flake8-return/1.2.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ANN001 | MissingTypeFunctionArgument | Missing type annotation for function argument `...` | |
| ANN002 | MissingTypeArgs | Missing type annotation for `*...` | |
| ANN003 | MissingTypeKwargs | Missing type annotation for `**...` | |
| ANN101 | MissingTypeSelf | Missing type annotation for `...` in method | |
| ANN102 | MissingTypeCls | Missing type annotation for `...` in classmethod | |
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function `...` | |
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function `...` | |
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method `...` | |
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod `...` | |
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
| ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | |
| RET501 | UnnecessaryReturnNone | Do not explicitly `return None` in function if it is the only possible return value | |
| RET502 | ImplicitReturnValue | Do not implicitly `return None` in function able to return non-`None` value | |
| RET503 | ImplicitReturn | Missing explicit `return` at the end of function able to return non-`None` value | |
| RET504 | UnnecessaryAssign | Unnecessary variable assignment before `return` statement | |
| RET505 | SuperfluousElseReturn | Unnecessary `else` after `return` statement | |
| RET506 | SuperfluousElseRaise | Unnecessary `else` after `raise` statement | |
| RET507 | SuperfluousElseContinue | Unnecessary `else` after `continue` statement | |
| RET508 | SuperfluousElseBreak | Unnecessary `else` after `break` statement | |
### flake8-2020
### flake8-tidy-imports
For more, see [flake8-2020](https://pypi.org/project/flake8-2020/1.7.0/) on PyPI.
For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/4.8.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| YTT101 | SysVersionSlice3Referenced | `sys.version[:3]` referenced (python3.10), use `sys.version_info` | |
| YTT102 | SysVersion2Referenced | `sys.version[2]` referenced (python3.10), use `sys.version_info` | |
| YTT103 | SysVersionCmpStr3 | `sys.version` compared to string (python3.10), use `sys.version_info` | |
| YTT201 | SysVersionInfo0Eq3Referenced | `sys.version_info[0] == 3` referenced (python4), use `>=` | |
| YTT202 | SixPY3Referenced | `six.PY3` referenced (python4), use `not six.PY2` | |
| YTT203 | SysVersionInfo1CmpInt | `sys.version_info[1]` compared to integer (python4), compare `sys.version_info` to tuple | |
| YTT204 | SysVersionInfoMinorCmpInt | `sys.version_info.minor` compared to integer (python4), compare `sys.version_info` to tuple | |
| YTT301 | SysVersion0Referenced | `sys.version[0]` referenced (python10), use `sys.version_info` | |
| YTT302 | SysVersionCmpStr10 | `sys.version` compared to string (python10), use `sys.version_info` | |
| YTT303 | SysVersionSlice1Referenced | `sys.version[:1]` referenced (python10), use `sys.version_info` | |
| I252 | BannedRelativeImport | Relative imports are banned | |
### flake8-blind-except
### eradicate
For more, see [flake8-blind-except](https://pypi.org/project/flake8-blind-except/0.2.1/) on PyPI.
For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| BLE001 | BlindExcept | Blind except Exception: statement | |
### mccabe
For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C901 | FunctionIsTooComplex | `...` is too complex (10) | |
| ERA001 | CommentedOutCode | Found commented-out code | 🛠 |
### pygrep-hooks
@ -918,6 +934,7 @@ natively, including:
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-return`](https://pypi.org/project/flake8-return/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`mccabe`](https://pypi.org/project/mccabe/)
@ -965,6 +982,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-return`](https://pypi.org/project/flake8-return/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`mccabe`](https://pypi.org/project/mccabe/)

11
create_plugin.sh Executable file
View File

@ -0,0 +1,11 @@
set -euxo pipefail
NAME=$1
mkdir -p src/$1
mkdir -p resources/test/fixtures/$1
touch src/$1/mod.rs
sed -i "" "s/mod flake8_print;/mod flake8_print; mod flake8_return;/g" src/lib.rs
sed -i "" "s|// flake8-print|// flake8-return\n// flake8-print|g" src/checks.rs

View File

@ -17,6 +17,7 @@ pub enum Plugin {
Flake8Eradicate,
Flake8Print,
Flake8Quotes,
Flake8Return,
Flake8TidyImports,
McCabe,
PEP8Naming,
@ -39,6 +40,7 @@ impl FromStr for Plugin {
"flake8-eradicate" => Ok(Plugin::Flake8BlindExcept),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-return" => Ok(Plugin::Flake8Return),
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
"mccabe" => Ok(Plugin::McCabe),
"pep8-naming" => Ok(Plugin::PEP8Naming),
@ -62,6 +64,7 @@ impl Plugin {
Plugin::Flake8Eradicate => CheckCodePrefix::ERA,
Plugin::Flake8Print => CheckCodePrefix::T2,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Return => CheckCodePrefix::RET,
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
Plugin::McCabe => CheckCodePrefix::C9,
Plugin::PEP8Naming => CheckCodePrefix::N,
@ -97,6 +100,7 @@ impl Plugin {
Plugin::Flake8Eradicate => vec![CheckCodePrefix::ERA],
Plugin::Flake8Print => vec![CheckCodePrefix::T2],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Return => vec![CheckCodePrefix::RET],
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
Plugin::McCabe => vec![CheckCodePrefix::C9],
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
@ -389,6 +393,7 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
Plugin::Flake8Eradicate,
Plugin::Flake8Print,
Plugin::Flake8Quotes,
Plugin::Flake8Return,
Plugin::Flake8TidyImports,
Plugin::PEP8Naming,
Plugin::Pyupgrade,

View File

@ -0,0 +1,4 @@
def x(y):
if not y:
return
return None # error

View File

@ -0,0 +1,65 @@
def x(y):
if not y:
return # error
return 1
# without return value
def x(y):
if not y:
return
return
def x(y):
if y:
return
print()
# with return value
def x(y):
if not y:
return 1
return 2
def x(y):
for i in range(10):
if i > 100:
return i
else:
return 1
def x(y):
try:
return 1
except:
return 2
def x(y):
try:
return 1
finally:
return 2
# inner function
def x(y):
if not y:
return 1
def inner():
return
return 2
def x(y):
if not y:
return
def inner():
return 1

View File

@ -0,0 +1,116 @@
###
# Errors
###
# if/elif/else
def x(y):
if not y:
return 1
# error
def x(y):
if not y:
print() # error
else:
return 2
def x(y):
if not y:
return 1
print() # error
# for
def x(y):
for i in range(10):
if i > 10:
return i
# error
def x(y):
for i in range(10):
if i > 10:
return i
else:
print() # error
###
# Non-errors
###
# raise as last return
def x(y):
if not y:
return 1
raise Exception
# last line in while loop
def x(y):
while True:
if y > 0:
return 1
y += 1
# exclude empty functions
def x(y):
return None
# return inner with statement
def x(y):
with y:
return 1
async def function():
async with thing as foo:
return foo.bar
# assert as last return
def x(y):
if not y:
return 1
assert False, "Sanity check"
# return value within loop
def bar1(x, y, z):
for i in x:
if i > y:
break
return z
def bar3(x, y, z):
for i in x:
if i > y:
if z:
break
else:
return z
return None
def bar1(x, y, z):
for i in x:
if i < y:
continue
return z
def bar3(x, y, z):
for i in x:
if i < y:
if z:
continue
else:
return z
return None

View File

@ -0,0 +1,232 @@
###
# Errors
###
def x():
a = 1
return a # error
def x():
b, a = 1, 2
print(b)
return a # error
def x():
a = 1
print()
return a # error
def x():
a = 1
print(a)
a = 2
return a # error
def x():
a = 1
if True:
return a # error
a = 2
print(a)
return a
# Can be refactored false positives
# https://github.com/afonasev/flake8-return/issues/47#issuecomment-1122571066
def get_bar_if_exists(obj):
result = ""
if hasattr(obj, "bar"):
result = str(obj.bar)
return result
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def x():
formatted = _USER_AGENT_FORMATTER.format(format_string, **values)
# clean up after any blank components
formatted = formatted.replace("()", "").replace(" ", " ").strip()
return formatted
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def user_agent_username(username=None):
if not username:
return ""
username = username.replace(" ", "_") # Avoid spaces or %20.
try:
username.encode("ascii") # just test, but not actually use it
except UnicodeEncodeError:
username = quote(username.encode("utf-8"))
else:
# % is legal in the default $wgLegalTitleChars
# This is so that ops know the real pywikibot will not
# allow a useragent in the username to allow through a hand-coded
# percent-encoded value.
if "%" in username:
username = quote(username)
return username
###
# Non-errors
###
def x(y):
a = 1
print(a)
return a
def x():
a = 1
if y:
return a
a = a + 2
print(a)
return a
def x():
a = {}
a["b"] = 2
return a
def x():
a = []
a.append(2)
return a
def x():
a = lambda x: x
a()
return a
# ignore unpacking
def x():
b, a = [1, 2]
return a
def x():
val = ""
for i in range(5):
val = val + str(i)
return val
def x():
val = ""
i = 5
while i:
val = val + str(i)
i = i - x
return val
# Test cases for using value for assignment then returning it
# See:https://github.com/afonasev/flake8-return/issues/47
def resolve_from_url(self, url: str) -> dict:
local_match = self.local_scope_re.match(url)
if local_match:
schema = get_schema(name=local_match.group(1))
self.store[url] = schema
return schema
raise NotImplementedError(...)
my_dict = {}
def my_func():
foo = calculate_foo()
my_dict["foo_result"] = foo
return foo
# Refactored incorrect false positives
# See test cases above marked: "Can be refactored false positives"
# https://github.com/afonasev/flake8-return/issues/47#issuecomment-1122571066
def get_bar_if_exists(obj):
if hasattr(obj, "bar"):
return str(obj.bar)
return ""
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def x():
formatted = _USER_AGENT_FORMATTER.format(format_string, **values)
# clean up after any blank components
return formatted.replace("()", "").replace(" ", " ").strip()
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def user_agent_username(username=None):
if not username:
return ""
username = username.replace(" ", "_") # Avoid spaces or %20.
try:
username.encode("ascii") # just test,
# but not actually use it
except UnicodeEncodeError:
username = quote(username.encode("utf-8"))
else:
# % is legal in the default $wgLegalTitleChars
# This is so that ops know the real pywikibot will not
# allow a useragent in the username to allow through a
# hand-coded percent-encoded value.
if "%" in username:
username = quote(username)
return username
# https://github.com/afonasev/flake8-return/issues/116#issue-1367575481
def no_exception_loop():
success = False
for _ in range(10):
try:
success = True
except Exception:
print("exception")
return success
# https://github.com/afonasev/flake8-return/issues/116#issue-1367575481
def no_exception():
success = False
try:
success = True
except Exception:
print("exception")
return success
# https://github.com/afonasev/flake8-return/issues/116#issue-1367575481
def exception():
success = True
try:
print("raising")
raise Exception
except Exception:
success = False
return success
# https://github.com/afonasev/flake8-return/issues/66
def close(self):
any_failed = False
for task in self.tasks:
try:
task()
except BaseException:
any_failed = True
report(traceback.format_exc())
return any_failed

View File

@ -0,0 +1,141 @@
###
# Error
###
def foo2(x, y, w, z):
if x: # [no-else-return]
a = 1
return y
elif z:
b = 2
return w
else:
c = 3
return z
def foo5(x, y, z):
if x: # [no-else-return]
if y:
a = 4
else:
b = 2
return
elif z:
c = 2
else:
c = 3
return
def foo2(x, y, w, z):
if x:
a = 1
elif z:
b = 2
else:
c = 3
if x: # [no-else-return]
a = 1
return y
elif z:
b = 2
return w
else:
c = 3
return z
def foo1(x, y, z):
if x: # [no-else-return]
a = 1
return y
else:
b = 2
return z
def foo3(x, y, z):
if x:
a = 1
if y: # [no-else-return]
b = 2
return y
else:
c = 3
return x
else:
d = 4
return z
def foo4(x, y):
if x: # [no-else-return]
if y:
a = 4
else:
b = 2
return
else:
c = 3
return
def foo6(x, y):
if x:
if y: # [no-else-return]
a = 4
return
else:
b = 2
else:
c = 3
return
def bar4(x):
if x: # [no-else-return]
return True
else:
try:
return False
except ValueError:
return None
###
# Non-error
###
def bar1(x, y, z):
if x:
return y
return z
def bar2(w, x, y, z):
if x:
return y
if z:
a = 1
else:
return w
return None
def bar3(x, y, z):
if x:
if z:
return y
else:
return z
return None
x = 0
if x == 1:
y = "a"
elif x == 2:
y = "b"
else:
y = "c"

View File

@ -0,0 +1,112 @@
###
# Errors
###
def foo2(x, y, w, z):
if x: # [no-else-raise]
a = 1
raise Exception(y)
elif z:
b = 2
raise Exception(w)
else:
c = 3
raise Exception(z)
def foo5(x, y, z):
if x: # [no-else-raise]
if y:
a = 4
else:
b = 2
raise Exception(x)
elif z:
raise Exception(y)
else:
c = 3
raise Exception(z)
def foo1(x, y, z):
if x: # [no-else-raise]
a = 1
raise Exception(y)
else:
b = 2
raise Exception(z)
def foo3(x, y, z):
if x:
a = 1
if y: # [no-else-raise]
b = 2
raise Exception(y)
else:
c = 3
raise Exception(x)
else:
d = 4
raise Exception(z)
def foo4(x, y):
if x: # [no-else-raise]
if y:
a = 4
else:
b = 2
raise Exception(x)
else:
c = 3
raise Exception(y)
def foo6(x, y):
if x:
if y: # [no-else-raise]
a = 4
raise Exception(x)
else:
b = 2
else:
c = 3
raise Exception(y)
def bar4(x):
if x: # [no-else-raise]
raise Exception(True)
else:
try:
raise Exception(False)
except ValueError:
raise Exception(None)
###
# Non-errors
###
def bar1(x, y, z):
if x:
raise Exception(y)
raise Exception(z)
def bar2(w, x, y, z):
if x:
raise Exception(y)
if z:
a = 1
else:
raise Exception(w)
raise Exception(None)
def bar3(x, y, z):
if x:
if z:
raise Exception(y)
else:
raise Exception(z)
raise Exception(None)

View File

@ -0,0 +1,106 @@
###
# Errors
###
def foo2(x, y, w, z):
for i in x:
if i < y: # [no-else-continue]
continue
elif i < w:
continue
else:
a = z
def foo5(x, y, z):
for i in x:
if i < y: # [no-else-continue]
if y:
a = 4
else:
b = 2
continue
elif z:
c = 2
else:
c = 3
continue
###
# Non-errors
###
def foo1(x, y, z):
for i in x:
if i < y: # [no-else-continue]
continue
else:
a = z
def foo3(x, y, z):
for i in x:
if i < y:
a = 1
if z: # [no-else-continue]
b = 2
continue
else:
c = 3
continue
else:
d = 4
continue
def foo4(x, y):
for i in x:
if i < x: # [no-else-continue]
if y:
a = 4
else:
b = 2
continue
else:
c = 3
continue
def foo6(x, y):
for i in x:
if i < x:
if y: # [no-else-continue]
a = 4
continue
else:
b = 2
else:
c = 3
continue
def bar4(x):
for i in range(10):
if x: # [no-else-continue]
continue
else:
try:
return
except ValueError:
continue
def bar1(x, y, z):
for i in x:
if i < y:
continue
return z
def bar3(x, y, z):
for i in x:
if i < y:
if z:
continue
else:
return z
return None

View File

@ -0,0 +1,138 @@
###
# Errors
###
def foo2(x, y, w, z):
for i in x:
if i > y: # [no-else-break]
break
elif i > w:
break
else:
a = z
def foo5(x, y, z):
for i in x:
if i > y: # [no-else-break]
if y:
a = 4
else:
b = 2
break
elif z:
c = 2
else:
c = 3
break
def foo1(x, y, z):
for i in x:
if i > y: # [no-else-break]
break
else:
a = z
def foo3(x, y, z):
for i in x:
if i > y:
a = 1
if z: # [no-else-break]
b = 2
break
else:
c = 3
break
else:
d = 4
break
def foo4(x, y):
for i in x:
if i > x: # [no-else-break]
if y:
a = 4
else:
b = 2
break
else:
c = 3
break
def foo6(x, y):
for i in x:
if i > x:
if y: # [no-else-break]
a = 4
break
else:
b = 2
else:
c = 3
break
def bar4(x):
for i in range(10):
if x: # [no-else-break]
break
else:
try:
return
except ValueError:
break
###
# Non-errors
###
def bar1(x, y, z):
for i in x:
if i > y:
break
return z
def bar2(w, x, y, z):
for i in w:
if i > x:
break
if z < i:
a = y
else:
break
return
def bar3(x, y, z):
for i in x:
if i > y:
if z:
break
else:
return z
return None
def nested1(x, y, z):
for i in x:
if i > x:
for j in y:
if j < z:
break
else:
a = z
def nested2(x, y, z):
for i in x:
if i > x:
for j in y:
if j > z:
break
break
else:
a = z

View File

@ -36,8 +36,8 @@ use crate::visibility::{module_visibility, transition_scope, Modifier, Visibilit
use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger,
flake8_print, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes,
pygrep_hooks, pylint, pyupgrade, rules,
flake8_print, flake8_return, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle,
pyflakes, pygrep_hooks, pylint, pyupgrade, rules,
};
const GLOBAL_SCOPE_INDEX: usize = 0;
@ -421,9 +421,23 @@ where
if self.settings.enabled.contains(&CheckCode::B018) {
flake8_bugbear::plugins::useless_expression(self, body);
}
if self.settings.enabled.contains(&CheckCode::B019) {
flake8_bugbear::plugins::cached_instance_method(self, decorator_list);
}
if self.settings.enabled.contains(&CheckCode::RET501)
|| self.settings.enabled.contains(&CheckCode::RET502)
|| self.settings.enabled.contains(&CheckCode::RET503)
|| self.settings.enabled.contains(&CheckCode::RET504)
|| self.settings.enabled.contains(&CheckCode::RET505)
|| self.settings.enabled.contains(&CheckCode::RET506)
|| self.settings.enabled.contains(&CheckCode::RET507)
|| self.settings.enabled.contains(&CheckCode::RET508)
{
flake8_return::plugins::function(self, body);
}
if self.settings.enabled.contains(&CheckCode::C901) {
if let Some(check) = mccabe::checks::function_is_too_complex(
stmt,

View File

@ -152,6 +152,15 @@ pub enum CheckCode {
C901,
// flake8-tidy-imports
I252,
// flake8-return
RET501,
RET502,
RET503,
RET504,
RET505,
RET506,
RET507,
RET508,
// flake8-print
T201,
T203,
@ -289,24 +298,25 @@ pub enum CheckCode {
pub enum CheckCategory {
Pyflakes,
Pycodestyle,
McCabe,
Isort,
Pydocstyle,
Pyupgrade,
PEP8Naming,
Eradicate,
Flake82020,
Flake8Annotations,
Flake8Bandit,
Flake8Comprehensions,
Flake8Debugger,
Flake8BlindExcept,
Flake8BooleanTrap,
Flake8Bugbear,
Flake8Builtins,
Flake8TidyImports,
Flake8Comprehensions,
Flake8Debugger,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
Flake82020,
Flake8BlindExcept,
McCabe,
Flake8Return,
Flake8TidyImports,
Eradicate,
PygrepHooks,
Pylint,
Ruff,
@ -342,6 +352,7 @@ impl CheckCategory {
CheckCategory::Flake8Debugger => "flake8-debugger",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes",
CheckCategory::Flake8Return => "flake8-return",
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
CheckCategory::Isort => "isort",
CheckCategory::McCabe => "mccabe",
@ -350,8 +361,8 @@ impl CheckCategory {
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Pylint => "Pylint",
CheckCategory::PygrepHooks => "pygrep-hooks",
CheckCategory::Pylint => "Pylint",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Ruff => "Ruff-specific rules",
}
@ -406,6 +417,10 @@ impl CheckCategory {
"https://pypi.org/project/flake8-quotes/3.3.1/",
&Platform::PyPI,
)),
CheckCategory::Flake8Return => Some((
"https://pypi.org/project/flake8-return/1.2.0/",
&Platform::PyPI,
)),
CheckCategory::Flake8TidyImports => Some((
"https://pypi.org/project/flake8-tidy-imports/4.8.0/",
&Platform::PyPI,
@ -479,6 +494,21 @@ impl fmt::Display for DeferralKeyword {
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Branch {
Elif,
Else,
}
impl fmt::Display for Branch {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Branch::Elif => fmt.write_str("elif"),
Branch::Else => fmt.write_str("else"),
}
}
}
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
// pycodestyle errors
@ -602,6 +632,15 @@ pub enum CheckKind {
Debugger(DebuggerUsingType),
// flake8-tidy-imports
BannedRelativeImport(Strictness),
// flake8-return
UnnecessaryReturnNone,
ImplicitReturnValue,
ImplicitReturn,
UnnecessaryAssign,
SuperfluousElseReturn(Branch),
SuperfluousElseRaise(Branch),
SuperfluousElseContinue(Branch),
SuperfluousElseBreak(Branch),
// flake8-print
PrintFound,
PPrintFound,
@ -907,6 +946,15 @@ impl CheckCode {
CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())),
// flake8-tidy-imports
CheckCode::I252 => CheckKind::BannedRelativeImport(Strictness::All),
// flake8-return
CheckCode::RET501 => CheckKind::UnnecessaryReturnNone,
CheckCode::RET502 => CheckKind::ImplicitReturnValue,
CheckCode::RET503 => CheckKind::ImplicitReturn,
CheckCode::RET504 => CheckKind::UnnecessaryAssign,
CheckCode::RET505 => CheckKind::SuperfluousElseReturn(Branch::Else),
CheckCode::RET506 => CheckKind::SuperfluousElseRaise(Branch::Else),
CheckCode::RET507 => CheckKind::SuperfluousElseContinue(Branch::Else),
CheckCode::RET508 => CheckKind::SuperfluousElseBreak(Branch::Else),
// flake8-print
CheckCode::T201 => CheckKind::PrintFound,
CheckCode::T203 => CheckKind::PPrintFound,
@ -1256,6 +1304,14 @@ impl CheckCode {
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::RET501 => CheckCategory::Flake8Return,
CheckCode::RET502 => CheckCategory::Flake8Return,
CheckCode::RET503 => CheckCategory::Flake8Return,
CheckCode::RET504 => CheckCategory::Flake8Return,
CheckCode::RET505 => CheckCategory::Flake8Return,
CheckCode::RET506 => CheckCategory::Flake8Return,
CheckCode::RET507 => CheckCategory::Flake8Return,
CheckCode::RET508 => CheckCategory::Flake8Return,
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
@ -1423,6 +1479,15 @@ impl CheckKind {
CheckKind::Debugger(_) => &CheckCode::T100,
// flake8-tidy-imports
CheckKind::BannedRelativeImport(_) => &CheckCode::I252,
// flake8-return
CheckKind::UnnecessaryReturnNone => &CheckCode::RET501,
CheckKind::ImplicitReturnValue => &CheckCode::RET502,
CheckKind::ImplicitReturn => &CheckCode::RET503,
CheckKind::UnnecessaryAssign => &CheckCode::RET504,
CheckKind::SuperfluousElseReturn(_) => &CheckCode::RET505,
CheckKind::SuperfluousElseRaise(_) => &CheckCode::RET506,
CheckKind::SuperfluousElseContinue(_) => &CheckCode::RET507,
CheckKind::SuperfluousElseBreak(_) => &CheckCode::RET508,
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
@ -1963,6 +2028,31 @@ impl CheckKind {
}
Strictness::All => "Relative imports are banned".to_string(),
},
// flake8-return
CheckKind::UnnecessaryReturnNone => "Do not explicitly `return None` in function if \
it is the only possible return value"
.to_string(),
CheckKind::ImplicitReturnValue => "Do not implicitly `return None` in function able \
to return non-`None` value"
.to_string(),
CheckKind::ImplicitReturn => "Missing explicit `return` at the end of function able \
to return non-`None` value"
.to_string(),
CheckKind::UnnecessaryAssign => {
"Unnecessary variable assignment before `return` statement".to_string()
}
CheckKind::SuperfluousElseReturn(branch) => {
format!("Unnecessary `{branch}` after `return` statement")
}
CheckKind::SuperfluousElseRaise(branch) => {
format!("Unnecessary `{branch}` after `raise` statement")
}
CheckKind::SuperfluousElseContinue(branch) => {
format!("Unnecessary `{branch}` after `continue` statement")
}
CheckKind::SuperfluousElseBreak(branch) => {
format!("Unnecessary `{branch}` after `break` statement")
}
// flake8-print
CheckKind::PrintFound => "`print` found".to_string(),
CheckKind::PPrintFound => "`pprint` found".to_string(),

View File

@ -308,6 +308,17 @@ pub enum CheckCodePrefix {
Q001,
Q002,
Q003,
RET,
RET5,
RET50,
RET501,
RET502,
RET503,
RET504,
RET505,
RET506,
RET507,
RET508,
RUF,
RUF0,
RUF00,
@ -1211,6 +1222,44 @@ impl CheckCodePrefix {
CheckCodePrefix::Q001 => vec![CheckCode::Q001],
CheckCodePrefix::Q002 => vec![CheckCode::Q002],
CheckCodePrefix::Q003 => vec![CheckCode::Q003],
CheckCodePrefix::RET => vec![
CheckCode::RET501,
CheckCode::RET502,
CheckCode::RET503,
CheckCode::RET504,
CheckCode::RET505,
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
],
CheckCodePrefix::RET5 => vec![
CheckCode::RET501,
CheckCode::RET502,
CheckCode::RET503,
CheckCode::RET504,
CheckCode::RET505,
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
],
CheckCodePrefix::RET50 => vec![
CheckCode::RET501,
CheckCode::RET502,
CheckCode::RET503,
CheckCode::RET504,
CheckCode::RET505,
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
],
CheckCodePrefix::RET501 => vec![CheckCode::RET501],
CheckCodePrefix::RET502 => vec![CheckCode::RET502],
CheckCodePrefix::RET503 => vec![CheckCode::RET503],
CheckCodePrefix::RET504 => vec![CheckCode::RET504],
CheckCodePrefix::RET505 => vec![CheckCode::RET505],
CheckCodePrefix::RET506 => vec![CheckCode::RET506],
CheckCodePrefix::RET507 => vec![CheckCode::RET507],
CheckCodePrefix::RET508 => vec![CheckCode::RET508],
CheckCodePrefix::RUF => vec![
CheckCode::RUF001,
CheckCode::RUF002,
@ -1681,6 +1730,17 @@ impl CheckCodePrefix {
CheckCodePrefix::Q001 => SuffixLength::Three,
CheckCodePrefix::Q002 => SuffixLength::Three,
CheckCodePrefix::Q003 => SuffixLength::Three,
CheckCodePrefix::RET => SuffixLength::Zero,
CheckCodePrefix::RET5 => SuffixLength::One,
CheckCodePrefix::RET50 => SuffixLength::Two,
CheckCodePrefix::RET501 => SuffixLength::Three,
CheckCodePrefix::RET502 => SuffixLength::Three,
CheckCodePrefix::RET503 => SuffixLength::Three,
CheckCodePrefix::RET504 => SuffixLength::Three,
CheckCodePrefix::RET505 => SuffixLength::Three,
CheckCodePrefix::RET506 => SuffixLength::Three,
CheckCodePrefix::RET507 => SuffixLength::Three,
CheckCodePrefix::RET508 => SuffixLength::Three,
CheckCodePrefix::RUF => SuffixLength::Zero,
CheckCodePrefix::RUF0 => SuffixLength::One,
CheckCodePrefix::RUF00 => SuffixLength::Two,
@ -1772,6 +1832,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::PLE,
CheckCodePrefix::PLR,
CheckCodePrefix::Q,
CheckCodePrefix::RET,
CheckCodePrefix::RUF,
CheckCodePrefix::S,
CheckCodePrefix::T,

View File

@ -0,0 +1,18 @@
use rustpython_ast::{Constant, Expr, ExprKind, Stmt};
/// Return `true` if a function's return statement include at least one
/// non-`None` value.
pub fn result_exists(returns: &[(&Stmt, Option<&Expr>)]) -> bool {
returns.iter().any(|(_, expr)| {
expr.map(|expr| {
!matches!(
expr.node,
ExprKind::Constant {
value: Constant::None,
..
}
)
})
.unwrap_or(false)
})
}

37
src/flake8_return/mod.rs Normal file
View File

@ -0,0 +1,37 @@
mod helpers;
pub mod plugins;
mod visitor;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::Settings;
#[test_case(CheckCode::RET501, Path::new("RET501.py"); "RET501")]
#[test_case(CheckCode::RET502, Path::new("RET502.py"); "RET502")]
#[test_case(CheckCode::RET503, Path::new("RET503.py"); "RET503")]
#[test_case(CheckCode::RET504, Path::new("RET504.py"); "RET504")]
#[test_case(CheckCode::RET505, Path::new("RET505.py"); "RET505")]
#[test_case(CheckCode::RET506, Path::new("RET506.py"); "RET506")]
#[test_case(CheckCode::RET507, Path::new("RET507.py"); "RET507")]
#[test_case(CheckCode::RET508, Path::new("RET508.py"); "RET508")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_return")
.join(path)
.as_path(),
&Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
}

View File

@ -0,0 +1,322 @@
use itertools::Itertools;
use rustpython_ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checks::{Branch, CheckCode, CheckKind};
use crate::flake8_return::helpers::result_exists;
use crate::flake8_return::visitor::{ReturnVisitor, Stack};
use crate::Check;
/// RET501
fn unnecessary_return_none(checker: &mut Checker, stack: &Stack) {
for (stmt, expr) in &stack.returns {
if let Some(expr) = expr {
if matches!(
expr.node,
ExprKind::Constant {
value: Constant::None,
..
}
) {
checker.add_check(Check::new(
CheckKind::UnnecessaryReturnNone,
Range::from_located(stmt),
));
}
}
}
}
/// RET502
fn implicit_return_value(checker: &mut Checker, stack: &Stack) {
for (stmt, expr) in &stack.returns {
if expr.is_none() {
checker.add_check(Check::new(
CheckKind::ImplicitReturnValue,
Range::from_located(stmt),
));
}
}
}
/// RET503
fn implicit_return(checker: &mut Checker, last_stmt: &Stmt) {
match &last_stmt.node {
StmtKind::If { body, orelse, .. } => {
if body.is_empty() || orelse.is_empty() {
checker.add_check(Check::new(
CheckKind::ImplicitReturn,
Range::from_located(last_stmt),
));
return;
}
if let Some(last_stmt) = body.last() {
implicit_return(checker, last_stmt);
}
if let Some(last_stmt) = orelse.last() {
implicit_return(checker, last_stmt);
}
}
StmtKind::For { body, orelse, .. } | StmtKind::AsyncFor { body, orelse, .. } => {
if let Some(last_stmt) = orelse.last() {
implicit_return(checker, last_stmt);
} else if let Some(last_stmt) = body.last() {
implicit_return(checker, last_stmt);
}
}
StmtKind::With { body, .. } | StmtKind::AsyncWith { body, .. } => {
if let Some(last_stmt) = body.last() {
implicit_return(checker, last_stmt);
}
}
StmtKind::Assert { test, .. }
if matches!(
test.node,
ExprKind::Constant {
value: Constant::Bool(false),
..
}
) => {}
StmtKind::Return { .. }
| StmtKind::While { .. }
| StmtKind::Raise { .. }
| StmtKind::Try { .. } => {}
_ => {
checker.add_check(Check::new(
CheckKind::ImplicitReturn,
Range::from_located(last_stmt),
));
}
}
}
fn has_refs_before_next_assign(id: &str, return_location: Location, stack: &Stack) -> bool {
let mut before_assign: &Location = &Location::default();
let mut after_assign: Option<&Location> = None;
if let Some(assigns) = stack.assigns.get(&id) {
for location in assigns.iter().sorted() {
if location.row() > return_location.row() {
after_assign = Some(location);
break;
}
if location.row() <= return_location.row() {
before_assign = location;
}
}
}
if let Some(refs) = stack.refs.get(&id) {
for location in refs {
if location.row() == return_location.row() {
continue;
}
if let Some(after_assign) = after_assign {
if before_assign.row() < location.row() && location.row() <= after_assign.row() {
return true;
}
} else if before_assign.row() < location.row() {
return true;
}
}
}
false
}
fn has_refs_or_assigns_within_try_or_loop(id: &str, stack: &Stack) -> bool {
if let Some(refs) = stack.refs.get(&id) {
for location in refs {
for (try_location, try_end_location) in &stack.tries {
if try_location.row() < location.row() && location.row() <= try_end_location.row() {
return true;
}
}
for (loop_location, loop_end_location) in &stack.loops {
if loop_location.row() < location.row() && location.row() <= loop_end_location.row()
{
return true;
}
}
}
}
if let Some(refs) = stack.assigns.get(&id) {
for location in refs {
for (try_location, try_end_location) in &stack.tries {
if try_location.row() < location.row() && location.row() <= try_end_location.row() {
return true;
}
}
for (loop_location, loop_end_location) in &stack.loops {
if loop_location.row() < location.row() && location.row() <= loop_end_location.row()
{
return true;
}
}
}
}
false
}
/// RET504
fn unnecessary_assign(checker: &mut Checker, stack: &Stack, expr: &Expr) {
if let ExprKind::Name { id, .. } = &expr.node {
if !stack.assigns.contains_key(id.as_str()) {
return;
}
if !stack.refs.contains_key(id.as_str()) {
checker.add_check(Check::new(
CheckKind::UnnecessaryAssign,
Range::from_located(expr),
));
return;
}
if has_refs_before_next_assign(id, expr.location, stack)
|| has_refs_or_assigns_within_try_or_loop(id, stack)
{
return;
}
checker.add_check(Check::new(
CheckKind::UnnecessaryAssign,
Range::from_located(expr),
));
}
}
/// RET505, RET506, RET507, RET508
fn superfluous_else_node(checker: &mut Checker, stmt: &Stmt, branch: Branch) -> bool {
if let StmtKind::If { body, .. } = &stmt.node {
for child in body {
if matches!(child.node, StmtKind::Return { .. }) {
if checker.settings.enabled.contains(&CheckCode::RET505) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseReturn(branch),
Range::from_located(stmt),
));
}
return true;
}
if matches!(child.node, StmtKind::Break) {
if checker.settings.enabled.contains(&CheckCode::RET508) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseBreak(branch),
Range::from_located(stmt),
));
}
return true;
}
if matches!(child.node, StmtKind::Raise { .. }) {
if checker.settings.enabled.contains(&CheckCode::RET506) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseRaise(branch),
Range::from_located(stmt),
));
}
return true;
}
if matches!(child.node, StmtKind::Continue) {
if checker.settings.enabled.contains(&CheckCode::RET507) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseContinue(branch),
Range::from_located(stmt),
));
}
return true;
}
}
}
false
}
/// RET505, RET506, RET507, RET508
fn superfluous_elif(checker: &mut Checker, stack: &Stack) -> bool {
for stmt in &stack.elifs {
if superfluous_else_node(checker, stmt, Branch::Elif) {
return true;
}
}
false
}
/// RET505, RET506, RET507, RET508
fn superfluous_else(checker: &mut Checker, stack: &Stack) -> bool {
for stmt in &stack.ifs {
if let StmtKind::If { orelse, .. } = &stmt.node {
if !orelse.is_empty() {
if superfluous_else_node(checker, stmt, Branch::Else) {
return true;
}
}
}
}
false
}
/// Run all checks from the `flake8-return` plugin.
pub fn function(checker: &mut Checker, body: &[Stmt]) {
// Skip empty functions.
if body.is_empty() {
return;
}
// Find the last statement in the function.
let last_stmt = body.last().unwrap();
// Skip functions that consist of a single return statement.
if body.len() == 1 && matches!(last_stmt.node, StmtKind::Return { .. }) {
return;
}
// Traverse the function body, to collect the stack.
let stack = {
let mut visitor = ReturnVisitor::default();
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.stack
};
if checker.settings.enabled.contains(&CheckCode::RET505)
|| checker.settings.enabled.contains(&CheckCode::RET506)
|| checker.settings.enabled.contains(&CheckCode::RET507)
|| checker.settings.enabled.contains(&CheckCode::RET508)
{
if superfluous_elif(checker, &stack) {
return;
}
if superfluous_else(checker, &stack) {
return;
}
}
// Skip any functions without return statements.
if stack.returns.is_empty() {
return;
}
if !result_exists(&stack.returns) {
if checker.settings.enabled.contains(&CheckCode::RET501) {
unnecessary_return_none(checker, &stack);
}
return;
}
if checker.settings.enabled.contains(&CheckCode::RET502) {
implicit_return_value(checker, &stack);
}
if checker.settings.enabled.contains(&CheckCode::RET503) {
implicit_return(checker, last_stmt);
}
if checker.settings.enabled.contains(&CheckCode::RET504) {
for (_, expr) in &stack.returns {
if let Some(expr) = expr {
unnecessary_assign(checker, &stack, expr);
}
}
}
}

View File

@ -0,0 +1,13 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind: UnnecessaryReturnNone
location:
row: 4
column: 4
end_location:
row: 4
column: 15
fix: ~

View File

@ -0,0 +1,13 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind: ImplicitReturnValue
location:
row: 3
column: 8
end_location:
row: 3
column: 14
fix: ~

View File

@ -0,0 +1,45 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind: ImplicitReturn
location:
row: 7
column: 4
end_location:
row: 12
column: 0
fix: ~
- kind: ImplicitReturn
location:
row: 14
column: 8
end_location:
row: 14
column: 15
fix: ~
- kind: ImplicitReturn
location:
row: 23
column: 4
end_location:
row: 23
column: 11
fix: ~
- kind: ImplicitReturn
location:
row: 29
column: 8
end_location:
row: 34
column: 0
fix: ~
- kind: ImplicitReturn
location:
row: 39
column: 8
end_location:
row: 39
column: 15
fix: ~

View File

@ -0,0 +1,61 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind: UnnecessaryAssign
location:
row: 6
column: 11
end_location:
row: 6
column: 12
fix: ~
- kind: UnnecessaryAssign
location:
row: 12
column: 11
end_location:
row: 12
column: 12
fix: ~
- kind: UnnecessaryAssign
location:
row: 18
column: 11
end_location:
row: 18
column: 12
fix: ~
- kind: UnnecessaryAssign
location:
row: 25
column: 11
end_location:
row: 25
column: 12
fix: ~
- kind: UnnecessaryAssign
location:
row: 31
column: 15
end_location:
row: 31
column: 16
fix: ~
- kind: UnnecessaryAssign
location:
row: 43
column: 11
end_location:
row: 43
column: 17
fix: ~
- kind: UnnecessaryAssign
location:
row: 51
column: 11
end_location:
row: 51
column: 20
fix: ~

View File

@ -0,0 +1,77 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind:
SuperfluousElseReturn: Elif
location:
row: 5
column: 4
end_location:
row: 16
column: 0
fix: ~
- kind:
SuperfluousElseReturn: Elif
location:
row: 17
column: 4
end_location:
row: 27
column: 4
fix: ~
- kind:
SuperfluousElseReturn: Elif
location:
row: 38
column: 4
end_location:
row: 49
column: 0
fix: ~
- kind:
SuperfluousElseReturn: Else
location:
row: 50
column: 4
end_location:
row: 58
column: 0
fix: ~
- kind:
SuperfluousElseReturn: Else
location:
row: 61
column: 8
end_location:
row: 67
column: 4
fix: ~
- kind:
SuperfluousElseReturn: Else
location:
row: 73
column: 4
end_location:
row: 81
column: 4
fix: ~
- kind:
SuperfluousElseReturn: Else
location:
row: 86
column: 8
end_location:
row: 91
column: 4
fix: ~
- kind:
SuperfluousElseReturn: Else
location:
row: 97
column: 4
end_location:
row: 109
column: 0
fix: ~

View File

@ -0,0 +1,68 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind:
SuperfluousElseRaise: Elif
location:
row: 5
column: 4
end_location:
row: 16
column: 0
fix: ~
- kind:
SuperfluousElseRaise: Elif
location:
row: 17
column: 4
end_location:
row: 27
column: 4
fix: ~
- kind:
SuperfluousElseRaise: Else
location:
row: 31
column: 4
end_location:
row: 39
column: 0
fix: ~
- kind:
SuperfluousElseRaise: Else
location:
row: 42
column: 8
end_location:
row: 48
column: 4
fix: ~
- kind:
SuperfluousElseRaise: Else
location:
row: 54
column: 4
end_location:
row: 62
column: 4
fix: ~
- kind:
SuperfluousElseRaise: Else
location:
row: 67
column: 8
end_location:
row: 72
column: 4
fix: ~
- kind:
SuperfluousElseRaise: Else
location:
row: 78
column: 4
end_location:
row: 90
column: 0
fix: ~

View File

@ -0,0 +1,68 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind:
SuperfluousElseContinue: Elif
location:
row: 6
column: 8
end_location:
row: 14
column: 0
fix: ~
- kind:
SuperfluousElseContinue: Elif
location:
row: 16
column: 8
end_location:
row: 26
column: 8
fix: ~
- kind:
SuperfluousElseContinue: Else
location:
row: 34
column: 8
end_location:
row: 40
column: 0
fix: ~
- kind:
SuperfluousElseContinue: Else
location:
row: 44
column: 12
end_location:
row: 50
column: 8
fix: ~
- kind:
SuperfluousElseContinue: Else
location:
row: 57
column: 8
end_location:
row: 65
column: 8
fix: ~
- kind:
SuperfluousElseContinue: Else
location:
row: 71
column: 12
end_location:
row: 76
column: 8
fix: ~
- kind:
SuperfluousElseContinue: Else
location:
row: 83
column: 8
end_location:
row: 92
column: 0
fix: ~

View File

@ -0,0 +1,68 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind:
SuperfluousElseBreak: Elif
location:
row: 6
column: 8
end_location:
row: 14
column: 0
fix: ~
- kind:
SuperfluousElseBreak: Elif
location:
row: 16
column: 8
end_location:
row: 26
column: 8
fix: ~
- kind:
SuperfluousElseBreak: Else
location:
row: 31
column: 8
end_location:
row: 37
column: 0
fix: ~
- kind:
SuperfluousElseBreak: Else
location:
row: 41
column: 12
end_location:
row: 47
column: 8
fix: ~
- kind:
SuperfluousElseBreak: Else
location:
row: 54
column: 8
end_location:
row: 62
column: 8
fix: ~
- kind:
SuperfluousElseBreak: Else
location:
row: 68
column: 12
end_location:
row: 73
column: 8
fix: ~
- kind:
SuperfluousElseBreak: Else
location:
row: 80
column: 8
end_location:
row: 92
column: 0
fix: ~

View File

@ -0,0 +1,119 @@
use rustc_hash::FxHashMap;
use rustpython_ast::{Expr, ExprKind, Location, Stmt, StmtKind};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
#[derive(Default)]
pub struct Stack<'a> {
pub returns: Vec<(&'a Stmt, Option<&'a Expr>)>,
pub ifs: Vec<&'a Stmt>,
pub elifs: Vec<&'a Stmt>,
pub refs: FxHashMap<&'a str, Vec<Location>>,
pub assigns: FxHashMap<&'a str, Vec<Location>>,
pub loops: Vec<(Location, Location)>,
pub tries: Vec<(Location, Location)>,
}
#[derive(Default)]
pub struct ReturnVisitor<'a> {
pub stack: Stack<'a>,
}
impl<'a> ReturnVisitor<'a> {
fn visit_assign_target(&mut self, expr: &'a Expr) {
match &expr.node {
ExprKind::Tuple { elts, .. } => {
for elt in elts {
self.visit_assign_target(elt);
}
return;
}
ExprKind::Name { id, .. } => {
self.stack
.assigns
.entry(id)
.or_insert_with(Vec::new)
.push(expr.location);
return;
}
_ => {}
}
visitor::walk_expr(self, expr);
}
}
impl<'a> Visitor<'a> for ReturnVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// Don't recurse.
}
StmtKind::Return { value } => {
self.stack
.returns
.push((stmt, value.as_ref().map(|expr| &**expr)));
visitor::walk_stmt(self, stmt);
}
StmtKind::If { orelse, .. } => {
if orelse.len() == 1 && matches!(orelse.first().unwrap().node, StmtKind::If { .. })
{
self.stack.elifs.push(stmt);
} else {
self.stack.ifs.push(stmt);
}
visitor::walk_stmt(self, stmt);
}
StmtKind::Assign { targets, value, .. } => {
if let ExprKind::Name { id, .. } = &value.node {
self.stack
.refs
.entry(id)
.or_insert_with(Vec::new)
.push(value.location);
}
visitor::walk_expr(self, value);
if let Some(target) = targets.first() {
// Skip unpacking assignments, like `x, y = my_object`.
if matches!(target.node, ExprKind::Tuple { .. })
&& !matches!(value.node, ExprKind::Tuple { .. })
{
return;
}
self.visit_assign_target(target);
}
}
StmtKind::For { .. } | StmtKind::AsyncFor { .. } | StmtKind::While { .. } => {
self.stack
.loops
.push((stmt.location, stmt.end_location.unwrap()));
visitor::walk_stmt(self, stmt);
}
StmtKind::Try { .. } => {
self.stack
.tries
.push((stmt.location, stmt.end_location.unwrap()));
visitor::walk_stmt(self, stmt);
}
_ => {
visitor::walk_stmt(self, stmt);
}
}
}
fn visit_expr(&mut self, expr: &'a Expr) {
match &expr.node {
ExprKind::Name { id, .. } => {
self.stack
.refs
.entry(id)
.or_insert_with(Vec::new)
.push(expr.location);
}
_ => visitor::walk_expr(self, expr),
}
}
}

View File

@ -51,6 +51,7 @@ mod flake8_comprehensions;
mod flake8_debugger;
mod flake8_print;
pub mod flake8_quotes;
mod flake8_return;
pub mod flake8_tidy_imports;
pub mod fs;
mod isort;