mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-31 15:47:41 +00:00
Refactor StmtIf
: Formatter and Linter (#5459)
## Summary Previously, `StmtIf` was defined recursively as ```rust pub struct StmtIf { pub range: TextRange, pub test: Box<Expr>, pub body: Vec<Stmt>, pub orelse: Vec<Stmt>, } ``` Every `elif` was represented as an `orelse` with a single `StmtIf`. This means that this representation couldn't differentiate between ```python if cond1: x = 1 else: if cond2: x = 2 ``` and ```python if cond1: x = 1 elif cond2: x = 2 ``` It also makes many checks harder than they need to be because we have to recurse just to iterate over an entire if-elif-else and because we're lacking nodes and ranges on the `elif` and `else` branches. We change the representation to a flat ```rust pub struct StmtIf { pub range: TextRange, pub test: Box<Expr>, pub body: Vec<Stmt>, pub elif_else_clauses: Vec<ElifElseClause>, } pub struct ElifElseClause { pub range: TextRange, pub test: Option<Expr>, pub body: Vec<Stmt>, } ``` where `test: Some(_)` represents an `elif` and `test: None` an else. This representation is different tradeoff, e.g. we need to allocate the `Vec<ElifElseClause>`, the `elif`s are now different than the `if`s (which matters in rules where want to check both `if`s and `elif`s) and the type system doesn't guarantee that the `test: None` else is actually last. We're also now a bit more inconsistent since all other `else`, those from `for`, `while` and `try`, still don't have nodes. With the new representation some things became easier, e.g. finding the `elif` token (we can use the start of the `ElifElseClause`) and formatting comments for if-elif-else (no more dangling comments splitting, we only have to insert the dangling comment after the colon manually and set `leading_alternate_branch_comments`, everything else is taken of by having nodes for each branch and the usual placement.rs fixups). ## Merge Plan This PR requires coordination between the parser repo and the main ruff repo. I've split the ruff part, into two stacked PRs which have to be merged together (only the second one fixes all tests), the first for the formatter to be reviewed by @michareiser and the second for the linter to be reviewed by @charliermarsh. * MH: Review and merge https://github.com/astral-sh/RustPython-Parser/pull/20 * MH: Review and merge or move later in stack https://github.com/astral-sh/RustPython-Parser/pull/21 * MH: Review and approve https://github.com/astral-sh/RustPython-Parser/pull/22 * MH: Review and approve formatter PR https://github.com/astral-sh/ruff/pull/5459 * CM: Review and approve linter PR https://github.com/astral-sh/ruff/pull/5460 * Merge linter PR in formatter PR, fix ecosystem checks (ecosystem checks can't run on the formatter PR and won't run on the linter PR, so we need to merge them first) * Merge https://github.com/astral-sh/RustPython-Parser/pull/22 * Create tag in the parser, update linter+formatter PR * Merge linter+formatter PR https://github.com/astral-sh/ruff/pull/5459 --------- Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
167b9356fa
commit
730e6b2b4c
82 changed files with 2333 additions and 2009 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -2226,7 +2226,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_text_size"
|
name = "ruff_text_size"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=b996b21ffca562ecb2086f632a6a0b05c245c24a#b996b21ffca562ecb2086f632a6a0b05c245c24a"
|
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=db04fd415774032e1e2ceb03bcbf5305e0d22c8c#db04fd415774032e1e2ceb03bcbf5305e0d22c8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2327,7 +2327,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpython-ast"
|
name = "rustpython-ast"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=b996b21ffca562ecb2086f632a6a0b05c245c24a#b996b21ffca562ecb2086f632a6a0b05c245c24a"
|
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=db04fd415774032e1e2ceb03bcbf5305e0d22c8c#db04fd415774032e1e2ceb03bcbf5305e0d22c8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"is-macro",
|
"is-macro",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
|
@ -2338,7 +2338,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpython-format"
|
name = "rustpython-format"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=b996b21ffca562ecb2086f632a6a0b05c245c24a#b996b21ffca562ecb2086f632a6a0b05c245c24a"
|
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=db04fd415774032e1e2ceb03bcbf5305e0d22c8c#db04fd415774032e1e2ceb03bcbf5305e0d22c8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.3.3",
|
"bitflags 2.3.3",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
@ -2350,7 +2350,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpython-literal"
|
name = "rustpython-literal"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=b996b21ffca562ecb2086f632a6a0b05c245c24a#b996b21ffca562ecb2086f632a6a0b05c245c24a"
|
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=db04fd415774032e1e2ceb03bcbf5305e0d22c8c#db04fd415774032e1e2ceb03bcbf5305e0d22c8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hexf-parse",
|
"hexf-parse",
|
||||||
"is-macro",
|
"is-macro",
|
||||||
|
@ -2362,7 +2362,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpython-parser"
|
name = "rustpython-parser"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=b996b21ffca562ecb2086f632a6a0b05c245c24a#b996b21ffca562ecb2086f632a6a0b05c245c24a"
|
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=db04fd415774032e1e2ceb03bcbf5305e0d22c8c#db04fd415774032e1e2ceb03bcbf5305e0d22c8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"is-macro",
|
"is-macro",
|
||||||
|
@ -2385,7 +2385,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpython-parser-core"
|
name = "rustpython-parser-core"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=b996b21ffca562ecb2086f632a6a0b05c245c24a#b996b21ffca562ecb2086f632a6a0b05c245c24a"
|
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=db04fd415774032e1e2ceb03bcbf5305e0d22c8c#db04fd415774032e1e2ceb03bcbf5305e0d22c8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"is-macro",
|
"is-macro",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -54,12 +54,12 @@ libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "3cacca1a1029f
|
||||||
|
|
||||||
# Please tag the RustPython version every time you update its revision here and in fuzz/Cargo.toml
|
# Please tag the RustPython version every time you update its revision here and in fuzz/Cargo.toml
|
||||||
# Tagging the version ensures that older ruff versions continue to build from source even when we rebase our RustPython fork.
|
# Tagging the version ensures that older ruff versions continue to build from source even when we rebase our RustPython fork.
|
||||||
# Current tag: v0.0.7
|
# Current tag: v0.0.9
|
||||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "b996b21ffca562ecb2086f632a6a0b05c245c24a" }
|
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "db04fd415774032e1e2ceb03bcbf5305e0d22c8c" }
|
||||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "b996b21ffca562ecb2086f632a6a0b05c245c24a" , default-features = false, features = ["num-bigint"]}
|
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "db04fd415774032e1e2ceb03bcbf5305e0d22c8c" , default-features = false, features = ["num-bigint"]}
|
||||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "b996b21ffca562ecb2086f632a6a0b05c245c24a", default-features = false, features = ["num-bigint"] }
|
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "db04fd415774032e1e2ceb03bcbf5305e0d22c8c", default-features = false, features = ["num-bigint"] }
|
||||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "b996b21ffca562ecb2086f632a6a0b05c245c24a", default-features = false }
|
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "db04fd415774032e1e2ceb03bcbf5305e0d22c8c", default-features = false }
|
||||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "b996b21ffca562ecb2086f632a6a0b05c245c24a" , default-features = false, features = ["full-lexer", "num-bigint"] }
|
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "db04fd415774032e1e2ceb03bcbf5305e0d22c8c" , default-features = false, features = ["full-lexer", "num-bigint"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
|
|
|
@ -97,3 +97,10 @@ def f():
|
||||||
# variable name).
|
# variable name).
|
||||||
for line_ in range(self.header_lines):
|
for line_ in range(self.header_lines):
|
||||||
fp.readline()
|
fp.readline()
|
||||||
|
|
||||||
|
# Regression test: visitor didn't walk the elif test
|
||||||
|
for key, value in current_crawler_tags.items():
|
||||||
|
if key:
|
||||||
|
pass
|
||||||
|
elif wanted_tag_value != value:
|
||||||
|
pass
|
||||||
|
|
|
@ -100,6 +100,14 @@ if node.module0123456789:
|
||||||
):
|
):
|
||||||
print("Bad module!")
|
print("Bad module!")
|
||||||
|
|
||||||
|
# SIM102
|
||||||
|
# Regression test for https://github.com/apache/airflow/blob/145b16caaa43f0c42bffd97344df916c602cddde/airflow/configuration.py#L1161
|
||||||
|
if a:
|
||||||
|
if b:
|
||||||
|
if c:
|
||||||
|
print("if")
|
||||||
|
elif d:
|
||||||
|
print("elif")
|
||||||
|
|
||||||
# OK
|
# OK
|
||||||
if a:
|
if a:
|
||||||
|
|
|
@ -23,7 +23,7 @@ elif a:
|
||||||
else:
|
else:
|
||||||
b = 2
|
b = 2
|
||||||
|
|
||||||
# OK (false negative)
|
# SIM108
|
||||||
if True:
|
if True:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -94,3 +94,10 @@ if result.eofs == "F":
|
||||||
errors = 1
|
errors = 1
|
||||||
else:
|
else:
|
||||||
errors = 1
|
errors = 1
|
||||||
|
|
||||||
|
if a:
|
||||||
|
# Ignore branches with diverging comments because it means we're repeating
|
||||||
|
# the bodies because we have different reasons for each branch
|
||||||
|
x = 1
|
||||||
|
elif c:
|
||||||
|
x = 1
|
||||||
|
|
|
@ -84,3 +84,15 @@ elif func_name == "remove":
|
||||||
return "D"
|
return "D"
|
||||||
elif func_name == "move":
|
elif func_name == "move":
|
||||||
return "MV"
|
return "MV"
|
||||||
|
|
||||||
|
# OK
|
||||||
|
def no_return_in_else(platform):
|
||||||
|
if platform == "linux":
|
||||||
|
return "auditwheel repair -w {dest_dir} {wheel}"
|
||||||
|
elif platform == "macos":
|
||||||
|
return "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
|
||||||
|
elif platform == "windows":
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
msg = f"Unknown platform: {platform!r}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
|
@ -38,6 +38,15 @@ if key in a_dict:
|
||||||
else:
|
else:
|
||||||
vars[idx] = "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789"
|
vars[idx] = "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789"
|
||||||
|
|
||||||
|
# SIM401
|
||||||
|
if foo():
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if key in a_dict:
|
||||||
|
vars[idx] = a_dict[key]
|
||||||
|
else:
|
||||||
|
vars[idx] = "default"
|
||||||
|
|
||||||
###
|
###
|
||||||
# Negative cases
|
# Negative cases
|
||||||
###
|
###
|
||||||
|
@ -105,12 +114,3 @@ elif key in a_dict:
|
||||||
vars[idx] = a_dict[key]
|
vars[idx] = a_dict[key]
|
||||||
else:
|
else:
|
||||||
vars[idx] = "default"
|
vars[idx] = "default"
|
||||||
|
|
||||||
# OK (false negative for nested else)
|
|
||||||
if foo():
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if key in a_dict:
|
|
||||||
vars[idx] = a_dict[key]
|
|
||||||
else:
|
|
||||||
vars[idx] = "default"
|
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
if (1, 2):
|
if (1, 2):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if (3, 4):
|
||||||
|
pass
|
||||||
|
elif foo:
|
||||||
|
pass
|
||||||
|
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
if True:
|
if True:
|
||||||
pass
|
pass
|
||||||
|
|
15
crates/ruff/resources/test/fixtures/pyflakes/F811_25.py
vendored
Normal file
15
crates/ruff/resources/test/fixtures/pyflakes/F811_25.py
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Regression test for branch detection from
|
||||||
|
# https://github.com/pypa/build/blob/5800521541e5e749d4429617420d1ef8cdb40b46/src/build/_importlib.py
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
import importlib_metadata as metadata
|
||||||
|
elif sys.version_info < (3, 9, 10) or (3, 10, 0) <= sys.version_info < (3, 10, 2):
|
||||||
|
try:
|
||||||
|
import importlib_metadata as metadata
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
from importlib import metadata
|
||||||
|
else:
|
||||||
|
from importlib import metadata
|
||||||
|
|
||||||
|
__all__ = ["metadata"]
|
|
@ -47,3 +47,17 @@ def not_ok1():
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Regression test for https://github.com/apache/airflow/blob/f1e1cdcc3b2826e68ba133f350300b5065bbca33/airflow/models/dag.py#L1737
|
||||||
|
def not_ok2():
|
||||||
|
if True:
|
||||||
|
print(1)
|
||||||
|
elif True:
|
||||||
|
print(2)
|
||||||
|
else:
|
||||||
|
if True:
|
||||||
|
print(3)
|
||||||
|
else:
|
||||||
|
print(4)
|
||||||
|
|
||||||
|
|
|
@ -7,20 +7,20 @@ if True:
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
if foo:
|
if foo:
|
||||||
pass
|
print()
|
||||||
elif sys.version_info < (3, 3):
|
elif sys.version_info < (3, 3):
|
||||||
cmd = [sys.executable, "-m", "test.regrtest"]
|
cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
if foo:
|
if foo:
|
||||||
pass
|
print()
|
||||||
elif sys.version_info < (3, 3):
|
elif sys.version_info < (3, 3):
|
||||||
cmd = [sys.executable, "-m", "test.regrtest"]
|
cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
elif foo:
|
elif foo:
|
||||||
cmd = [sys.executable, "-m", "test", "-j0"]
|
cmd = [sys.executable, "-m", "test", "-j0"]
|
||||||
|
|
||||||
if foo:
|
if foo:
|
||||||
pass
|
print()
|
||||||
elif sys.version_info < (3, 3):
|
elif sys.version_info < (3, 3):
|
||||||
cmd = [sys.executable, "-m", "test.regrtest"]
|
cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ if True:
|
||||||
cmd = [sys.executable, "-m", "test.regrtest"]
|
cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
|
|
||||||
if foo:
|
if foo:
|
||||||
pass
|
print()
|
||||||
elif sys.version_info < (3, 3):
|
elif sys.version_info < (3, 3):
|
||||||
cmd = [sys.executable, "-m", "test.regrtest"]
|
cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -230,6 +230,15 @@ def incorrect_multi_conditional(arg1, arg2):
|
||||||
raise Exception("...") # should be typeerror
|
raise Exception("...") # should be typeerror
|
||||||
|
|
||||||
|
|
||||||
|
def multiple_is_instance_checks(some_arg):
|
||||||
|
if isinstance(some_arg, str):
|
||||||
|
pass
|
||||||
|
elif isinstance(some_arg, int):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise Exception("...") # should be typeerror
|
||||||
|
|
||||||
|
|
||||||
class MyCustomTypeValidation(Exception):
|
class MyCustomTypeValidation(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -296,6 +305,17 @@ def multiple_ifs(some_args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def else_body(obj):
|
||||||
|
if isinstance(obj, datetime.timedelta):
|
||||||
|
return "TimeDelta"
|
||||||
|
elif isinstance(obj, relativedelta.relativedelta):
|
||||||
|
return "RelativeDelta"
|
||||||
|
elif isinstance(obj, CronExpression):
|
||||||
|
return "CronExpression"
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unknown object type: {obj.__class__.__name__}")
|
||||||
|
|
||||||
|
|
||||||
def early_return():
|
def early_return():
|
||||||
if isinstance(this, some_type):
|
if isinstance(this, some_type):
|
||||||
if x in this:
|
if x in this:
|
||||||
|
|
|
@ -190,12 +190,24 @@ fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
|
||||||
}
|
}
|
||||||
Stmt::For(ast::StmtFor { body, orelse, .. })
|
Stmt::For(ast::StmtFor { body, orelse, .. })
|
||||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
||||||
| Stmt::While(ast::StmtWhile { body, orelse, .. })
|
| Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
||||||
| Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
|
||||||
if is_only(body, child) || is_only(orelse, child) {
|
if is_only(body, child) || is_only(orelse, child) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Stmt::If(ast::StmtIf {
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if is_only(body, child)
|
||||||
|
|| elif_else_clauses
|
||||||
|
.iter()
|
||||||
|
.any(|ast::ElifElseClause { body, .. }| is_only(body, child))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Stmt::Try(ast::StmtTry {
|
Stmt::Try(ast::StmtTry {
|
||||||
body,
|
body,
|
||||||
handlers,
|
handlers,
|
||||||
|
|
|
@ -5,8 +5,8 @@ use log::error;
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
use rustpython_format::cformat::{CFormatError, CFormatErrorType};
|
use rustpython_format::cformat::{CFormatError, CFormatErrorType};
|
||||||
use rustpython_parser::ast::{
|
use rustpython_parser::ast::{
|
||||||
self, Arg, ArgWithDefault, Arguments, Comprehension, Constant, ExceptHandler, Expr,
|
self, Arg, ArgWithDefault, Arguments, Comprehension, Constant, ElifElseClause, ExceptHandler,
|
||||||
ExprContext, Keyword, Operator, Pattern, Ranged, Stmt, Suite, UnaryOp,
|
Expr, ExprContext, Keyword, Operator, Pattern, Ranged, Stmt, Suite, UnaryOp,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Fix, IsolationLevel};
|
use ruff_diagnostics::{Diagnostic, Fix, IsolationLevel};
|
||||||
|
@ -588,6 +588,7 @@ where
|
||||||
name,
|
name,
|
||||||
bases,
|
bases,
|
||||||
keywords,
|
keywords,
|
||||||
|
type_params: _,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
body,
|
body,
|
||||||
range: _,
|
range: _,
|
||||||
|
@ -1159,9 +1160,8 @@ where
|
||||||
Stmt::If(
|
Stmt::If(
|
||||||
stmt_if @ ast::StmtIf {
|
stmt_if @ ast::StmtIf {
|
||||||
test,
|
test,
|
||||||
body,
|
elif_else_clauses,
|
||||||
orelse,
|
..
|
||||||
range: _,
|
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if self.enabled(Rule::EmptyTypeCheckingBlock) {
|
if self.enabled(Rule::EmptyTypeCheckingBlock) {
|
||||||
|
@ -1170,70 +1170,42 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::IfTuple) {
|
if self.enabled(Rule::IfTuple) {
|
||||||
pyflakes::rules::if_tuple(self, stmt, test);
|
pyflakes::rules::if_tuple(self, stmt_if);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::CollapsibleIf) {
|
if self.enabled(Rule::CollapsibleIf) {
|
||||||
flake8_simplify::rules::nested_if_statements(
|
flake8_simplify::rules::nested_if_statements(
|
||||||
self,
|
self,
|
||||||
stmt,
|
stmt_if,
|
||||||
test,
|
|
||||||
body,
|
|
||||||
orelse,
|
|
||||||
self.semantic.stmt_parent(),
|
self.semantic.stmt_parent(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::IfWithSameArms) {
|
if self.enabled(Rule::IfWithSameArms) {
|
||||||
flake8_simplify::rules::if_with_same_arms(
|
flake8_simplify::rules::if_with_same_arms(self, self.locator, stmt_if);
|
||||||
self,
|
|
||||||
stmt,
|
|
||||||
self.semantic.stmt_parent(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::NeedlessBool) {
|
if self.enabled(Rule::NeedlessBool) {
|
||||||
flake8_simplify::rules::needless_bool(self, stmt);
|
flake8_simplify::rules::needless_bool(self, stmt);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::IfElseBlockInsteadOfDictLookup) {
|
if self.enabled(Rule::IfElseBlockInsteadOfDictLookup) {
|
||||||
flake8_simplify::rules::manual_dict_lookup(
|
flake8_simplify::rules::manual_dict_lookup(self, stmt_if);
|
||||||
self,
|
|
||||||
stmt,
|
|
||||||
test,
|
|
||||||
body,
|
|
||||||
orelse,
|
|
||||||
self.semantic.stmt_parent(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::IfElseBlockInsteadOfIfExp) {
|
if self.enabled(Rule::IfElseBlockInsteadOfIfExp) {
|
||||||
flake8_simplify::rules::use_ternary_operator(
|
flake8_simplify::rules::use_ternary_operator(self, stmt);
|
||||||
self,
|
|
||||||
stmt,
|
|
||||||
self.semantic.stmt_parent(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::IfElseBlockInsteadOfDictGet) {
|
if self.enabled(Rule::IfElseBlockInsteadOfDictGet) {
|
||||||
flake8_simplify::rules::use_dict_get_with_default(
|
flake8_simplify::rules::use_dict_get_with_default(self, stmt_if);
|
||||||
self,
|
|
||||||
stmt,
|
|
||||||
test,
|
|
||||||
body,
|
|
||||||
orelse,
|
|
||||||
self.semantic.stmt_parent(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::TypeCheckWithoutTypeError) {
|
if self.enabled(Rule::TypeCheckWithoutTypeError) {
|
||||||
tryceratops::rules::type_check_without_type_error(
|
tryceratops::rules::type_check_without_type_error(
|
||||||
self,
|
self,
|
||||||
body,
|
stmt_if,
|
||||||
test,
|
|
||||||
orelse,
|
|
||||||
self.semantic.stmt_parent(),
|
self.semantic.stmt_parent(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::OutdatedVersionBlock) {
|
if self.enabled(Rule::OutdatedVersionBlock) {
|
||||||
pyupgrade::rules::outdated_version_block(self, stmt, test, body, orelse);
|
pyupgrade::rules::outdated_version_block(self, stmt_if);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::CollapsibleElseIf) {
|
if self.enabled(Rule::CollapsibleElseIf) {
|
||||||
if let Some(diagnostic) =
|
if let Some(diagnostic) = pylint::rules::collapsible_else_if(elif_else_clauses)
|
||||||
pylint::rules::collapsible_else_if(orelse, self.locator)
|
|
||||||
{
|
{
|
||||||
self.diagnostics.push(diagnostic);
|
self.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
@ -2053,7 +2025,7 @@ where
|
||||||
stmt_if @ ast::StmtIf {
|
stmt_if @ ast::StmtIf {
|
||||||
test,
|
test,
|
||||||
body,
|
body,
|
||||||
orelse,
|
elif_else_clauses,
|
||||||
range: _,
|
range: _,
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
|
@ -2068,7 +2040,9 @@ where
|
||||||
self.visit_body(body);
|
self.visit_body(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.visit_body(orelse);
|
for clause in elif_else_clauses {
|
||||||
|
self.visit_elif_else_clause(clause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => visitor::walk_stmt(self, stmt),
|
_ => visitor::walk_stmt(self, stmt),
|
||||||
};
|
};
|
||||||
|
@ -4344,6 +4318,14 @@ impl<'a> Checker<'a> {
|
||||||
self.semantic.flags = snapshot;
|
self.semantic.flags = snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Visit an [`ElifElseClause`]
|
||||||
|
fn visit_elif_else_clause(&mut self, clause: &'a ElifElseClause) {
|
||||||
|
if let Some(test) = &clause.test {
|
||||||
|
self.visit_boolean_test(test);
|
||||||
|
}
|
||||||
|
self.visit_body(&clause.body);
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a [`Binding`] to the current scope, bound to the given name.
|
/// Add a [`Binding`] to the current scope, bound to the given name.
|
||||||
fn add_binding(
|
fn add_binding(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -55,8 +55,6 @@ struct GroupNameFinder<'a> {
|
||||||
/// A flag indicating that the `group_name` variable has been overridden
|
/// A flag indicating that the `group_name` variable has been overridden
|
||||||
/// during the visit.
|
/// during the visit.
|
||||||
overridden: bool,
|
overridden: bool,
|
||||||
/// A stack of `if` statements.
|
|
||||||
parent_ifs: Vec<&'a Stmt>,
|
|
||||||
/// A stack of counters where each counter is itself a list of usage count.
|
/// A stack of counters where each counter is itself a list of usage count.
|
||||||
/// This is used specifically for mutually exclusive statements such as an
|
/// This is used specifically for mutually exclusive statements such as an
|
||||||
/// `if` or `match`.
|
/// `if` or `match`.
|
||||||
|
@ -77,7 +75,6 @@ impl<'a> GroupNameFinder<'a> {
|
||||||
usage_count: 0,
|
usage_count: 0,
|
||||||
nested: false,
|
nested: false,
|
||||||
overridden: false,
|
overridden: false,
|
||||||
parent_ifs: Vec::new(),
|
|
||||||
counter_stack: Vec::new(),
|
counter_stack: Vec::new(),
|
||||||
exprs: Vec::new(),
|
exprs: Vec::new(),
|
||||||
}
|
}
|
||||||
|
@ -146,56 +143,28 @@ where
|
||||||
Stmt::If(ast::StmtIf {
|
Stmt::If(ast::StmtIf {
|
||||||
test,
|
test,
|
||||||
body,
|
body,
|
||||||
orelse,
|
elif_else_clauses,
|
||||||
range: _,
|
range: _,
|
||||||
}) => {
|
}) => {
|
||||||
// Determine whether we're on an `if` arm (as opposed to an `elif`).
|
// base if plus branches
|
||||||
let is_if_arm = !self.parent_ifs.iter().any(|parent| {
|
let mut if_stack = Vec::with_capacity(1 + elif_else_clauses.len());
|
||||||
if let Stmt::If(ast::StmtIf { orelse, .. }) = parent {
|
// Initialize the vector with the count for the if branch.
|
||||||
orelse.len() == 1 && &orelse[0] == stmt
|
if_stack.push(0);
|
||||||
} else {
|
self.counter_stack.push(if_stack);
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if is_if_arm {
|
self.visit_expr(test);
|
||||||
// Initialize the vector with the count for current branch.
|
self.visit_body(body);
|
||||||
self.counter_stack.push(vec![0]);
|
|
||||||
} else {
|
for clause in elif_else_clauses {
|
||||||
// SAFETY: `unwrap` is safe because we're either in `elif` or
|
|
||||||
// `else` branch which can come only after an `if` branch.
|
|
||||||
// When inside an `if` branch, a new vector will be pushed
|
|
||||||
// onto the stack.
|
|
||||||
self.counter_stack.last_mut().unwrap().push(0);
|
self.counter_stack.last_mut().unwrap().push(0);
|
||||||
|
self.visit_elif_else_clause(clause);
|
||||||
}
|
}
|
||||||
|
|
||||||
let has_else = orelse
|
if let Some(last) = self.counter_stack.pop() {
|
||||||
.first()
|
// This is the max number of group usage from all the
|
||||||
.map_or(false, |expr| !matches!(expr, Stmt::If(_)));
|
// branches of this `if` statement.
|
||||||
|
let max_count = last.into_iter().max().unwrap_or(0);
|
||||||
self.parent_ifs.push(stmt);
|
self.increment_usage_count(max_count);
|
||||||
if has_else {
|
|
||||||
// There's no `Stmt::Else`; instead, the `else` contents are directly on
|
|
||||||
// the `orelse` of the `Stmt::If` node. We want to add a new counter for
|
|
||||||
// the `orelse` branch, but first, we need to visit the `if` body manually.
|
|
||||||
self.visit_expr(test);
|
|
||||||
self.visit_body(body);
|
|
||||||
|
|
||||||
// Now, we're in an `else` block.
|
|
||||||
self.counter_stack.last_mut().unwrap().push(0);
|
|
||||||
self.visit_body(orelse);
|
|
||||||
} else {
|
|
||||||
visitor::walk_stmt(self, stmt);
|
|
||||||
}
|
|
||||||
self.parent_ifs.pop();
|
|
||||||
|
|
||||||
if is_if_arm {
|
|
||||||
if let Some(last) = self.counter_stack.pop() {
|
|
||||||
// This is the max number of group usage from all the
|
|
||||||
// branches of this `if` statement.
|
|
||||||
let max_count = last.into_iter().max().unwrap_or(0);
|
|
||||||
self.increment_usage_count(max_count);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::Match(ast::StmtMatch {
|
Stmt::Match(ast::StmtMatch {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
use rustpython_parser::ast::{self, Expr, Ranged, Stmt};
|
use rustpython_parser::ast::{self, ElifElseClause, Expr, Ranged, Stmt};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::helpers::is_const_none;
|
use ruff_python_ast::helpers::is_const_none;
|
||||||
use ruff_python_ast::helpers::{elif_else_range, is_const_false, is_const_true};
|
use ruff_python_ast::helpers::{is_const_false, is_const_true};
|
||||||
|
use ruff_python_ast::stmt_if::elif_else_range;
|
||||||
use ruff_python_ast::visitor::Visitor;
|
use ruff_python_ast::visitor::Visitor;
|
||||||
use ruff_python_ast::whitespace::indentation;
|
use ruff_python_ast::whitespace::indentation;
|
||||||
use ruff_python_semantic::SemanticModel;
|
use ruff_python_semantic::SemanticModel;
|
||||||
|
@ -387,13 +388,25 @@ fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
/// RET503
|
/// RET503
|
||||||
fn implicit_return(checker: &mut Checker, stmt: &Stmt) {
|
fn implicit_return(checker: &mut Checker, stmt: &Stmt) {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
Stmt::If(ast::StmtIf {
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
if let Some(last_stmt) = body.last() {
|
if let Some(last_stmt) = body.last() {
|
||||||
implicit_return(checker, last_stmt);
|
implicit_return(checker, last_stmt);
|
||||||
}
|
}
|
||||||
if let Some(last_stmt) = orelse.last() {
|
for clause in elif_else_clauses {
|
||||||
implicit_return(checker, last_stmt);
|
if let Some(last_stmt) = clause.body.last() {
|
||||||
} else {
|
implicit_return(checker, last_stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we don't have an else clause
|
||||||
|
if matches!(
|
||||||
|
elif_else_clauses.last(),
|
||||||
|
None | Some(ast::ElifElseClause { test: Some(_), .. })
|
||||||
|
) {
|
||||||
let mut diagnostic = Diagnostic::new(ImplicitReturn, stmt.range());
|
let mut diagnostic = Diagnostic::new(ImplicitReturn, stmt.range());
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if let Some(indent) = indentation(checker.locator, stmt) {
|
if let Some(indent) = indentation(checker.locator, stmt) {
|
||||||
|
@ -564,13 +577,21 @@ fn unnecessary_assign(checker: &mut Checker, stack: &Stack) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RET505, RET506, RET507, RET508
|
/// RET505, RET506, RET507, RET508
|
||||||
fn superfluous_else_node(checker: &mut Checker, stmt: &ast::StmtIf, branch: Branch) -> bool {
|
fn superfluous_else_node(
|
||||||
let ast::StmtIf { body, .. } = stmt;
|
checker: &mut Checker,
|
||||||
for child in body {
|
if_elif_body: &[Stmt],
|
||||||
|
elif_else: &ElifElseClause,
|
||||||
|
) -> bool {
|
||||||
|
let branch = if elif_else.test.is_some() {
|
||||||
|
Branch::Elif
|
||||||
|
} else {
|
||||||
|
Branch::Else
|
||||||
|
};
|
||||||
|
for child in if_elif_body {
|
||||||
if child.is_return_stmt() {
|
if child.is_return_stmt() {
|
||||||
let diagnostic = Diagnostic::new(
|
let diagnostic = Diagnostic::new(
|
||||||
SuperfluousElseReturn { branch },
|
SuperfluousElseReturn { branch },
|
||||||
elif_else_range(stmt, checker.locator).unwrap_or_else(|| stmt.range()),
|
elif_else_range(elif_else, checker.locator).unwrap_or_else(|| elif_else.range()),
|
||||||
);
|
);
|
||||||
if checker.enabled(diagnostic.kind.rule()) {
|
if checker.enabled(diagnostic.kind.rule()) {
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
@ -579,7 +600,7 @@ fn superfluous_else_node(checker: &mut Checker, stmt: &ast::StmtIf, branch: Bran
|
||||||
} else if child.is_break_stmt() {
|
} else if child.is_break_stmt() {
|
||||||
let diagnostic = Diagnostic::new(
|
let diagnostic = Diagnostic::new(
|
||||||
SuperfluousElseBreak { branch },
|
SuperfluousElseBreak { branch },
|
||||||
elif_else_range(stmt, checker.locator).unwrap_or_else(|| stmt.range()),
|
elif_else_range(elif_else, checker.locator).unwrap_or_else(|| elif_else.range()),
|
||||||
);
|
);
|
||||||
if checker.enabled(diagnostic.kind.rule()) {
|
if checker.enabled(diagnostic.kind.rule()) {
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
@ -588,7 +609,7 @@ fn superfluous_else_node(checker: &mut Checker, stmt: &ast::StmtIf, branch: Bran
|
||||||
} else if child.is_raise_stmt() {
|
} else if child.is_raise_stmt() {
|
||||||
let diagnostic = Diagnostic::new(
|
let diagnostic = Diagnostic::new(
|
||||||
SuperfluousElseRaise { branch },
|
SuperfluousElseRaise { branch },
|
||||||
elif_else_range(stmt, checker.locator).unwrap_or_else(|| stmt.range()),
|
elif_else_range(elif_else, checker.locator).unwrap_or_else(|| elif_else.range()),
|
||||||
);
|
);
|
||||||
if checker.enabled(diagnostic.kind.rule()) {
|
if checker.enabled(diagnostic.kind.rule()) {
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
@ -597,7 +618,7 @@ fn superfluous_else_node(checker: &mut Checker, stmt: &ast::StmtIf, branch: Bran
|
||||||
} else if child.is_continue_stmt() {
|
} else if child.is_continue_stmt() {
|
||||||
let diagnostic = Diagnostic::new(
|
let diagnostic = Diagnostic::new(
|
||||||
SuperfluousElseContinue { branch },
|
SuperfluousElseContinue { branch },
|
||||||
elif_else_range(stmt, checker.locator).unwrap_or_else(|| stmt.range()),
|
elif_else_range(elif_else, checker.locator).unwrap_or_else(|| elif_else.range()),
|
||||||
);
|
);
|
||||||
if checker.enabled(diagnostic.kind.rule()) {
|
if checker.enabled(diagnostic.kind.rule()) {
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
@ -609,16 +630,9 @@ fn superfluous_else_node(checker: &mut Checker, stmt: &ast::StmtIf, branch: Bran
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RET505, RET506, RET507, RET508
|
/// RET505, RET506, RET507, RET508
|
||||||
fn superfluous_elif(checker: &mut Checker, stack: &Stack) {
|
fn superfluous_elif_else(checker: &mut Checker, stack: &Stack) {
|
||||||
for stmt in &stack.elifs {
|
for (if_elif_body, elif_else) in &stack.elifs_elses {
|
||||||
superfluous_else_node(checker, stmt, Branch::Elif);
|
superfluous_else_node(checker, if_elif_body, elif_else);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RET505, RET506, RET507, RET508
|
|
||||||
fn superfluous_else(checker: &mut Checker, stack: &Stack) {
|
|
||||||
for stmt in &stack.elses {
|
|
||||||
superfluous_else_node(checker, stmt, Branch::Else);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,8 +669,7 @@ pub(crate) fn function(checker: &mut Checker, body: &[Stmt], returns: Option<&Ex
|
||||||
Rule::SuperfluousElseContinue,
|
Rule::SuperfluousElseContinue,
|
||||||
Rule::SuperfluousElseBreak,
|
Rule::SuperfluousElseBreak,
|
||||||
]) {
|
]) {
|
||||||
superfluous_elif(checker, &stack);
|
superfluous_elif_else(checker, &stack);
|
||||||
superfluous_else(checker, &stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip any functions without return statements.
|
// Skip any functions without return statements.
|
||||||
|
|
|
@ -70,4 +70,13 @@ RET508.py:82:9: RET508 Unnecessary `else` after `break` statement
|
||||||
84 | return
|
84 | return
|
||||||
|
|
|
|
||||||
|
|
||||||
|
RET508.py:158:13: RET508 Unnecessary `else` after `break` statement
|
||||||
|
|
|
||||||
|
156 | if i > w:
|
||||||
|
157 | break
|
||||||
|
158 | else:
|
||||||
|
| ^^^^ RET508
|
||||||
|
159 | a = z
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use rustpython_parser::ast::{self, Expr, Identifier, Stmt};
|
use rustpython_parser::ast::{self, ElifElseClause, Expr, Identifier, Stmt};
|
||||||
|
|
||||||
use ruff_python_ast::visitor;
|
use ruff_python_ast::visitor;
|
||||||
use ruff_python_ast::visitor::Visitor;
|
use ruff_python_ast::visitor::Visitor;
|
||||||
|
@ -8,10 +8,8 @@ use ruff_python_ast::visitor::Visitor;
|
||||||
pub(super) struct Stack<'a> {
|
pub(super) struct Stack<'a> {
|
||||||
/// The `return` statements in the current function.
|
/// The `return` statements in the current function.
|
||||||
pub(super) returns: Vec<&'a ast::StmtReturn>,
|
pub(super) returns: Vec<&'a ast::StmtReturn>,
|
||||||
/// The `else` statements in the current function.
|
/// The `elif` or `else` statements in the current function.
|
||||||
pub(super) elses: Vec<&'a ast::StmtIf>,
|
pub(super) elifs_elses: Vec<(&'a [Stmt], &'a ElifElseClause)>,
|
||||||
/// The `elif` statements in the current function.
|
|
||||||
pub(super) elifs: Vec<&'a ast::StmtIf>,
|
|
||||||
/// The non-local variables in the current function.
|
/// The non-local variables in the current function.
|
||||||
pub(super) non_locals: FxHashSet<&'a str>,
|
pub(super) non_locals: FxHashSet<&'a str>,
|
||||||
/// Whether the current function is a generator.
|
/// Whether the current function is a generator.
|
||||||
|
@ -117,27 +115,13 @@ impl<'a> Visitor<'a> for ReturnVisitor<'a> {
|
||||||
|
|
||||||
self.stack.returns.push(stmt_return);
|
self.stack.returns.push(stmt_return);
|
||||||
}
|
}
|
||||||
Stmt::If(stmt_if) => {
|
Stmt::If(ast::StmtIf {
|
||||||
let is_elif_arm = self.parents.iter().any(|parent| {
|
body,
|
||||||
if let Stmt::If(ast::StmtIf { orelse, .. }) = parent {
|
elif_else_clauses,
|
||||||
orelse.len() == 1 && &orelse[0] == stmt
|
..
|
||||||
} else {
|
}) => {
|
||||||
false
|
if let Some(first) = elif_else_clauses.first() {
|
||||||
}
|
self.stack.elifs_elses.push((body, first));
|
||||||
});
|
|
||||||
|
|
||||||
if !is_elif_arm {
|
|
||||||
let has_elif =
|
|
||||||
stmt_if.orelse.len() == 1 && stmt_if.orelse.first().unwrap().is_if_stmt();
|
|
||||||
let has_else = !stmt_if.orelse.is_empty();
|
|
||||||
|
|
||||||
if has_elif {
|
|
||||||
// `stmt` is an `if` block followed by an `elif` clause.
|
|
||||||
self.stack.elifs.push(stmt_if);
|
|
||||||
} else if has_else {
|
|
||||||
// `stmt` is an `if` block followed by an `else` clause.
|
|
||||||
self.stack.elses.push(stmt_if);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
use log::error;
|
use log::error;
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use rustpython_parser::ast::{self, CmpOp, Constant, Expr, ExprContext, Identifier, Ranged, Stmt};
|
use rustpython_parser::ast::{
|
||||||
|
self, CmpOp, Constant, ElifElseClause, Expr, ExprContext, Identifier, Ranged, Stmt, StmtIf,
|
||||||
|
};
|
||||||
|
|
||||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::comparable::{ComparableConstant, ComparableExpr, ComparableStmt};
|
use ruff_python_ast::comparable::{ComparableConstant, ComparableExpr, ComparableStmt};
|
||||||
use ruff_python_ast::helpers::{any_over_expr, contains_effect, first_colon_range, has_comments};
|
use ruff_python_ast::helpers::{any_over_expr, contains_effect, first_colon_range, has_comments};
|
||||||
|
use ruff_python_ast::source_code::Locator;
|
||||||
|
use ruff_python_ast::stmt_if::if_elif_branches;
|
||||||
use ruff_python_semantic::SemanticModel;
|
use ruff_python_semantic::SemanticModel;
|
||||||
use ruff_python_whitespace::UniversalNewlines;
|
use ruff_python_whitespace::UniversalNewlines;
|
||||||
|
|
||||||
|
@ -23,16 +27,6 @@ fn compare_stmt(stmt1: &ComparableStmt, stmt2: &ComparableStmt) -> bool {
|
||||||
stmt1.eq(stmt2)
|
stmt1.eq(stmt2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_body(body1: &[Stmt], body2: &[Stmt]) -> bool {
|
|
||||||
if body1.len() != body2.len() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
body1
|
|
||||||
.iter()
|
|
||||||
.zip(body2.iter())
|
|
||||||
.all(|(stmt1, stmt2)| compare_stmt(&stmt1.into(), &stmt2.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for nested `if` statements that can be collapsed into a single `if`
|
/// Checks for nested `if` statements that can be collapsed into a single `if`
|
||||||
/// statement.
|
/// statement.
|
||||||
|
@ -287,7 +281,7 @@ fn is_main_check(expr: &Expr) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the last nested if statement and return the test expression and the
|
/// Find the last nested if statement and return the test expression and the
|
||||||
/// first statement.
|
/// last statement.
|
||||||
///
|
///
|
||||||
/// ```python
|
/// ```python
|
||||||
/// if xxx:
|
/// if xxx:
|
||||||
|
@ -301,13 +295,13 @@ fn find_last_nested_if(body: &[Stmt]) -> Option<(&Expr, &Stmt)> {
|
||||||
let [Stmt::If(ast::StmtIf {
|
let [Stmt::If(ast::StmtIf {
|
||||||
test,
|
test,
|
||||||
body: inner_body,
|
body: inner_body,
|
||||||
orelse,
|
elif_else_clauses,
|
||||||
..
|
..
|
||||||
})] = body
|
})] = body
|
||||||
else {
|
else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if !orelse.is_empty() {
|
if !elif_else_clauses.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
find_last_nested_if(inner_body).or_else(|| {
|
find_last_nested_if(inner_body).or_else(|| {
|
||||||
|
@ -318,30 +312,36 @@ fn find_last_nested_if(body: &[Stmt]) -> Option<(&Expr, &Stmt)> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SIM102
|
fn nested_if_body(stmt_if: &StmtIf) -> Option<(&[Stmt], TextRange)> {
|
||||||
pub(crate) fn nested_if_statements(
|
let StmtIf {
|
||||||
checker: &mut Checker,
|
test,
|
||||||
stmt: &Stmt,
|
body,
|
||||||
test: &Expr,
|
elif_else_clauses,
|
||||||
body: &[Stmt],
|
..
|
||||||
orelse: &[Stmt],
|
} = stmt_if;
|
||||||
parent: Option<&Stmt>,
|
|
||||||
) {
|
|
||||||
// If the parent could contain a nested if-statement, abort.
|
|
||||||
if let Some(Stmt::If(ast::StmtIf { body, orelse, .. })) = parent {
|
|
||||||
if orelse.is_empty() && body.len() == 1 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this if-statement has an else clause, or more than one child, abort.
|
// It must be the last condition, otherwise there could be another `elif` or `else` that only
|
||||||
if !(orelse.is_empty() && body.len() == 1) {
|
// depends on the outer of the two conditions
|
||||||
return;
|
let (test, body, range) = if let Some(clause) = elif_else_clauses.last() {
|
||||||
|
if let Some(test) = &clause.test {
|
||||||
|
(test, &clause.body, clause.range())
|
||||||
|
} else {
|
||||||
|
// The last condition is an `else` (different rule)
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(test.as_ref(), body, stmt_if.range())
|
||||||
|
};
|
||||||
|
|
||||||
|
// The nested if must be the only child, otherwise there is at least one more statement that
|
||||||
|
// only depends on the outer condition
|
||||||
|
if body.len() > 1 {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow `if __name__ == "__main__":` statements.
|
// Allow `if __name__ == "__main__":` statements.
|
||||||
if is_main_check(test) {
|
if is_main_check(test) {
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow `if True:` and `if False:` statements.
|
// Allow `if True:` and `if False:` statements.
|
||||||
|
@ -352,9 +352,18 @@ pub(crate) fn nested_if_statements(
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some((body, range))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SIM102
|
||||||
|
pub(crate) fn nested_if_statements(checker: &mut Checker, stmt_if: &StmtIf, parent: Option<&Stmt>) {
|
||||||
|
let Some((body, range)) = nested_if_body(stmt_if) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
// Find the deepest nested if-statement, to inform the range.
|
// Find the deepest nested if-statement, to inform the range.
|
||||||
let Some((test, first_stmt)) = find_last_nested_if(body) else {
|
let Some((test, first_stmt)) = find_last_nested_if(body) else {
|
||||||
return;
|
return;
|
||||||
|
@ -365,12 +374,22 @@ pub(crate) fn nested_if_statements(
|
||||||
checker.locator,
|
checker.locator,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check if the parent is already emitting a larger diagnostic including this if statement
|
||||||
|
if let Some(Stmt::If(stmt_if)) = parent {
|
||||||
|
if let Some((body, _range)) = nested_if_body(stmt_if) {
|
||||||
|
// In addition to repeating the `nested_if_body` and `find_last_nested_if` check, we
|
||||||
|
// also need to be the first child in the parent
|
||||||
|
if matches!(&body[0], Stmt::If(inner) if inner == stmt_if)
|
||||||
|
&& find_last_nested_if(body).is_some()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
CollapsibleIf,
|
CollapsibleIf,
|
||||||
colon.map_or_else(
|
colon.map_or(range, |colon| TextRange::new(range.start(), colon.end())),
|
||||||
|| stmt.range(),
|
|
||||||
|colon| TextRange::new(stmt.start(), colon.end()),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
// The fixer preserves comments in the nested body, but removes comments between
|
// The fixer preserves comments in the nested body, but removes comments between
|
||||||
|
@ -379,9 +398,9 @@ pub(crate) fn nested_if_statements(
|
||||||
if !checker
|
if !checker
|
||||||
.indexer
|
.indexer
|
||||||
.comment_ranges()
|
.comment_ranges()
|
||||||
.intersects(TextRange::new(stmt.start(), nested_if.start()))
|
.intersects(TextRange::new(range.start(), nested_if.start()))
|
||||||
{
|
{
|
||||||
match fix_if::fix_nested_if_statements(checker.locator, checker.stylist, stmt) {
|
match fix_if::fix_nested_if_statements(checker.locator, checker.stylist, range) {
|
||||||
Ok(edit) => {
|
Ok(edit) => {
|
||||||
if edit
|
if edit
|
||||||
.content()
|
.content()
|
||||||
|
@ -437,17 +456,43 @@ fn is_one_line_return_bool(stmts: &[Stmt]) -> Option<Bool> {
|
||||||
/// SIM103
|
/// SIM103
|
||||||
pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
|
pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
|
||||||
let Stmt::If(ast::StmtIf {
|
let Stmt::If(ast::StmtIf {
|
||||||
test,
|
test: if_test,
|
||||||
body,
|
body: if_body,
|
||||||
orelse,
|
elif_else_clauses,
|
||||||
range: _,
|
range: _,
|
||||||
}) = stmt
|
}) = stmt
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// Extract an `if` or `elif` (that returns) followed by an else (that returns the same value)
|
||||||
|
let (if_test, if_body, else_body, range) = match elif_else_clauses.as_slice() {
|
||||||
|
// if-else case
|
||||||
|
[ElifElseClause {
|
||||||
|
body: else_body,
|
||||||
|
test: None,
|
||||||
|
..
|
||||||
|
}] => (if_test.as_ref(), if_body, else_body, stmt.range()),
|
||||||
|
// elif-else case
|
||||||
|
[.., ElifElseClause {
|
||||||
|
body: elif_body,
|
||||||
|
test: Some(elif_test),
|
||||||
|
range: elif_range,
|
||||||
|
}, ElifElseClause {
|
||||||
|
body: else_body,
|
||||||
|
test: None,
|
||||||
|
range: else_range,
|
||||||
|
}] => (
|
||||||
|
elif_test,
|
||||||
|
elif_body,
|
||||||
|
else_body,
|
||||||
|
TextRange::new(elif_range.start(), else_range.end()),
|
||||||
|
),
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
let (Some(if_return), Some(else_return)) = (
|
let (Some(if_return), Some(else_return)) = (
|
||||||
is_one_line_return_bool(body),
|
is_one_line_return_bool(if_body),
|
||||||
is_one_line_return_bool(orelse),
|
is_one_line_return_bool(else_body),
|
||||||
) else {
|
) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -458,23 +503,23 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let condition = checker.generator().expr(test);
|
let condition = checker.generator().expr(if_test);
|
||||||
let mut diagnostic = Diagnostic::new(NeedlessBool { condition }, stmt.range());
|
let mut diagnostic = Diagnostic::new(NeedlessBool { condition }, range);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if matches!(if_return, Bool::True)
|
if matches!(if_return, Bool::True)
|
||||||
&& matches!(else_return, Bool::False)
|
&& matches!(else_return, Bool::False)
|
||||||
&& !has_comments(stmt, checker.locator, checker.indexer)
|
&& !has_comments(&range, checker.locator, checker.indexer)
|
||||||
&& (test.is_compare_expr() || checker.semantic().is_builtin("bool"))
|
&& (if_test.is_compare_expr() || checker.semantic().is_builtin("bool"))
|
||||||
{
|
{
|
||||||
if test.is_compare_expr() {
|
if if_test.is_compare_expr() {
|
||||||
// If the condition is a comparison, we can replace it with the condition.
|
// If the condition is a comparison, we can replace it with the condition.
|
||||||
let node = ast::StmtReturn {
|
let node = ast::StmtReturn {
|
||||||
value: Some(test.clone()),
|
value: Some(Box::new(if_test.clone())),
|
||||||
range: TextRange::default(),
|
range: TextRange::default(),
|
||||||
};
|
};
|
||||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||||
checker.generator().stmt(&node.into()),
|
checker.generator().stmt(&node.into()),
|
||||||
stmt.range(),
|
range,
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we need to wrap the condition in a call to `bool`. (We've already
|
// Otherwise, we need to wrap the condition in a call to `bool`. (We've already
|
||||||
|
@ -486,7 +531,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
|
||||||
};
|
};
|
||||||
let node1 = ast::ExprCall {
|
let node1 = ast::ExprCall {
|
||||||
func: Box::new(node.into()),
|
func: Box::new(node.into()),
|
||||||
args: vec![(**test).clone()],
|
args: vec![if_test.clone()],
|
||||||
keywords: vec![],
|
keywords: vec![],
|
||||||
range: TextRange::default(),
|
range: TextRange::default(),
|
||||||
};
|
};
|
||||||
|
@ -496,7 +541,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
|
||||||
};
|
};
|
||||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||||
checker.generator().stmt(&node2.into()),
|
checker.generator().stmt(&node2.into()),
|
||||||
stmt.range(),
|
range,
|
||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -520,99 +565,71 @@ fn ternary(target_var: &Expr, body_value: &Expr, test: &Expr, orelse_value: &Exp
|
||||||
node1.into()
|
node1.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the `Expr` contains a reference to `${module}.${target}`.
|
/// Return `true` if the `Expr` contains a reference to any of the given `${module}.${target}`.
|
||||||
fn contains_call_path(expr: &Expr, target: &[&str], semantic: &SemanticModel) -> bool {
|
fn contains_call_path(expr: &Expr, targets: &[&[&str]], semantic: &SemanticModel) -> bool {
|
||||||
any_over_expr(expr, &|expr| {
|
any_over_expr(expr, &|expr| {
|
||||||
semantic
|
semantic.resolve_call_path(expr).map_or(false, |call_path| {
|
||||||
.resolve_call_path(expr)
|
targets.iter().any(|target| &call_path.as_slice() == target)
|
||||||
.map_or(false, |call_path| call_path.as_slice() == target)
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SIM108
|
/// SIM108
|
||||||
pub(crate) fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt, parent: Option<&Stmt>) {
|
pub(crate) fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt) {
|
||||||
let Stmt::If(ast::StmtIf {
|
let Stmt::If(ast::StmtIf {
|
||||||
test,
|
test,
|
||||||
body,
|
body,
|
||||||
orelse,
|
elif_else_clauses,
|
||||||
range: _,
|
range: _,
|
||||||
}) = stmt
|
}) = stmt
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if body.len() != 1 || orelse.len() != 1 {
|
// `test: None` to only match an `else` clause
|
||||||
|
let [ElifElseClause {
|
||||||
|
body: else_body,
|
||||||
|
test: None,
|
||||||
|
..
|
||||||
|
}] = elif_else_clauses.as_slice()
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
let Stmt::Assign(ast::StmtAssign {
|
let [Stmt::Assign(ast::StmtAssign {
|
||||||
targets: body_targets,
|
targets: body_targets,
|
||||||
value: body_value,
|
value: body_value,
|
||||||
..
|
..
|
||||||
}) = &body[0]
|
})] = body.as_slice()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Stmt::Assign(ast::StmtAssign {
|
let [Stmt::Assign(ast::StmtAssign {
|
||||||
targets: orelse_targets,
|
targets: else_targets,
|
||||||
value: orelse_value,
|
value: else_value,
|
||||||
..
|
..
|
||||||
}) = &orelse[0]
|
})] = else_body.as_slice()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if body_targets.len() != 1 || orelse_targets.len() != 1 {
|
let ([body_target], [else_target]) = (body_targets.as_slice(), else_targets.as_slice()) else {
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Expr::Name(ast::ExprName { id: body_id, .. }) = &body_targets[0] else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Expr::Name(ast::ExprName { id: orelse_id, .. }) = &orelse_targets[0] else {
|
let Expr::Name(ast::ExprName { id: body_id, .. }) = body_target else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if body_id != orelse_id {
|
let Expr::Name(ast::ExprName { id: else_id, .. }) = else_target else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if body_id != else_id {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid suggesting ternary for `if sys.version_info >= ...`-style checks.
|
// Avoid suggesting ternary for `if sys.version_info >= ...`-style and
|
||||||
if contains_call_path(test, &["sys", "version_info"], checker.semantic()) {
|
// `if sys.platform.startswith("...")`-style checks.
|
||||||
|
let ignored_call_paths: &[&[&str]] = &[&["sys", "version_info"], &["sys", "platform"]];
|
||||||
|
if contains_call_path(test, ignored_call_paths, checker.semantic()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid suggesting ternary for `if sys.platform.startswith("...")`-style
|
|
||||||
// checks.
|
|
||||||
if contains_call_path(test, &["sys", "platform"], checker.semantic()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's part of a bigger if-elif block:
|
|
||||||
// https://github.com/MartinThoma/flake8-simplify/issues/115
|
|
||||||
if let Some(Stmt::If(ast::StmtIf {
|
|
||||||
orelse: parent_orelse,
|
|
||||||
..
|
|
||||||
})) = parent
|
|
||||||
{
|
|
||||||
if parent_orelse.len() == 1 && stmt == &parent_orelse[0] {
|
|
||||||
// TODO(charlie): These two cases have the same AST:
|
|
||||||
//
|
|
||||||
// if True:
|
|
||||||
// pass
|
|
||||||
// elif a:
|
|
||||||
// b = 1
|
|
||||||
// else:
|
|
||||||
// b = 2
|
|
||||||
//
|
|
||||||
// if True:
|
|
||||||
// pass
|
|
||||||
// else:
|
|
||||||
// if a:
|
|
||||||
// b = 1
|
|
||||||
// else:
|
|
||||||
// b = 2
|
|
||||||
//
|
|
||||||
// We want to flag the latter, but not the former. Right now, we flag neither.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid suggesting ternary for `if (yield ...)`-style checks.
|
// Avoid suggesting ternary for `if (yield ...)`-style checks.
|
||||||
// TODO(charlie): Fix precedence handling for yields in generator.
|
// TODO(charlie): Fix precedence handling for yields in generator.
|
||||||
if matches!(
|
if matches!(
|
||||||
|
@ -622,14 +639,14 @@ pub(crate) fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt, parent: O
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if matches!(
|
if matches!(
|
||||||
orelse_value.as_ref(),
|
else_value.as_ref(),
|
||||||
Expr::Yield(_) | Expr::YieldFrom(_) | Expr::Await(_)
|
Expr::Yield(_) | Expr::YieldFrom(_) | Expr::Await(_)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let target_var = &body_targets[0];
|
let target_var = &body_target;
|
||||||
let ternary = ternary(target_var, body_value, test, orelse_value);
|
let ternary = ternary(target_var, body_value, test, else_value);
|
||||||
let contents = checker.generator().stmt(&ternary);
|
let contents = checker.generator().stmt(&ternary);
|
||||||
|
|
||||||
// Don't flag if the resulting expression would exceed the maximum line length.
|
// Don't flag if the resulting expression would exceed the maximum line length.
|
||||||
|
@ -659,135 +676,85 @@ pub(crate) fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt, parent: O
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_if_body_pairs<'a>(
|
|
||||||
test: &'a Expr,
|
|
||||||
body: &'a [Stmt],
|
|
||||||
orelse: &'a [Stmt],
|
|
||||||
) -> Vec<(&'a Expr, &'a [Stmt])> {
|
|
||||||
let mut pairs = vec![(test, body)];
|
|
||||||
let mut orelse = orelse;
|
|
||||||
loop {
|
|
||||||
if orelse.len() != 1 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let Stmt::If(ast::StmtIf {
|
|
||||||
test,
|
|
||||||
body,
|
|
||||||
orelse: orelse_orelse,
|
|
||||||
range: _,
|
|
||||||
}) = &orelse[0]
|
|
||||||
else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
pairs.push((test, body));
|
|
||||||
orelse = orelse_orelse;
|
|
||||||
}
|
|
||||||
pairs
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SIM114
|
/// SIM114
|
||||||
pub(crate) fn if_with_same_arms(checker: &mut Checker, stmt: &Stmt, parent: Option<&Stmt>) {
|
pub(crate) fn if_with_same_arms(checker: &mut Checker, locator: &Locator, stmt_if: &StmtIf) {
|
||||||
let Stmt::If(ast::StmtIf {
|
let mut branches_iter = if_elif_branches(stmt_if).peekable();
|
||||||
test,
|
while let Some(current_branch) = branches_iter.next() {
|
||||||
body,
|
let Some(following_branch) = branches_iter.peek() else {
|
||||||
orelse,
|
continue;
|
||||||
range: _,
|
};
|
||||||
}) = stmt
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// It's part of a bigger if-elif block:
|
// The bodies must have the same code ...
|
||||||
// https://github.com/MartinThoma/flake8-simplify/issues/115
|
if current_branch.body.len() != following_branch.body.len() {
|
||||||
if let Some(Stmt::If(ast::StmtIf {
|
continue;
|
||||||
orelse: parent_orelse,
|
}
|
||||||
..
|
if !current_branch
|
||||||
})) = parent
|
.body
|
||||||
{
|
.iter()
|
||||||
if parent_orelse.len() == 1 && stmt == &parent_orelse[0] {
|
.zip(following_branch.body.iter())
|
||||||
// TODO(charlie): These two cases have the same AST:
|
.all(|(stmt1, stmt2)| compare_stmt(&stmt1.into(), &stmt2.into()))
|
||||||
//
|
{
|
||||||
// if True:
|
continue;
|
||||||
// pass
|
|
||||||
// elif a:
|
|
||||||
// b = 1
|
|
||||||
// else:
|
|
||||||
// b = 2
|
|
||||||
//
|
|
||||||
// if True:
|
|
||||||
// pass
|
|
||||||
// else:
|
|
||||||
// if a:
|
|
||||||
// b = 1
|
|
||||||
// else:
|
|
||||||
// b = 2
|
|
||||||
//
|
|
||||||
// We want to flag the latter, but not the former. Right now, we flag neither.
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let if_body_pairs = get_if_body_pairs(test, body, orelse);
|
// ...and the same comments
|
||||||
for i in 0..(if_body_pairs.len() - 1) {
|
let first_comments: Vec<_> = checker
|
||||||
let (test, body) = &if_body_pairs[i];
|
.indexer
|
||||||
let (.., next_body) = &if_body_pairs[i + 1];
|
.comments_in_range(current_branch.range, locator)
|
||||||
if compare_body(body, next_body) {
|
.collect();
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
let second_comments: Vec<_> = checker
|
||||||
IfWithSameArms,
|
.indexer
|
||||||
TextRange::new(
|
.comments_in_range(following_branch.range, locator)
|
||||||
if i == 0 { stmt.start() } else { test.start() },
|
.collect();
|
||||||
next_body.last().unwrap().end(),
|
if first_comments != second_comments {
|
||||||
),
|
continue;
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
IfWithSameArms,
|
||||||
|
TextRange::new(
|
||||||
|
current_branch.range.start(),
|
||||||
|
following_branch.body.last().unwrap().end(),
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SIM116
|
/// SIM116
|
||||||
pub(crate) fn manual_dict_lookup(
|
pub(crate) fn manual_dict_lookup(checker: &mut Checker, stmt_if: &StmtIf) {
|
||||||
checker: &mut Checker,
|
|
||||||
stmt: &Stmt,
|
|
||||||
test: &Expr,
|
|
||||||
body: &[Stmt],
|
|
||||||
orelse: &[Stmt],
|
|
||||||
parent: Option<&Stmt>,
|
|
||||||
) {
|
|
||||||
// Throughout this rule:
|
// Throughout this rule:
|
||||||
// * Each if-statement's test must consist of a constant equality check with the same variable.
|
// * Each if or elif statement's test must consist of a constant equality check with the same variable.
|
||||||
// * Each if-statement's body must consist of a single `return`.
|
// * Each if or elif statement's body must consist of a single `return`.
|
||||||
// * Each if-statement's orelse must be either another if-statement or empty.
|
// * The else clause must be empty, or a single `return`.
|
||||||
// * The final if-statement's orelse must be empty, or a single `return`.
|
let StmtIf {
|
||||||
|
body,
|
||||||
|
test,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
} = stmt_if;
|
||||||
|
|
||||||
let Expr::Compare(ast::ExprCompare {
|
let Expr::Compare(ast::ExprCompare {
|
||||||
left,
|
left,
|
||||||
ops,
|
ops,
|
||||||
comparators,
|
comparators,
|
||||||
range: _,
|
range: _,
|
||||||
}) = &test
|
}) = test.as_ref()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Expr::Name(ast::ExprName { id: target, .. }) = left.as_ref() else {
|
let Expr::Name(ast::ExprName { id: target, .. }) = left.as_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if body.len() != 1 {
|
if ops != &[CmpOp::Eq] {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if orelse.len() != 1 {
|
let [Expr::Constant(ast::ExprConstant {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if !(ops.len() == 1 && ops[0] == CmpOp::Eq) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if comparators.len() != 1 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Expr::Constant(ast::ExprConstant {
|
|
||||||
value: constant, ..
|
value: constant, ..
|
||||||
}) = &comparators[0]
|
})] = comparators.as_slice()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Stmt::Return(ast::StmtReturn { value, range: _ }) = &body[0] else {
|
let [Stmt::Return(ast::StmtReturn { value, range: _ })] = body.as_slice() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if value.as_ref().map_or(false, |value| {
|
if value.as_ref().map_or(false, |value| {
|
||||||
|
@ -796,99 +763,60 @@ pub(crate) fn manual_dict_lookup(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's part of a bigger if-elif block:
|
|
||||||
// https://github.com/MartinThoma/flake8-simplify/issues/115
|
|
||||||
if let Some(Stmt::If(ast::StmtIf {
|
|
||||||
orelse: parent_orelse,
|
|
||||||
..
|
|
||||||
})) = parent
|
|
||||||
{
|
|
||||||
if parent_orelse.len() == 1 && stmt == &parent_orelse[0] {
|
|
||||||
// TODO(charlie): These two cases have the same AST:
|
|
||||||
//
|
|
||||||
// if True:
|
|
||||||
// pass
|
|
||||||
// elif a:
|
|
||||||
// b = 1
|
|
||||||
// else:
|
|
||||||
// b = 2
|
|
||||||
//
|
|
||||||
// if True:
|
|
||||||
// pass
|
|
||||||
// else:
|
|
||||||
// if a:
|
|
||||||
// b = 1
|
|
||||||
// else:
|
|
||||||
// b = 2
|
|
||||||
//
|
|
||||||
// We want to flag the latter, but not the former. Right now, we flag neither.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut constants: FxHashSet<ComparableConstant> = FxHashSet::default();
|
let mut constants: FxHashSet<ComparableConstant> = FxHashSet::default();
|
||||||
constants.insert(constant.into());
|
constants.insert(constant.into());
|
||||||
|
|
||||||
let mut child: Option<&Stmt> = orelse.get(0);
|
for clause in elif_else_clauses {
|
||||||
while let Some(current) = child.take() {
|
let ElifElseClause { test, body, .. } = clause;
|
||||||
let Stmt::If(ast::StmtIf {
|
let [Stmt::Return(ast::StmtReturn { value, range: _ })] = body.as_slice() else {
|
||||||
test,
|
|
||||||
body,
|
|
||||||
orelse,
|
|
||||||
range: _,
|
|
||||||
}) = ¤t
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if body.len() != 1 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if orelse.len() > 1 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Expr::Compare(ast::ExprCompare {
|
|
||||||
left,
|
|
||||||
ops,
|
|
||||||
comparators,
|
|
||||||
range: _,
|
|
||||||
}) = test.as_ref()
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if !(id == target && matches!(ops.as_slice(), [CmpOp::Eq])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let [Expr::Constant(ast::ExprConstant {
|
|
||||||
value: constant, ..
|
|
||||||
})] = comparators.as_slice()
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Stmt::Return(ast::StmtReturn { value, range: _ }) = &body[0] else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if value.as_ref().map_or(false, |value| {
|
|
||||||
contains_effect(value, |id| checker.semantic().is_builtin(id))
|
|
||||||
}) {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
constants.insert(constant.into());
|
match test.as_ref() {
|
||||||
if let Some(orelse) = orelse.first() {
|
// `else`
|
||||||
match orelse {
|
None => {
|
||||||
Stmt::If(_) => {
|
// The else must also be a single effect-free return statement
|
||||||
child = Some(orelse);
|
let [Stmt::Return(ast::StmtReturn { value, range: _ })] = body.as_slice() else {
|
||||||
}
|
return;
|
||||||
Stmt::Return(_) => {
|
};
|
||||||
child = None;
|
if value.as_ref().map_or(false, |value| {
|
||||||
}
|
contains_effect(value, |id| checker.semantic().is_builtin(id))
|
||||||
_ => return,
|
}) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// `elif`
|
||||||
|
Some(Expr::Compare(ast::ExprCompare {
|
||||||
|
left,
|
||||||
|
ops,
|
||||||
|
comparators,
|
||||||
|
range: _,
|
||||||
|
})) => {
|
||||||
|
let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if id != target || ops != &[CmpOp::Eq] {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let [Expr::Constant(ast::ExprConstant {
|
||||||
|
value: constant, ..
|
||||||
|
})] = comparators.as_slice()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if value.as_ref().map_or(false, |value| {
|
||||||
|
contains_effect(value, |id| checker.semantic().is_builtin(id))
|
||||||
|
}) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
constants.insert(constant.into());
|
||||||
|
}
|
||||||
|
// Different `elif`
|
||||||
|
_ => {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
child = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -898,27 +826,38 @@ pub(crate) fn manual_dict_lookup(
|
||||||
|
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
IfElseBlockInsteadOfDictLookup,
|
IfElseBlockInsteadOfDictLookup,
|
||||||
stmt.range(),
|
stmt_if.range(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SIM401
|
/// SIM401
|
||||||
pub(crate) fn use_dict_get_with_default(
|
pub(crate) fn use_dict_get_with_default(checker: &mut Checker, stmt_if: &StmtIf) {
|
||||||
checker: &mut Checker,
|
let StmtIf {
|
||||||
stmt: &Stmt,
|
test,
|
||||||
test: &Expr,
|
body,
|
||||||
body: &[Stmt],
|
elif_else_clauses,
|
||||||
orelse: &[Stmt],
|
..
|
||||||
parent: Option<&Stmt>,
|
} = stmt_if;
|
||||||
) {
|
|
||||||
if body.len() != 1 || orelse.len() != 1 {
|
let [body_stmt] = body.as_slice() else {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
let [ElifElseClause {
|
||||||
|
body: else_body,
|
||||||
|
test: None,
|
||||||
|
..
|
||||||
|
}] = elif_else_clauses.as_slice()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let [else_body_stmt] = else_body.as_slice() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let Stmt::Assign(ast::StmtAssign {
|
let Stmt::Assign(ast::StmtAssign {
|
||||||
targets: body_var,
|
targets: body_var,
|
||||||
value: body_value,
|
value: body_value,
|
||||||
..
|
..
|
||||||
}) = &body[0]
|
}) = &body_stmt
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -929,7 +868,7 @@ pub(crate) fn use_dict_get_with_default(
|
||||||
targets: orelse_var,
|
targets: orelse_var,
|
||||||
value: orelse_value,
|
value: orelse_value,
|
||||||
..
|
..
|
||||||
}) = &orelse[0]
|
}) = &else_body_stmt
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -941,7 +880,7 @@ pub(crate) fn use_dict_get_with_default(
|
||||||
ops,
|
ops,
|
||||||
comparators: test_dict,
|
comparators: test_dict,
|
||||||
range: _,
|
range: _,
|
||||||
}) = &test
|
}) = test.as_ref()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -949,8 +888,18 @@ pub(crate) fn use_dict_get_with_default(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let (expected_var, expected_value, default_var, default_value) = match ops[..] {
|
let (expected_var, expected_value, default_var, default_value) = match ops[..] {
|
||||||
[CmpOp::In] => (&body_var[0], body_value, &orelse_var[0], orelse_value),
|
[CmpOp::In] => (
|
||||||
[CmpOp::NotIn] => (&orelse_var[0], orelse_value, &body_var[0], body_value),
|
&body_var[0],
|
||||||
|
body_value,
|
||||||
|
&orelse_var[0],
|
||||||
|
orelse_value.as_ref(),
|
||||||
|
),
|
||||||
|
[CmpOp::NotIn] => (
|
||||||
|
&orelse_var[0],
|
||||||
|
orelse_value,
|
||||||
|
&body_var[0],
|
||||||
|
body_value.as_ref(),
|
||||||
|
),
|
||||||
_ => {
|
_ => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -979,37 +928,7 @@ pub(crate) fn use_dict_get_with_default(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's part of a bigger if-elif block:
|
let node = default_value.clone();
|
||||||
// https://github.com/MartinThoma/flake8-simplify/issues/115
|
|
||||||
if let Some(Stmt::If(ast::StmtIf {
|
|
||||||
orelse: parent_orelse,
|
|
||||||
..
|
|
||||||
})) = parent
|
|
||||||
{
|
|
||||||
if parent_orelse.len() == 1 && stmt == &parent_orelse[0] {
|
|
||||||
// TODO(charlie): These two cases have the same AST:
|
|
||||||
//
|
|
||||||
// if True:
|
|
||||||
// pass
|
|
||||||
// elif a:
|
|
||||||
// b = 1
|
|
||||||
// else:
|
|
||||||
// b = 2
|
|
||||||
//
|
|
||||||
// if True:
|
|
||||||
// pass
|
|
||||||
// else:
|
|
||||||
// if a:
|
|
||||||
// b = 1
|
|
||||||
// else:
|
|
||||||
// b = 2
|
|
||||||
//
|
|
||||||
// We want to flag the latter, but not the former. Right now, we flag neither.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = *default_value.clone();
|
|
||||||
let node1 = *test_key.clone();
|
let node1 = *test_key.clone();
|
||||||
let node2 = ast::ExprAttribute {
|
let node2 = ast::ExprAttribute {
|
||||||
value: expected_subscript.clone(),
|
value: expected_subscript.clone(),
|
||||||
|
@ -1033,9 +952,9 @@ pub(crate) fn use_dict_get_with_default(
|
||||||
let contents = checker.generator().stmt(&node5.into());
|
let contents = checker.generator().stmt(&node5.into());
|
||||||
|
|
||||||
// Don't flag if the resulting expression would exceed the maximum line length.
|
// Don't flag if the resulting expression would exceed the maximum line length.
|
||||||
let line_start = checker.locator.line_start(stmt.start());
|
let line_start = checker.locator.line_start(stmt_if.start());
|
||||||
if LineWidth::new(checker.settings.tab_size)
|
if LineWidth::new(checker.settings.tab_size)
|
||||||
.add_str(&checker.locator.contents()[TextRange::new(line_start, stmt.start())])
|
.add_str(&checker.locator.contents()[TextRange::new(line_start, stmt_if.start())])
|
||||||
.add_str(&contents)
|
.add_str(&contents)
|
||||||
> checker.settings.line_length
|
> checker.settings.line_length
|
||||||
{
|
{
|
||||||
|
@ -1046,13 +965,13 @@ pub(crate) fn use_dict_get_with_default(
|
||||||
IfElseBlockInsteadOfDictGet {
|
IfElseBlockInsteadOfDictGet {
|
||||||
contents: contents.clone(),
|
contents: contents.clone(),
|
||||||
},
|
},
|
||||||
stmt.range(),
|
stmt_if.range(),
|
||||||
);
|
);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if !has_comments(stmt, checker.locator, checker.indexer) {
|
if !has_comments(stmt_if, checker.locator, checker.indexer) {
|
||||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||||
contents,
|
contents,
|
||||||
stmt.range(),
|
stmt_if.range(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,13 +127,7 @@ fn is_dunder_method(name: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_exception_check(stmt: &Stmt) -> bool {
|
fn is_exception_check(stmt: &Stmt) -> bool {
|
||||||
let Stmt::If(ast::StmtIf {
|
let Stmt::If(ast::StmtIf { body, .. }) = stmt else {
|
||||||
test: _,
|
|
||||||
body,
|
|
||||||
orelse: _,
|
|
||||||
range: _,
|
|
||||||
}) = stmt
|
|
||||||
else {
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
matches!(body.as_slice(), [Stmt::Raise(_)])
|
matches!(body.as_slice(), [Stmt::Raise(_)])
|
||||||
|
|
|
@ -5,14 +5,13 @@ use libcst_native::{
|
||||||
BooleanOp, BooleanOperation, CompoundStatement, Expression, If, LeftParen,
|
BooleanOp, BooleanOperation, CompoundStatement, Expression, If, LeftParen,
|
||||||
ParenthesizableWhitespace, ParenthesizedNode, RightParen, SimpleWhitespace, Statement, Suite,
|
ParenthesizableWhitespace, ParenthesizedNode, RightParen, SimpleWhitespace, Statement, Suite,
|
||||||
};
|
};
|
||||||
use rustpython_parser::ast::Ranged;
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
use crate::autofix::codemods::CodegenStylist;
|
|
||||||
use ruff_diagnostics::Edit;
|
use ruff_diagnostics::Edit;
|
||||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||||
use ruff_python_ast::whitespace;
|
use ruff_python_ast::whitespace;
|
||||||
use ruff_python_whitespace::PythonWhitespace;
|
|
||||||
|
|
||||||
|
use crate::autofix::codemods::CodegenStylist;
|
||||||
use crate::cst::matchers::{match_function_def, match_if, match_indented_block, match_statement};
|
use crate::cst::matchers::{match_function_def, match_if, match_indented_block, match_statement};
|
||||||
|
|
||||||
fn parenthesize_and_operand(expr: Expression) -> Expression {
|
fn parenthesize_and_operand(expr: Expression) -> Expression {
|
||||||
|
@ -34,21 +33,19 @@ fn parenthesize_and_operand(expr: Expression) -> Expression {
|
||||||
pub(crate) fn fix_nested_if_statements(
|
pub(crate) fn fix_nested_if_statements(
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
stmt: &rustpython_parser::ast::Stmt,
|
range: TextRange,
|
||||||
) -> Result<Edit> {
|
) -> Result<Edit> {
|
||||||
// Infer the indentation of the outer block.
|
// Infer the indentation of the outer block.
|
||||||
let Some(outer_indent) = whitespace::indentation(locator, stmt) else {
|
let Some(outer_indent) = whitespace::indentation(locator, &range) else {
|
||||||
bail!("Unable to fix multiline statement");
|
bail!("Unable to fix multiline statement");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract the module text.
|
// Extract the module text.
|
||||||
let contents = locator.lines(stmt.range());
|
let contents = locator.lines(range);
|
||||||
|
|
||||||
// Handle `elif` blocks differently; detect them upfront.
|
|
||||||
let is_elif = contents.trim_whitespace_start().starts_with("elif");
|
|
||||||
|
|
||||||
// If this is an `elif`, we have to remove the `elif` keyword for now. (We'll
|
// If this is an `elif`, we have to remove the `elif` keyword for now. (We'll
|
||||||
// restore the `el` later on.)
|
// restore the `el` later on.)
|
||||||
|
let is_elif = contents.starts_with("elif");
|
||||||
let module_text = if is_elif {
|
let module_text = if is_elif {
|
||||||
Cow::Owned(contents.replacen("elif", "if", 1))
|
Cow::Owned(contents.replacen("elif", "if", 1))
|
||||||
} else {
|
} else {
|
||||||
|
@ -128,6 +125,6 @@ pub(crate) fn fix_nested_if_statements(
|
||||||
Cow::Borrowed(module_text)
|
Cow::Borrowed(module_text)
|
||||||
};
|
};
|
||||||
|
|
||||||
let range = locator.lines_range(stmt.range());
|
let range = locator.lines_range(range);
|
||||||
Ok(Edit::range_replacement(contents.to_string(), range))
|
Ok(Edit::range_replacement(contents.to_string(), range))
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,7 +237,7 @@ fn return_values_for_else(stmt: &Stmt) -> Option<Loop> {
|
||||||
let Stmt::If(ast::StmtIf {
|
let Stmt::If(ast::StmtIf {
|
||||||
body: nested_body,
|
body: nested_body,
|
||||||
test: nested_test,
|
test: nested_test,
|
||||||
orelse: nested_orelse,
|
elif_else_clauses: nested_elif_else_clauses,
|
||||||
range: _,
|
range: _,
|
||||||
}) = &body[0]
|
}) = &body[0]
|
||||||
else {
|
else {
|
||||||
|
@ -246,7 +246,7 @@ fn return_values_for_else(stmt: &Stmt) -> Option<Loop> {
|
||||||
if nested_body.len() != 1 {
|
if nested_body.len() != 1 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if !nested_orelse.is_empty() {
|
if !nested_elif_else_clauses.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let Stmt::Return(ast::StmtReturn { value, range: _ }) = &nested_body[0] else {
|
let Stmt::Return(ast::StmtReturn { value, range: _ }) = &nested_body[0] else {
|
||||||
|
@ -317,7 +317,7 @@ fn return_values_for_siblings<'a>(stmt: &'a Stmt, sibling: &'a Stmt) -> Option<L
|
||||||
let Stmt::If(ast::StmtIf {
|
let Stmt::If(ast::StmtIf {
|
||||||
body: nested_body,
|
body: nested_body,
|
||||||
test: nested_test,
|
test: nested_test,
|
||||||
orelse: nested_orelse,
|
elif_else_clauses: nested_elif_else_clauses,
|
||||||
range: _,
|
range: _,
|
||||||
}) = &body[0]
|
}) = &body[0]
|
||||||
else {
|
else {
|
||||||
|
@ -326,7 +326,7 @@ fn return_values_for_siblings<'a>(stmt: &'a Stmt, sibling: &'a Stmt) -> Option<L
|
||||||
if nested_body.len() != 1 {
|
if nested_body.len() != 1 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if !nested_orelse.is_empty() {
|
if !nested_elif_else_clauses.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let Stmt::Return(ast::StmtReturn { value, range: _ }) = &nested_body[0] else {
|
let Stmt::Return(ast::StmtReturn { value, range: _ }) = &nested_body[0] else {
|
||||||
|
|
|
@ -48,6 +48,31 @@ SIM102.py:7:1: SIM102 [*] Use a single `if` statement instead of nested `if` sta
|
||||||
12 11 | # SIM102
|
12 11 | # SIM102
|
||||||
13 12 | if a:
|
13 12 | if a:
|
||||||
|
|
||||||
|
SIM102.py:8:5: SIM102 [*] Use a single `if` statement instead of nested `if` statements
|
||||||
|
|
|
||||||
|
6 | # SIM102
|
||||||
|
7 | if a:
|
||||||
|
8 | if b:
|
||||||
|
| _____^
|
||||||
|
9 | | if c:
|
||||||
|
| |_____________^ SIM102
|
||||||
|
10 | d
|
||||||
|
|
|
||||||
|
= help: Combine `if` statements using `and`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
5 5 |
|
||||||
|
6 6 | # SIM102
|
||||||
|
7 7 | if a:
|
||||||
|
8 |- if b:
|
||||||
|
9 |- if c:
|
||||||
|
10 |- d
|
||||||
|
8 |+ if b and c:
|
||||||
|
9 |+ d
|
||||||
|
11 10 |
|
||||||
|
12 11 | # SIM102
|
||||||
|
13 12 | if a:
|
||||||
|
|
||||||
SIM102.py:15:1: SIM102 [*] Use a single `if` statement instead of nested `if` statements
|
SIM102.py:15:1: SIM102 [*] Use a single `if` statement instead of nested `if` statements
|
||||||
|
|
|
|
||||||
13 | if a:
|
13 | if a:
|
||||||
|
@ -255,30 +280,56 @@ SIM102.py:97:1: SIM102 Use a single `if` statement instead of nested `if` statem
|
||||||
|
|
|
|
||||||
= help: Combine `if` statements using `and`
|
= help: Combine `if` statements using `and`
|
||||||
|
|
||||||
SIM102.py:124:5: SIM102 [*] Use a single `if` statement instead of nested `if` statements
|
SIM102.py:106:5: SIM102 [*] Use a single `if` statement instead of nested `if` statements
|
||||||
|
|
|
|
||||||
122 | if a:
|
104 | # Regression test for https://github.com/apache/airflow/blob/145b16caaa43f0c42bffd97344df916c602cddde/airflow/configuration.py#L1161
|
||||||
123 | # SIM 102
|
105 | if a:
|
||||||
124 | if b:
|
106 | if b:
|
||||||
| _____^
|
| _____^
|
||||||
125 | | if c:
|
107 | | if c:
|
||||||
| |_____________^ SIM102
|
| |_____________^ SIM102
|
||||||
126 | print("foo")
|
108 | print("if")
|
||||||
127 | else:
|
109 | elif d:
|
||||||
|
|
|
|
||||||
= help: Combine `if` statements using `and`
|
= help: Combine `if` statements using `and`
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
121 121 | # OK
|
103 103 | # SIM102
|
||||||
122 122 | if a:
|
104 104 | # Regression test for https://github.com/apache/airflow/blob/145b16caaa43f0c42bffd97344df916c602cddde/airflow/configuration.py#L1161
|
||||||
123 123 | # SIM 102
|
105 105 | if a:
|
||||||
124 |- if b:
|
106 |- if b:
|
||||||
125 |- if c:
|
107 |- if c:
|
||||||
126 |- print("foo")
|
108 |- print("if")
|
||||||
124 |+ if b and c:
|
106 |+ if b and c:
|
||||||
125 |+ print("foo")
|
107 |+ print("if")
|
||||||
127 126 | else:
|
109 108 | elif d:
|
||||||
128 127 | print("bar")
|
110 109 | print("elif")
|
||||||
129 128 |
|
111 110 |
|
||||||
|
|
||||||
|
SIM102.py:132:5: SIM102 [*] Use a single `if` statement instead of nested `if` statements
|
||||||
|
|
|
||||||
|
130 | if a:
|
||||||
|
131 | # SIM 102
|
||||||
|
132 | if b:
|
||||||
|
| _____^
|
||||||
|
133 | | if c:
|
||||||
|
| |_____________^ SIM102
|
||||||
|
134 | print("foo")
|
||||||
|
135 | else:
|
||||||
|
|
|
||||||
|
= help: Combine `if` statements using `and`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
129 129 | # OK
|
||||||
|
130 130 | if a:
|
||||||
|
131 131 | # SIM 102
|
||||||
|
132 |- if b:
|
||||||
|
133 |- if c:
|
||||||
|
134 |- print("foo")
|
||||||
|
132 |+ if b and c:
|
||||||
|
133 |+ print("foo")
|
||||||
|
135 134 | else:
|
||||||
|
136 135 | print("bar")
|
||||||
|
137 136 |
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,32 @@ SIM108.py:2:1: SIM108 [*] Use ternary operator `b = c if a else d` instead of `i
|
||||||
7 4 | # OK
|
7 4 | # OK
|
||||||
8 5 | b = c if a else d
|
8 5 | b = c if a else d
|
||||||
|
|
||||||
|
SIM108.py:30:5: SIM108 [*] Use ternary operator `b = 1 if a else 2` instead of `if`-`else`-block
|
||||||
|
|
|
||||||
|
28 | pass
|
||||||
|
29 | else:
|
||||||
|
30 | if a:
|
||||||
|
| _____^
|
||||||
|
31 | | b = 1
|
||||||
|
32 | | else:
|
||||||
|
33 | | b = 2
|
||||||
|
| |_____________^ SIM108
|
||||||
|
|
|
||||||
|
= help: Replace `if`-`else`-block with `b = 1 if a else 2`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
27 27 | if True:
|
||||||
|
28 28 | pass
|
||||||
|
29 29 | else:
|
||||||
|
30 |- if a:
|
||||||
|
31 |- b = 1
|
||||||
|
32 |- else:
|
||||||
|
33 |- b = 2
|
||||||
|
30 |+ b = 1 if a else 2
|
||||||
|
34 31 |
|
||||||
|
35 32 |
|
||||||
|
36 33 | import sys
|
||||||
|
|
||||||
SIM108.py:58:1: SIM108 Use ternary operator `abc = x if x > 0 else -x` instead of `if`-`else`-block
|
SIM108.py:58:1: SIM108 Use ternary operator `abc = x if x > 0 else -x` instead of `if`-`else`-block
|
||||||
|
|
|
|
||||||
57 | # SIM108 (without fix due to comments)
|
57 | # SIM108 (without fix due to comments)
|
||||||
|
|
|
@ -127,12 +127,11 @@ SIM114.py:38:1: SIM114 Combine `if` branches using logical `or` operator
|
||||||
58 | if result.eofs == "O":
|
58 | if result.eofs == "O":
|
||||||
|
|
|
|
||||||
|
|
||||||
SIM114.py:62:6: SIM114 Combine `if` branches using logical `or` operator
|
SIM114.py:62:1: SIM114 Combine `if` branches using logical `or` operator
|
||||||
|
|
|
|
||||||
60 | elif result.eofs == "S":
|
60 | elif result.eofs == "S":
|
||||||
61 | skipped = 1
|
61 | skipped = 1
|
||||||
62 | elif result.eofs == "F":
|
62 | / elif result.eofs == "F":
|
||||||
| ______^
|
|
||||||
63 | | errors = 1
|
63 | | errors = 1
|
||||||
64 | | elif result.eofs == "E":
|
64 | | elif result.eofs == "E":
|
||||||
65 | | errors = 1
|
65 | | errors = 1
|
||||||
|
|
|
@ -105,6 +105,8 @@ SIM116.py:79:1: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||||
85 | | elif func_name == "move":
|
85 | | elif func_name == "move":
|
||||||
86 | | return "MV"
|
86 | | return "MV"
|
||||||
| |_______________^ SIM116
|
| |_______________^ SIM116
|
||||||
|
87 |
|
||||||
|
88 | # OK
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ SIM401.py:36:1: SIM401 [*] Use `vars[idx] = a_dict.get(key, "defaultß9💣2ℝ6
|
||||||
39 | | vars[idx] = "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789"
|
39 | | vars[idx] = "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789"
|
||||||
| |___________________________________________________________________________^ SIM401
|
| |___________________________________________________________________________^ SIM401
|
||||||
40 |
|
40 |
|
||||||
41 | ###
|
41 | # SIM401
|
||||||
|
|
|
|
||||||
= help: Replace with `vars[idx] = a_dict.get(key, "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789")`
|
= help: Replace with `vars[idx] = a_dict.get(key, "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789")`
|
||||||
|
|
||||||
|
@ -128,7 +128,35 @@ SIM401.py:36:1: SIM401 [*] Use `vars[idx] = a_dict.get(key, "defaultß9💣2ℝ6
|
||||||
39 |- vars[idx] = "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789"
|
39 |- vars[idx] = "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789"
|
||||||
36 |+vars[idx] = a_dict.get(key, "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789")
|
36 |+vars[idx] = a_dict.get(key, "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789")
|
||||||
40 37 |
|
40 37 |
|
||||||
41 38 | ###
|
41 38 | # SIM401
|
||||||
42 39 | # Negative cases
|
42 39 | if foo():
|
||||||
|
|
||||||
|
SIM401.py:45:5: SIM401 [*] Use `vars[idx] = a_dict.get(key, "default")` instead of an `if` block
|
||||||
|
|
|
||||||
|
43 | pass
|
||||||
|
44 | else:
|
||||||
|
45 | if key in a_dict:
|
||||||
|
| _____^
|
||||||
|
46 | | vars[idx] = a_dict[key]
|
||||||
|
47 | | else:
|
||||||
|
48 | | vars[idx] = "default"
|
||||||
|
| |_____________________________^ SIM401
|
||||||
|
49 |
|
||||||
|
50 | ###
|
||||||
|
|
|
||||||
|
= help: Replace with `vars[idx] = a_dict.get(key, "default")`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
42 42 | if foo():
|
||||||
|
43 43 | pass
|
||||||
|
44 44 | else:
|
||||||
|
45 |- if key in a_dict:
|
||||||
|
46 |- vars[idx] = a_dict[key]
|
||||||
|
47 |- else:
|
||||||
|
48 |- vars[idx] = "default"
|
||||||
|
45 |+ vars[idx] = a_dict.get(key, "default")
|
||||||
|
49 46 |
|
||||||
|
50 47 | ###
|
||||||
|
51 48 | # Negative cases
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -241,14 +241,18 @@ where
|
||||||
}
|
}
|
||||||
self.finalize(None);
|
self.finalize(None);
|
||||||
}
|
}
|
||||||
Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
Stmt::If(ast::StmtIf {
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
for stmt in body {
|
for stmt in body {
|
||||||
self.visit_stmt(stmt);
|
self.visit_stmt(stmt);
|
||||||
}
|
}
|
||||||
self.finalize(None);
|
self.finalize(None);
|
||||||
|
|
||||||
for stmt in orelse {
|
for clause in elif_else_clauses {
|
||||||
self.visit_stmt(stmt);
|
self.visit_elif_else_clause(clause);
|
||||||
}
|
}
|
||||||
self.finalize(None);
|
self.finalize(None);
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,10 +68,19 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize {
|
||||||
let mut complexity = 0;
|
let mut complexity = 0;
|
||||||
for stmt in stmts {
|
for stmt in stmts {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
Stmt::If(ast::StmtIf {
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
complexity += 1;
|
complexity += 1;
|
||||||
complexity += get_complexity_number(body);
|
complexity += get_complexity_number(body);
|
||||||
complexity += get_complexity_number(orelse);
|
for clause in elif_else_clauses {
|
||||||
|
if clause.test.is_some() {
|
||||||
|
complexity += 1;
|
||||||
|
}
|
||||||
|
complexity += get_complexity_number(&clause.body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::For(ast::StmtFor { body, orelse, .. })
|
Stmt::For(ast::StmtFor { body, orelse, .. })
|
||||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. }) => {
|
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. }) => {
|
||||||
|
|
|
@ -64,9 +64,12 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
|
||||||
// filtered.append(x)
|
// filtered.append(x)
|
||||||
// ```
|
// ```
|
||||||
[Stmt::If(ast::StmtIf {
|
[Stmt::If(ast::StmtIf {
|
||||||
body, orelse, test, ..
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
test,
|
||||||
|
..
|
||||||
})] => {
|
})] => {
|
||||||
if !orelse.is_empty() {
|
if !elif_else_clauses.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let [stmt] = body.as_slice() else {
|
let [stmt] = body.as_slice() else {
|
||||||
|
|
|
@ -224,6 +224,7 @@ fn function(
|
||||||
body: vec![body],
|
body: vec![body],
|
||||||
decorator_list: vec![],
|
decorator_list: vec![],
|
||||||
returns: Some(Box::new(return_type)),
|
returns: Some(Box::new(return_type)),
|
||||||
|
type_params: vec![],
|
||||||
type_comment: None,
|
type_comment: None,
|
||||||
range: TextRange::default(),
|
range: TextRange::default(),
|
||||||
});
|
});
|
||||||
|
@ -236,6 +237,7 @@ fn function(
|
||||||
body: vec![body],
|
body: vec![body],
|
||||||
decorator_list: vec![],
|
decorator_list: vec![],
|
||||||
returns: None,
|
returns: None,
|
||||||
|
type_params: vec![],
|
||||||
type_comment: None,
|
type_comment: None,
|
||||||
range: TextRange::default(),
|
range: TextRange::default(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -107,6 +107,7 @@ mod tests {
|
||||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_22.py"))]
|
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_22.py"))]
|
||||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_23.py"))]
|
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_23.py"))]
|
||||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_24.py"))]
|
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_24.py"))]
|
||||||
|
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_25.py"))]
|
||||||
#[test_case(Rule::UndefinedName, Path::new("F821_0.py"))]
|
#[test_case(Rule::UndefinedName, Path::new("F821_0.py"))]
|
||||||
#[test_case(Rule::UndefinedName, Path::new("F821_1.py"))]
|
#[test_case(Rule::UndefinedName, Path::new("F821_1.py"))]
|
||||||
#[test_case(Rule::UndefinedName, Path::new("F821_2.py"))]
|
#[test_case(Rule::UndefinedName, Path::new("F821_2.py"))]
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use rustpython_parser::ast::{self, Expr, Ranged, Stmt};
|
use rustpython_parser::ast::{self, Expr, Ranged, StmtIf};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::stmt_if::if_elif_branches;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
@ -37,12 +38,16 @@ impl Violation for IfTuple {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// F634
|
/// F634
|
||||||
pub(crate) fn if_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
|
pub(crate) fn if_tuple(checker: &mut Checker, stmt_if: &StmtIf) {
|
||||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &test {
|
for branch in if_elif_branches(stmt_if) {
|
||||||
if !elts.is_empty() {
|
let Expr::Tuple(ast::ExprTuple { elts, .. }) = &branch.test else {
|
||||||
checker
|
continue;
|
||||||
.diagnostics
|
};
|
||||||
.push(Diagnostic::new(IfTuple, stmt.range()));
|
if elts.is_empty() {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(IfTuple, branch.test.range()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,31 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||||
---
|
---
|
||||||
F634.py:1:1: F634 If test is a tuple, which is always `True`
|
F634.py:1:4: F634 If test is a tuple, which is always `True`
|
||||||
|
|
|
|
||||||
1 | / if (1, 2):
|
1 | if (1, 2):
|
||||||
2 | | pass
|
| ^^^^^^ F634
|
||||||
| |________^ F634
|
2 | pass
|
||||||
3 |
|
|
||||||
4 | for _ in range(5):
|
|
||||||
|
|
|
|
||||||
|
|
||||||
F634.py:7:5: F634 If test is a tuple, which is always `True`
|
F634.py:4:4: F634 If test is a tuple, which is always `True`
|
||||||
|
|
|
||||||
|
2 | pass
|
||||||
|
3 |
|
||||||
|
4 | if (3, 4):
|
||||||
|
| ^^^^^^ F634
|
||||||
|
5 | pass
|
||||||
|
6 | elif foo:
|
||||||
|
|
|
||||||
|
|
||||||
|
F634.py:12:10: F634 If test is a tuple, which is always `True`
|
||||||
|
|
|
|
||||||
5 | if True:
|
10 | if True:
|
||||||
6 | pass
|
11 | pass
|
||||||
7 | elif (3, 4):
|
12 | elif (3, 4):
|
||||||
| _____^
|
| ^^^^^^ F634
|
||||||
8 | | pass
|
13 | pass
|
||||||
9 | | elif ():
|
14 | elif ():
|
||||||
10 | | pass
|
|
||||||
| |____________^ F634
|
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||||
|
---
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use rustpython_parser::ast::{Ranged, Stmt};
|
use ruff_text_size::TextRange;
|
||||||
|
use rustpython_parser::ast::{ElifElseClause, Ranged, Stmt};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::source_code::Locator;
|
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for `else` blocks that consist of a single `if` statement.
|
/// Checks for `else` blocks that consist of a single `if` statement.
|
||||||
|
@ -47,15 +47,20 @@ impl Violation for CollapsibleElseIf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PLR5501
|
/// PLR5501
|
||||||
pub(crate) fn collapsible_else_if(orelse: &[Stmt], locator: &Locator) -> Option<Diagnostic> {
|
pub(crate) fn collapsible_else_if(elif_else_clauses: &[ElifElseClause]) -> Option<Diagnostic> {
|
||||||
if orelse.len() == 1 {
|
let Some(ElifElseClause {
|
||||||
let first = &orelse[0];
|
body,
|
||||||
if matches!(first, Stmt::If(_)) {
|
test: None,
|
||||||
// Determine whether this is an `elif`, or an `if` in an `else` block.
|
range,
|
||||||
if locator.slice(first.range()).starts_with("if") {
|
}) = elif_else_clauses.last()
|
||||||
return Some(Diagnostic::new(CollapsibleElseIf, first.range()));
|
else {
|
||||||
}
|
return None;
|
||||||
}
|
};
|
||||||
|
if let [first @ Stmt::If(_)] = body.as_slice() {
|
||||||
|
return Some(Diagnostic::new(
|
||||||
|
CollapsibleElseIf,
|
||||||
|
TextRange::new(range.start(), first.start()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,8 +54,17 @@ fn traverse_body(checker: &mut Checker, body: &[Stmt]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::If(ast::StmtIf { body, orelse, .. })
|
Stmt::If(ast::StmtIf {
|
||||||
| Stmt::Try(ast::StmtTry { body, orelse, .. })
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
traverse_body(checker, body);
|
||||||
|
for clause in elif_else_clauses {
|
||||||
|
traverse_body(checker, &clause.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Stmt::Try(ast::StmtTry { body, orelse, .. })
|
||||||
| Stmt::TryStar(ast::StmtTryStar { body, orelse, .. }) => {
|
| Stmt::TryStar(ast::StmtTryStar { body, orelse, .. }) => {
|
||||||
traverse_body(checker, body);
|
traverse_body(checker, body);
|
||||||
traverse_body(checker, orelse);
|
traverse_body(checker, orelse);
|
||||||
|
|
|
@ -88,74 +88,74 @@ impl Violation for TooManyBranches {
|
||||||
fn num_branches(stmts: &[Stmt]) -> usize {
|
fn num_branches(stmts: &[Stmt]) -> usize {
|
||||||
stmts
|
stmts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|stmt| {
|
.map(|stmt| match stmt {
|
||||||
match stmt {
|
Stmt::If(ast::StmtIf {
|
||||||
Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
body,
|
||||||
1 + num_branches(body)
|
elif_else_clauses,
|
||||||
+ (if let Some(stmt) = orelse.first() {
|
..
|
||||||
// `elif:` and `else: if:` have the same AST representation.
|
}) => {
|
||||||
// Avoid treating `elif:` as two statements.
|
1 + num_branches(body)
|
||||||
usize::from(!stmt.is_if_stmt())
|
+ elif_else_clauses.len()
|
||||||
} else {
|
+ elif_else_clauses
|
||||||
0
|
|
||||||
})
|
|
||||||
+ num_branches(orelse)
|
|
||||||
}
|
|
||||||
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
|
||||||
1 + cases
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|case| num_branches(&case.body))
|
.map(|clause| num_branches(&clause.body))
|
||||||
.sum::<usize>()
|
.sum::<usize>()
|
||||||
}
|
|
||||||
Stmt::For(ast::StmtFor { body, orelse, .. })
|
|
||||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
|
||||||
| Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
|
||||||
1 + num_branches(body)
|
|
||||||
+ (if orelse.is_empty() {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1 + num_branches(orelse)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Stmt::Try(ast::StmtTry {
|
|
||||||
body,
|
|
||||||
handlers,
|
|
||||||
orelse,
|
|
||||||
finalbody,
|
|
||||||
range: _,
|
|
||||||
})
|
|
||||||
| Stmt::TryStar(ast::StmtTryStar {
|
|
||||||
body,
|
|
||||||
handlers,
|
|
||||||
orelse,
|
|
||||||
finalbody,
|
|
||||||
range: _,
|
|
||||||
}) => {
|
|
||||||
1 + num_branches(body)
|
|
||||||
+ (if orelse.is_empty() {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1 + num_branches(orelse)
|
|
||||||
})
|
|
||||||
+ (if finalbody.is_empty() {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1 + num_branches(finalbody)
|
|
||||||
})
|
|
||||||
+ handlers
|
|
||||||
.iter()
|
|
||||||
.map(|handler| {
|
|
||||||
1 + {
|
|
||||||
let ExceptHandler::ExceptHandler(
|
|
||||||
ast::ExceptHandlerExceptHandler { body, .. },
|
|
||||||
) = handler;
|
|
||||||
num_branches(body)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sum::<usize>()
|
|
||||||
}
|
|
||||||
_ => 0,
|
|
||||||
}
|
}
|
||||||
|
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
||||||
|
1 + cases
|
||||||
|
.iter()
|
||||||
|
.map(|case| num_branches(&case.body))
|
||||||
|
.sum::<usize>()
|
||||||
|
}
|
||||||
|
Stmt::For(ast::StmtFor { body, orelse, .. })
|
||||||
|
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
||||||
|
| Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
||||||
|
1 + num_branches(body)
|
||||||
|
+ (if orelse.is_empty() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1 + num_branches(orelse)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Stmt::Try(ast::StmtTry {
|
||||||
|
body,
|
||||||
|
handlers,
|
||||||
|
orelse,
|
||||||
|
finalbody,
|
||||||
|
range: _,
|
||||||
|
})
|
||||||
|
| Stmt::TryStar(ast::StmtTryStar {
|
||||||
|
body,
|
||||||
|
handlers,
|
||||||
|
orelse,
|
||||||
|
finalbody,
|
||||||
|
range: _,
|
||||||
|
}) => {
|
||||||
|
1 + num_branches(body)
|
||||||
|
+ (if orelse.is_empty() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1 + num_branches(orelse)
|
||||||
|
})
|
||||||
|
+ (if finalbody.is_empty() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1 + num_branches(finalbody)
|
||||||
|
})
|
||||||
|
+ handlers
|
||||||
|
.iter()
|
||||||
|
.map(|handler| {
|
||||||
|
1 + {
|
||||||
|
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||||
|
body,
|
||||||
|
..
|
||||||
|
}) = handler;
|
||||||
|
num_branches(body)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum::<usize>()
|
||||||
|
}
|
||||||
|
_ => 0,
|
||||||
})
|
})
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
|
@ -205,8 +205,7 @@ else:
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
"#;
|
"#;
|
||||||
|
test_helper(source, 4)?;
|
||||||
test_helper(source, 3)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,16 +66,16 @@ fn num_statements(stmts: &[Stmt]) -> usize {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
for stmt in stmts {
|
for stmt in stmts {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
Stmt::If(ast::StmtIf {
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
count += 1;
|
count += 1;
|
||||||
count += num_statements(body);
|
count += num_statements(body);
|
||||||
if let Some(stmt) = orelse.first() {
|
for clause in elif_else_clauses {
|
||||||
// `elif:` and `else: if:` have the same AST representation.
|
count += 1;
|
||||||
// Avoid treating `elif:` as two statements.
|
count += num_statements(&clause.body);
|
||||||
if !stmt.is_if_stmt() {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
count += num_statements(orelse);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::For(ast::StmtFor { body, orelse, .. })
|
Stmt::For(ast::StmtFor { body, orelse, .. })
|
||||||
|
@ -207,7 +207,7 @@ def f():
|
||||||
print()
|
print()
|
||||||
"#;
|
"#;
|
||||||
let stmts = Suite::parse(source, "<filename>")?;
|
let stmts = Suite::parse(source, "<filename>")?;
|
||||||
assert_eq!(num_statements(&stmts), 5);
|
assert_eq!(num_statements(&stmts), 6);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,8 +53,15 @@ impl Violation for UselessElseOnLoop {
|
||||||
|
|
||||||
fn loop_exits_early(body: &[Stmt]) -> bool {
|
fn loop_exits_early(body: &[Stmt]) -> bool {
|
||||||
body.iter().any(|stmt| match stmt {
|
body.iter().any(|stmt| match stmt {
|
||||||
Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
Stmt::If(ast::StmtIf {
|
||||||
loop_exits_early(body) || loop_exits_early(orelse)
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
loop_exits_early(body)
|
||||||
|
|| elif_else_clauses
|
||||||
|
.iter()
|
||||||
|
.any(|clause| loop_exits_early(&clause.body))
|
||||||
}
|
}
|
||||||
Stmt::With(ast::StmtWith { body, .. })
|
Stmt::With(ast::StmtWith { body, .. })
|
||||||
| Stmt::AsyncWith(ast::StmtAsyncWith { body, .. }) => loop_exits_early(body),
|
| Stmt::AsyncWith(ast::StmtAsyncWith { body, .. }) => loop_exits_early(body),
|
||||||
|
|
|
@ -1,26 +1,39 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/pylint/mod.rs
|
source: crates/ruff/src/rules/pylint/mod.rs
|
||||||
---
|
---
|
||||||
collapsible_else_if.py:38:9: PLR5501 Use `elif` instead of `else` then `if`, to reduce indentation
|
collapsible_else_if.py:37:5: PLR5501 Use `elif` instead of `else` then `if`, to reduce indentation
|
||||||
|
|
|
|
||||||
|
35 | if 1:
|
||||||
36 | pass
|
36 | pass
|
||||||
37 | else:
|
37 | else:
|
||||||
38 | if 2:
|
| _____^
|
||||||
| _________^
|
38 | | if 2:
|
||||||
39 | | pass
|
| |________^ PLR5501
|
||||||
| |________________^ PLR5501
|
39 | pass
|
||||||
|
|
|
|
||||||
|
|
||||||
collapsible_else_if.py:46:9: PLR5501 Use `elif` instead of `else` then `if`, to reduce indentation
|
collapsible_else_if.py:45:5: PLR5501 Use `elif` instead of `else` then `if`, to reduce indentation
|
||||||
|
|
|
|
||||||
|
43 | if 1:
|
||||||
44 | pass
|
44 | pass
|
||||||
45 | else:
|
45 | else:
|
||||||
46 | if 2:
|
| _____^
|
||||||
| _________^
|
46 | | if 2:
|
||||||
47 | | pass
|
| |________^ PLR5501
|
||||||
48 | | else:
|
47 | pass
|
||||||
49 | | pass
|
48 | else:
|
||||||
| |________________^ PLR5501
|
|
|
||||||
|
|
||||||
|
collapsible_else_if.py:58:5: PLR5501 Use `elif` instead of `else` then `if`, to reduce indentation
|
||||||
|
|
|
||||||
|
56 | elif True:
|
||||||
|
57 | print(2)
|
||||||
|
58 | else:
|
||||||
|
| _____^
|
||||||
|
59 | | if True:
|
||||||
|
| |________^ PLR5501
|
||||||
|
60 | print(3)
|
||||||
|
61 | else:
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -170,6 +170,7 @@ fn create_class_def_stmt(typename: &str, body: Vec<Stmt>, base_class: &Expr) ->
|
||||||
bases: vec![base_class.clone()],
|
bases: vec![base_class.clone()],
|
||||||
keywords: vec![],
|
keywords: vec![],
|
||||||
body,
|
body,
|
||||||
|
type_params: vec![],
|
||||||
decorator_list: vec![],
|
decorator_list: vec![],
|
||||||
range: TextRange::default(),
|
range: TextRange::default(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,6 +127,7 @@ fn create_class_def_stmt(
|
||||||
bases: vec![base_class.clone()],
|
bases: vec![base_class.clone()],
|
||||||
keywords,
|
keywords,
|
||||||
body,
|
body,
|
||||||
|
type_params: vec![],
|
||||||
decorator_list: vec![],
|
decorator_list: vec![],
|
||||||
range: TextRange::default(),
|
range: TextRange::default(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use num_bigint::{BigInt, Sign};
|
use num_bigint::{BigInt, Sign};
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextLen, TextRange};
|
||||||
use rustpython_parser::ast::{self, CmpOp, Constant, Expr, Ranged, Stmt};
|
use rustpython_parser::ast::{self, CmpOp, Constant, ElifElseClause, Expr, Ranged, StmtIf};
|
||||||
use rustpython_parser::{lexer, Mode, Tok};
|
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::source_code::Locator;
|
use ruff_python_ast::stmt_if::{if_elif_branches, BranchKind, IfElifBranch};
|
||||||
use ruff_python_ast::whitespace::indentation;
|
use ruff_python_ast::whitespace::indentation;
|
||||||
|
|
||||||
use crate::autofix::edits::delete_stmt;
|
use crate::autofix::edits::delete_stmt;
|
||||||
|
@ -61,98 +60,6 @@ impl AlwaysAutofixableViolation for OutdatedVersionBlock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The metadata for a version-comparison block.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct BlockMetadata {
|
|
||||||
/// The first `if` or `elif` token in the block, used to signal the start of the
|
|
||||||
/// version-comparison block.
|
|
||||||
leading_token: StartToken,
|
|
||||||
/// The first `elif` or `else` token following the start token, if any, used to signal the end
|
|
||||||
/// of the version-comparison block.
|
|
||||||
trailing_token: Option<EndToken>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The set of tokens that can start a block, i.e., the first token in an `if` statement.
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum StartTok {
|
|
||||||
If,
|
|
||||||
Elif,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StartTok {
|
|
||||||
fn from_tok(tok: &Tok) -> Option<Self> {
|
|
||||||
match tok {
|
|
||||||
Tok::If => Some(Self::If),
|
|
||||||
Tok::Elif => Some(Self::Elif),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct StartToken {
|
|
||||||
tok: StartTok,
|
|
||||||
range: TextRange,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The set of tokens that can end a block, i.e., the first token in the subsequent `elif` or `else`
|
|
||||||
/// branch that follows an `if` or `elif` statement.
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum EndTok {
|
|
||||||
Elif,
|
|
||||||
Else,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EndTok {
|
|
||||||
fn from_tok(tok: &Tok) -> Option<Self> {
|
|
||||||
match tok {
|
|
||||||
Tok::Elif => Some(Self::Elif),
|
|
||||||
Tok::Else => Some(Self::Else),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct EndToken {
|
|
||||||
tok: EndTok,
|
|
||||||
range: TextRange,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn metadata<T>(locator: &Locator, located: &T, body: &[Stmt]) -> Option<BlockMetadata>
|
|
||||||
where
|
|
||||||
T: Ranged,
|
|
||||||
{
|
|
||||||
indentation(locator, located)?;
|
|
||||||
|
|
||||||
let mut iter = lexer::lex_starts_at(
|
|
||||||
locator.slice(located.range()),
|
|
||||||
Mode::Module,
|
|
||||||
located.start(),
|
|
||||||
)
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
// First the leading `if` or `elif` token.
|
|
||||||
let (tok, range) = iter.next()?;
|
|
||||||
let leading_token = StartToken {
|
|
||||||
tok: StartTok::from_tok(&tok)?,
|
|
||||||
range,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Skip any tokens until we reach the end of the `if` body.
|
|
||||||
let body_end = body.last()?.range().end();
|
|
||||||
|
|
||||||
// Find the trailing `elif` or `else` token, if any.
|
|
||||||
let trailing_token = iter
|
|
||||||
.skip_while(|(_, range)| range.start() < body_end)
|
|
||||||
.find_map(|(tok, range)| EndTok::from_tok(&tok).map(|tok| EndToken { tok, range }));
|
|
||||||
|
|
||||||
Some(BlockMetadata {
|
|
||||||
leading_token,
|
|
||||||
trailing_token,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a `BigInt` to a `u32`. If the number is negative, it will return 0.
|
/// Converts a `BigInt` to a `u32`. If the number is negative, it will return 0.
|
||||||
fn bigint_to_u32(number: &BigInt) -> u32 {
|
fn bigint_to_u32(number: &BigInt) -> u32 {
|
||||||
let the_number = number.to_u32_digits();
|
let the_number = number.to_u32_digits();
|
||||||
|
@ -207,96 +114,110 @@ fn compare_version(if_version: &[u32], py_version: PythonVersion, or_equal: bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a [`Stmt::If`], retaining the `else`.
|
/// For fixing, we have 4 cases:
|
||||||
fn fix_py2_block(
|
/// * Just an if: delete as statement (insert pass in parent if required)
|
||||||
checker: &Checker,
|
/// * If with an elif: delete, turn elif into if
|
||||||
stmt: &Stmt,
|
/// * If with an else: delete, dedent else
|
||||||
orelse: &[Stmt],
|
/// * Just an elif: delete, `elif False` can always be removed
|
||||||
block: &BlockMetadata,
|
fn fix_py2_block(checker: &Checker, stmt_if: &StmtIf, branch: &IfElifBranch) -> Option<Fix> {
|
||||||
) -> Option<Fix> {
|
match branch.kind {
|
||||||
let leading_token = &block.leading_token;
|
BranchKind::If => match stmt_if.elif_else_clauses.first() {
|
||||||
let Some(trailing_token) = &block.trailing_token else {
|
// If we have a lone `if`, delete as statement (insert pass in parent if required)
|
||||||
// Delete the entire statement. If this is an `elif`, know it's the only child
|
None => {
|
||||||
// of its parent, so avoid passing in the parent at all. Otherwise,
|
let stmt = checker.semantic().stmt();
|
||||||
// `delete_stmt` will erroneously include a `pass`.
|
let parent = checker.semantic().stmt_parent();
|
||||||
let stmt = checker.semantic().stmt();
|
let edit = delete_stmt(stmt, parent, checker.locator, checker.indexer);
|
||||||
let parent = checker.semantic().stmt_parent();
|
Some(Fix::suggested(edit))
|
||||||
let edit = delete_stmt(
|
|
||||||
stmt,
|
|
||||||
if matches!(block.leading_token.tok, StartTok::If) {
|
|
||||||
parent
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
checker.locator,
|
|
||||||
checker.indexer,
|
|
||||||
);
|
|
||||||
return Some(Fix::suggested(edit));
|
|
||||||
};
|
|
||||||
|
|
||||||
match (&leading_token.tok, &trailing_token.tok) {
|
|
||||||
// If we only have an `if` and an `else`, dedent the `else` block.
|
|
||||||
(StartTok::If, EndTok::Else) => {
|
|
||||||
let start = orelse.first()?;
|
|
||||||
let end = orelse.last()?;
|
|
||||||
if indentation(checker.locator, start).is_none() {
|
|
||||||
// Inline `else` block (e.g., `else: x = 1`).
|
|
||||||
Some(Fix::suggested(Edit::range_replacement(
|
|
||||||
checker
|
|
||||||
.locator
|
|
||||||
.slice(TextRange::new(start.start(), end.end()))
|
|
||||||
.to_string(),
|
|
||||||
stmt.range(),
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
indentation(checker.locator, stmt)
|
|
||||||
.and_then(|indentation| {
|
|
||||||
adjust_indentation(
|
|
||||||
TextRange::new(checker.locator.line_start(start.start()), end.end()),
|
|
||||||
indentation,
|
|
||||||
checker.locator,
|
|
||||||
checker.stylist,
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.map(|contents| {
|
|
||||||
Fix::suggested(Edit::replacement(
|
|
||||||
contents,
|
|
||||||
checker.locator.line_start(stmt.start()),
|
|
||||||
stmt.end(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
// If we have an `if` and an `elif`, turn the `elif` into an `if`
|
||||||
(StartTok::If, EndTok::Elif) => {
|
Some(ElifElseClause {
|
||||||
// If we have an `if` and an `elif`, turn the `elif` into an `if`.
|
test: Some(_),
|
||||||
let start_location = leading_token.range.start();
|
range,
|
||||||
let end_location = trailing_token.range.start() + TextSize::from(2);
|
..
|
||||||
Some(Fix::suggested(Edit::deletion(start_location, end_location)))
|
}) => {
|
||||||
}
|
debug_assert!(
|
||||||
(StartTok::Elif, _) => {
|
&checker.locator.contents()[TextRange::at(range.start(), "elif".text_len())]
|
||||||
// If we have an `elif`, delete up to the `else` or the end of the statement.
|
== "elif"
|
||||||
let start_location = leading_token.range.start();
|
);
|
||||||
let end_location = trailing_token.range.start();
|
let end_location = range.start() + ("elif".text_len() - "if".text_len());
|
||||||
Some(Fix::suggested(Edit::deletion(start_location, end_location)))
|
Some(Fix::suggested(Edit::deletion(
|
||||||
|
stmt_if.start(),
|
||||||
|
end_location,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
// If we only have an `if` and an `else`, dedent the `else` block
|
||||||
|
Some(ElifElseClause {
|
||||||
|
body, test: None, ..
|
||||||
|
}) => {
|
||||||
|
let start = body.first()?;
|
||||||
|
let end = body.last()?;
|
||||||
|
if indentation(checker.locator, start).is_none() {
|
||||||
|
// Inline `else` block (e.g., `else: x = 1`).
|
||||||
|
Some(Fix::suggested(Edit::range_replacement(
|
||||||
|
checker
|
||||||
|
.locator
|
||||||
|
.slice(TextRange::new(start.start(), end.end()))
|
||||||
|
.to_string(),
|
||||||
|
stmt_if.range(),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
indentation(checker.locator, stmt_if)
|
||||||
|
.and_then(|indentation| {
|
||||||
|
adjust_indentation(
|
||||||
|
TextRange::new(
|
||||||
|
checker.locator.line_start(start.start()),
|
||||||
|
end.end(),
|
||||||
|
),
|
||||||
|
indentation,
|
||||||
|
checker.locator,
|
||||||
|
checker.stylist,
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.map(|contents| {
|
||||||
|
Fix::suggested(Edit::replacement(
|
||||||
|
contents,
|
||||||
|
checker.locator.line_start(stmt_if.start()),
|
||||||
|
stmt_if.end(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BranchKind::Elif => {
|
||||||
|
// The range of the `ElifElseClause` ends in the line of the last statement. To avoid
|
||||||
|
// inserting an empty line between the end of `if` branch and the beginning `elif` or
|
||||||
|
// `else` branch after the deleted branch we find the next branch after the current, if
|
||||||
|
// any, and delete to its start.
|
||||||
|
// ```python
|
||||||
|
// if cond:
|
||||||
|
// x = 1
|
||||||
|
// elif sys.version < (3.0):
|
||||||
|
// delete from here ... ^ x = 2
|
||||||
|
// else:
|
||||||
|
// ... to here (exclusive) ^ x = 3
|
||||||
|
// ```
|
||||||
|
let next_start = stmt_if
|
||||||
|
.elif_else_clauses
|
||||||
|
.iter()
|
||||||
|
.map(Ranged::start)
|
||||||
|
.find(|start| *start > branch.range.start());
|
||||||
|
Some(Fix::suggested(Edit::deletion(
|
||||||
|
branch.range.start(),
|
||||||
|
next_start.unwrap_or(branch.range.end()),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a [`Stmt::If`], removing the `else` block.
|
/// Convert a [`Stmt::If`], removing the `else` block.
|
||||||
fn fix_py3_block(
|
fn fix_py3_block(checker: &mut Checker, stmt_if: &StmtIf, branch: &IfElifBranch) -> Option<Fix> {
|
||||||
checker: &mut Checker,
|
match branch.kind {
|
||||||
stmt: &Stmt,
|
BranchKind::If => {
|
||||||
test: &Expr,
|
// If the first statement is an `if`, use the body of this statement, and ignore
|
||||||
body: &[Stmt],
|
|
||||||
block: &BlockMetadata,
|
|
||||||
) -> Option<Fix> {
|
|
||||||
match block.leading_token.tok {
|
|
||||||
StartTok::If => {
|
|
||||||
// If the first statement is an if, use the body of this statement, and ignore
|
|
||||||
// the rest.
|
// the rest.
|
||||||
let start = body.first()?;
|
let start = branch.body.first()?;
|
||||||
let end = body.last()?;
|
let end = branch.body.last()?;
|
||||||
if indentation(checker.locator, start).is_none() {
|
if indentation(checker.locator, start).is_none() {
|
||||||
// Inline `if` block (e.g., `if ...: x = 1`).
|
// Inline `if` block (e.g., `if ...: x = 1`).
|
||||||
Some(Fix::suggested(Edit::range_replacement(
|
Some(Fix::suggested(Edit::range_replacement(
|
||||||
|
@ -304,10 +225,10 @@ fn fix_py3_block(
|
||||||
.locator
|
.locator
|
||||||
.slice(TextRange::new(start.start(), end.end()))
|
.slice(TextRange::new(start.start(), end.end()))
|
||||||
.to_string(),
|
.to_string(),
|
||||||
stmt.range(),
|
stmt_if.range,
|
||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
indentation(checker.locator, stmt)
|
indentation(checker.locator, &stmt_if)
|
||||||
.and_then(|indentation| {
|
.and_then(|indentation| {
|
||||||
adjust_indentation(
|
adjust_indentation(
|
||||||
TextRange::new(checker.locator.line_start(start.start()), end.end()),
|
TextRange::new(checker.locator.line_start(start.start()), end.end()),
|
||||||
|
@ -320,81 +241,76 @@ fn fix_py3_block(
|
||||||
.map(|contents| {
|
.map(|contents| {
|
||||||
Fix::suggested(Edit::replacement(
|
Fix::suggested(Edit::replacement(
|
||||||
contents,
|
contents,
|
||||||
checker.locator.line_start(stmt.start()),
|
checker.locator.line_start(stmt_if.start()),
|
||||||
stmt.end(),
|
stmt_if.end(),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StartTok::Elif => {
|
BranchKind::Elif => {
|
||||||
// Replace the `elif` with an `else, preserve the body of the elif, and remove
|
// Replace the `elif` with an `else`, preserve the body of the elif, and remove
|
||||||
// the rest.
|
// the rest.
|
||||||
let end = body.last()?;
|
let end = branch.body.last()?;
|
||||||
let text = checker.locator.slice(TextRange::new(test.end(), end.end()));
|
let text = checker
|
||||||
|
.locator
|
||||||
|
.slice(TextRange::new(branch.test.end(), end.end()));
|
||||||
Some(Fix::suggested(Edit::range_replacement(
|
Some(Fix::suggested(Edit::range_replacement(
|
||||||
format!("else{text}"),
|
format!("else{text}"),
|
||||||
stmt.range(),
|
TextRange::new(branch.range.start(), stmt_if.end()),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UP036
|
/// UP036
|
||||||
pub(crate) fn outdated_version_block(
|
pub(crate) fn outdated_version_block(checker: &mut Checker, stmt_if: &StmtIf) {
|
||||||
checker: &mut Checker,
|
for branch in if_elif_branches(stmt_if) {
|
||||||
stmt: &Stmt,
|
let Expr::Compare(ast::ExprCompare {
|
||||||
test: &Expr,
|
left,
|
||||||
body: &[Stmt],
|
ops,
|
||||||
orelse: &[Stmt],
|
comparators,
|
||||||
) {
|
range: _,
|
||||||
let Expr::Compare(ast::ExprCompare {
|
}) = &branch.test
|
||||||
left,
|
else {
|
||||||
ops,
|
continue;
|
||||||
comparators,
|
};
|
||||||
range: _,
|
|
||||||
}) = &test
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !checker
|
let ([op], [comparison]) = (ops.as_slice(), comparators.as_slice()) else {
|
||||||
.semantic()
|
continue;
|
||||||
.resolve_call_path(left)
|
};
|
||||||
.map_or(false, |call_path| {
|
|
||||||
matches!(call_path.as_slice(), ["sys", "version_info"])
|
if !checker
|
||||||
})
|
.semantic()
|
||||||
{
|
.resolve_call_path(left)
|
||||||
return;
|
.map_or(false, |call_path| {
|
||||||
}
|
matches!(call_path.as_slice(), ["sys", "version_info"])
|
||||||
|
})
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ops.len() == 1 && comparators.len() == 1 {
|
|
||||||
let comparison = &comparators[0];
|
|
||||||
let op = &ops[0];
|
|
||||||
match comparison {
|
match comparison {
|
||||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||||
let version = extract_version(elts);
|
let version = extract_version(elts);
|
||||||
let target = checker.settings.target_version;
|
let target = checker.settings.target_version;
|
||||||
if op == &CmpOp::Lt || op == &CmpOp::LtE {
|
if op == &CmpOp::Lt || op == &CmpOp::LtE {
|
||||||
if compare_version(&version, target, op == &CmpOp::LtE) {
|
if compare_version(&version, target, op == &CmpOp::LtE) {
|
||||||
let mut diagnostic = Diagnostic::new(OutdatedVersionBlock, stmt.range());
|
let mut diagnostic =
|
||||||
|
Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if let Some(block) = metadata(checker.locator, stmt, body) {
|
if let Some(fix) = fix_py2_block(checker, stmt_if, &branch) {
|
||||||
if let Some(fix) = fix_py2_block(checker, stmt, orelse, &block) {
|
diagnostic.set_fix(fix);
|
||||||
diagnostic.set_fix(fix);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
} else if op == &CmpOp::Gt || op == &CmpOp::GtE {
|
} else if op == &CmpOp::Gt || op == &CmpOp::GtE {
|
||||||
if compare_version(&version, target, op == &CmpOp::GtE) {
|
if compare_version(&version, target, op == &CmpOp::GtE) {
|
||||||
let mut diagnostic = Diagnostic::new(OutdatedVersionBlock, stmt.range());
|
let mut diagnostic =
|
||||||
|
Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if let Some(block) = metadata(checker.locator, stmt, body) {
|
if let Some(fix) = fix_py3_block(checker, stmt_if, &branch) {
|
||||||
if let Some(fix) = fix_py3_block(checker, stmt, test, body, &block)
|
diagnostic.set_fix(fix);
|
||||||
{
|
|
||||||
diagnostic.set_fix(fix);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
@ -407,22 +323,18 @@ pub(crate) fn outdated_version_block(
|
||||||
}) => {
|
}) => {
|
||||||
let version_number = bigint_to_u32(number);
|
let version_number = bigint_to_u32(number);
|
||||||
if version_number == 2 && op == &CmpOp::Eq {
|
if version_number == 2 && op == &CmpOp::Eq {
|
||||||
let mut diagnostic = Diagnostic::new(OutdatedVersionBlock, stmt.range());
|
let mut diagnostic = Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if let Some(block) = metadata(checker.locator, stmt, body) {
|
if let Some(fix) = fix_py2_block(checker, stmt_if, &branch) {
|
||||||
if let Some(fix) = fix_py2_block(checker, stmt, orelse, &block) {
|
diagnostic.set_fix(fix);
|
||||||
diagnostic.set_fix(fix);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
} else if version_number == 3 && op == &CmpOp::Eq {
|
} else if version_number == 3 && op == &CmpOp::Eq {
|
||||||
let mut diagnostic = Diagnostic::new(OutdatedVersionBlock, stmt.range());
|
let mut diagnostic = Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if let Some(block) = metadata(checker.locator, stmt, body) {
|
if let Some(fix) = fix_py3_block(checker, stmt_if, &branch) {
|
||||||
if let Some(fix) = fix_py3_block(checker, stmt, test, body, &block) {
|
diagnostic.set_fix(fix);
|
||||||
diagnostic.set_fix(fix);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
||||||
---
|
---
|
||||||
UP036_0.py:3:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:3:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
1 | import sys
|
1 | import sys
|
||||||
2 |
|
2 |
|
||||||
3 | / if sys.version_info < (3,0):
|
3 | if sys.version_info < (3,0):
|
||||||
4 | | print("py2")
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
5 | | else:
|
4 | print("py2")
|
||||||
6 | | print("py3")
|
5 | else:
|
||||||
| |________________^ UP036
|
|
||||||
7 |
|
|
||||||
8 | if sys.version_info < (3,0):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -27,20 +24,14 @@ UP036_0.py:3:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
8 5 | if sys.version_info < (3,0):
|
8 5 | if sys.version_info < (3,0):
|
||||||
9 6 | if True:
|
9 6 | if True:
|
||||||
|
|
||||||
UP036_0.py:8:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:8:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
6 | print("py3")
|
6 | print("py3")
|
||||||
7 |
|
7 |
|
||||||
8 | / if sys.version_info < (3,0):
|
8 | if sys.version_info < (3,0):
|
||||||
9 | | if True:
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
10 | | print("py2!")
|
9 | if True:
|
||||||
11 | | else:
|
10 | print("py2!")
|
||||||
12 | | print("???")
|
|
||||||
13 | | else:
|
|
||||||
14 | | print("py3")
|
|
||||||
| |________________^ UP036
|
|
||||||
15 |
|
|
||||||
16 | if sys.version_info < (3,0): print("PY2!")
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -60,15 +51,13 @@ UP036_0.py:8:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
16 10 | if sys.version_info < (3,0): print("PY2!")
|
16 10 | if sys.version_info < (3,0): print("PY2!")
|
||||||
17 11 | else: print("PY3!")
|
17 11 | else: print("PY3!")
|
||||||
|
|
||||||
UP036_0.py:16:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:16:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
14 | print("py3")
|
14 | print("py3")
|
||||||
15 |
|
15 |
|
||||||
16 | / if sys.version_info < (3,0): print("PY2!")
|
16 | if sys.version_info < (3,0): print("PY2!")
|
||||||
17 | | else: print("PY3!")
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
| |___________________^ UP036
|
17 | else: print("PY3!")
|
||||||
18 |
|
|
||||||
19 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -83,17 +72,13 @@ UP036_0.py:16:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
19 18 | if True:
|
19 18 | if True:
|
||||||
20 19 | if sys.version_info < (3,0):
|
20 19 | if sys.version_info < (3,0):
|
||||||
|
|
||||||
UP036_0.py:20:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:20:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
19 | if True:
|
19 | if True:
|
||||||
20 | if sys.version_info < (3,0):
|
20 | if sys.version_info < (3,0):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
21 | | print("PY2")
|
21 | print("PY2")
|
||||||
22 | | else:
|
22 | else:
|
||||||
23 | | print("PY3")
|
|
||||||
| |____________________^ UP036
|
|
||||||
24 |
|
|
||||||
25 | if sys.version_info < (3,0): print(1 if True else 3)
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -110,16 +95,14 @@ UP036_0.py:20:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
25 22 | if sys.version_info < (3,0): print(1 if True else 3)
|
25 22 | if sys.version_info < (3,0): print(1 if True else 3)
|
||||||
26 23 | else:
|
26 23 | else:
|
||||||
|
|
||||||
UP036_0.py:25:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:25:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
23 | print("PY3")
|
23 | print("PY3")
|
||||||
24 |
|
24 |
|
||||||
25 | / if sys.version_info < (3,0): print(1 if True else 3)
|
25 | if sys.version_info < (3,0): print(1 if True else 3)
|
||||||
26 | | else:
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
27 | | print("py3")
|
26 | else:
|
||||||
| |________________^ UP036
|
27 | print("py3")
|
||||||
28 |
|
|
||||||
29 | if sys.version_info < (3,0):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -135,20 +118,14 @@ UP036_0.py:25:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
29 27 | if sys.version_info < (3,0):
|
29 27 | if sys.version_info < (3,0):
|
||||||
30 28 | def f():
|
30 28 | def f():
|
||||||
|
|
||||||
UP036_0.py:29:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:29:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
27 | print("py3")
|
27 | print("py3")
|
||||||
28 |
|
28 |
|
||||||
29 | / if sys.version_info < (3,0):
|
29 | if sys.version_info < (3,0):
|
||||||
30 | | def f():
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
31 | | print("py2")
|
30 | def f():
|
||||||
32 | | else:
|
31 | print("py2")
|
||||||
33 | | def f():
|
|
||||||
34 | | print("py3")
|
|
||||||
35 | | print("This the next")
|
|
||||||
| |______________________________^ UP036
|
|
||||||
36 |
|
|
||||||
37 | if sys.version_info > (3,0):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -170,15 +147,14 @@ UP036_0.py:29:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
37 33 | if sys.version_info > (3,0):
|
37 33 | if sys.version_info > (3,0):
|
||||||
38 34 | print("py3")
|
38 34 | print("py3")
|
||||||
|
|
||||||
UP036_0.py:37:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:37:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
35 | print("This the next")
|
35 | print("This the next")
|
||||||
36 |
|
36 |
|
||||||
37 | / if sys.version_info > (3,0):
|
37 | if sys.version_info > (3,0):
|
||||||
38 | | print("py3")
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
39 | | else:
|
38 | print("py3")
|
||||||
40 | | print("py2")
|
39 | else:
|
||||||
| |________________^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -195,16 +171,14 @@ UP036_0.py:37:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
42 39 |
|
42 39 |
|
||||||
43 40 | x = 1
|
43 40 | x = 1
|
||||||
|
|
||||||
UP036_0.py:45:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:45:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
43 | x = 1
|
43 | x = 1
|
||||||
44 |
|
44 |
|
||||||
45 | / if sys.version_info > (3,0):
|
45 | if sys.version_info > (3,0):
|
||||||
46 | | print("py3")
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
47 | | else:
|
46 | print("py3")
|
||||||
48 | | print("py2")
|
47 | else:
|
||||||
| |________________^ UP036
|
|
||||||
49 | # ohai
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -221,15 +195,13 @@ UP036_0.py:45:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
50 47 |
|
50 47 |
|
||||||
51 48 | x = 1
|
51 48 | x = 1
|
||||||
|
|
||||||
UP036_0.py:53:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:53:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
51 | x = 1
|
51 | x = 1
|
||||||
52 |
|
52 |
|
||||||
53 | / if sys.version_info > (3,0): print("py3")
|
53 | if sys.version_info > (3,0): print("py3")
|
||||||
54 | | else: print("py2")
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
| |__________________^ UP036
|
54 | else: print("py2")
|
||||||
55 |
|
|
||||||
56 | if sys.version_info > (3,):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -244,17 +216,14 @@ UP036_0.py:53:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
56 55 | if sys.version_info > (3,):
|
56 55 | if sys.version_info > (3,):
|
||||||
57 56 | print("py3")
|
57 56 | print("py3")
|
||||||
|
|
||||||
UP036_0.py:56:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:56:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
54 | else: print("py2")
|
54 | else: print("py2")
|
||||||
55 |
|
55 |
|
||||||
56 | / if sys.version_info > (3,):
|
56 | if sys.version_info > (3,):
|
||||||
57 | | print("py3")
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
58 | | else:
|
57 | print("py3")
|
||||||
59 | | print("py2")
|
58 | else:
|
||||||
| |________________^ UP036
|
|
||||||
60 |
|
|
||||||
61 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -271,17 +240,13 @@ UP036_0.py:56:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
61 58 | if True:
|
61 58 | if True:
|
||||||
62 59 | if sys.version_info > (3,):
|
62 59 | if sys.version_info > (3,):
|
||||||
|
|
||||||
UP036_0.py:62:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:62:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
61 | if True:
|
61 | if True:
|
||||||
62 | if sys.version_info > (3,):
|
62 | if sys.version_info > (3,):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
63 | | print("py3")
|
63 | print("py3")
|
||||||
64 | | else:
|
64 | else:
|
||||||
65 | | print("py2")
|
|
||||||
| |____________________^ UP036
|
|
||||||
66 |
|
|
||||||
67 | if sys.version_info < (3,):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -298,17 +263,14 @@ UP036_0.py:62:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
67 64 | if sys.version_info < (3,):
|
67 64 | if sys.version_info < (3,):
|
||||||
68 65 | print("py2")
|
68 65 | print("py2")
|
||||||
|
|
||||||
UP036_0.py:67:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:67:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
65 | print("py2")
|
65 | print("py2")
|
||||||
66 |
|
66 |
|
||||||
67 | / if sys.version_info < (3,):
|
67 | if sys.version_info < (3,):
|
||||||
68 | | print("py2")
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
69 | | else:
|
68 | print("py2")
|
||||||
70 | | print("py3")
|
69 | else:
|
||||||
| |________________^ UP036
|
|
||||||
71 |
|
|
||||||
72 | def f():
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -325,18 +287,13 @@ UP036_0.py:67:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
72 69 | def f():
|
72 69 | def f():
|
||||||
73 70 | if sys.version_info < (3,0):
|
73 70 | if sys.version_info < (3,0):
|
||||||
|
|
||||||
UP036_0.py:73:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:73:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
72 | def f():
|
72 | def f():
|
||||||
73 | if sys.version_info < (3,0):
|
73 | if sys.version_info < (3,0):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
74 | | try:
|
74 | try:
|
||||||
75 | | yield
|
75 | yield
|
||||||
76 | | finally:
|
|
||||||
77 | | pass
|
|
||||||
78 | | else:
|
|
||||||
79 | | yield
|
|
||||||
| |_____________^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -356,20 +313,14 @@ UP036_0.py:73:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
81 75 |
|
81 75 |
|
||||||
82 76 | class C:
|
82 76 | class C:
|
||||||
|
|
||||||
UP036_0.py:86:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:86:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
84 | pass
|
84 | pass
|
||||||
85 |
|
85 |
|
||||||
86 | if sys.version_info < (3,0):
|
86 | if sys.version_info < (3,0):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
87 | | def f(py2):
|
87 | def f(py2):
|
||||||
88 | | pass
|
88 | pass
|
||||||
89 | | else:
|
|
||||||
90 | | def f(py3):
|
|
||||||
91 | | pass
|
|
||||||
| |________________^ UP036
|
|
||||||
92 |
|
|
||||||
93 | def h():
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -389,19 +340,15 @@ UP036_0.py:86:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
93 89 | def h():
|
93 89 | def h():
|
||||||
94 90 | pass
|
94 90 | pass
|
||||||
|
|
||||||
UP036_0.py:97:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:97:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
96 | if True:
|
96 | if True:
|
||||||
97 | if sys.version_info < (3,0):
|
97 | if sys.version_info < (3,0):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
98 | | 2
|
98 | 2
|
||||||
99 | | else:
|
99 | else:
|
||||||
100 | | 3
|
|
|
||||||
| |_________^ UP036
|
= help: Remove outdated version block
|
||||||
101 |
|
|
||||||
102 | # comment
|
|
||||||
|
|
|
||||||
= help: Remove outdated version block
|
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
94 94 | pass
|
94 94 | pass
|
||||||
|
@ -416,23 +363,14 @@ UP036_0.py:97:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
102 99 | # comment
|
102 99 | # comment
|
||||||
103 100 |
|
103 100 |
|
||||||
|
|
||||||
UP036_0.py:104:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:104:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
102 | # comment
|
102 | # comment
|
||||||
103 |
|
103 |
|
||||||
104 | / if sys.version_info < (3,0):
|
104 | if sys.version_info < (3,0):
|
||||||
105 | | def f():
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
106 | | print("py2")
|
105 | def f():
|
||||||
107 | | def g():
|
106 | print("py2")
|
||||||
108 | | print("py2")
|
|
||||||
109 | | else:
|
|
||||||
110 | | def f():
|
|
||||||
111 | | print("py3")
|
|
||||||
112 | | def g():
|
|
||||||
113 | | print("py3")
|
|
||||||
| |____________________^ UP036
|
|
||||||
114 |
|
|
||||||
115 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -458,15 +396,13 @@ UP036_0.py:104:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
115 109 | if True:
|
115 109 | if True:
|
||||||
116 110 | if sys.version_info > (3,):
|
116 110 | if sys.version_info > (3,):
|
||||||
|
|
||||||
UP036_0.py:116:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:116:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
115 | if True:
|
115 | if True:
|
||||||
116 | if sys.version_info > (3,):
|
116 | if sys.version_info > (3,):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
117 | | print(3)
|
117 | print(3)
|
||||||
| |________________^ UP036
|
118 | # comment
|
||||||
118 | # comment
|
|
||||||
119 | print(2+3)
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -481,11 +417,11 @@ UP036_0.py:116:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
119 118 | print(2+3)
|
119 118 | print(2+3)
|
||||||
120 119 |
|
120 119 |
|
||||||
|
|
||||||
UP036_0.py:122:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:122:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
121 | if True:
|
121 | if True:
|
||||||
122 | if sys.version_info > (3,): print(3)
|
122 | if sys.version_info > (3,): print(3)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
123 |
|
123 |
|
||||||
124 | if True:
|
124 | if True:
|
||||||
|
|
|
|
||||||
|
@ -501,13 +437,12 @@ UP036_0.py:122:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
124 124 | if True:
|
124 124 | if True:
|
||||||
125 125 | if sys.version_info > (3,):
|
125 125 | if sys.version_info > (3,):
|
||||||
|
|
||||||
UP036_0.py:125:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:125:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
124 | if True:
|
124 | if True:
|
||||||
125 | if sys.version_info > (3,):
|
125 | if sys.version_info > (3,):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
126 | | print(3)
|
126 | print(3)
|
||||||
| |________________^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -522,19 +457,13 @@ UP036_0.py:125:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
128 127 |
|
128 127 |
|
||||||
129 128 | if True:
|
129 128 | if True:
|
||||||
|
|
||||||
UP036_0.py:130:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:130:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
129 | if True:
|
129 | if True:
|
||||||
130 | if sys.version_info <= (3, 0):
|
130 | if sys.version_info <= (3, 0):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
131 | | expected_error = []
|
131 | expected_error = []
|
||||||
132 | | else:
|
132 | else:
|
||||||
133 | | expected_error = [
|
|
||||||
134 | | "<stdin>:1:5: Generator expression must be parenthesized",
|
|
||||||
135 | | "max(1 for i in range(10), key=lambda x: x+1)",
|
|
||||||
136 | | " ^",
|
|
||||||
137 | | ]
|
|
||||||
| |_________^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -556,17 +485,12 @@ UP036_0.py:130:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
139 136 |
|
139 136 |
|
||||||
140 137 | if sys.version_info <= (3, 0):
|
140 137 | if sys.version_info <= (3, 0):
|
||||||
|
|
||||||
UP036_0.py:140:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:140:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
140 | / if sys.version_info <= (3, 0):
|
140 | if sys.version_info <= (3, 0):
|
||||||
141 | | expected_error = []
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
142 | | else:
|
141 | expected_error = []
|
||||||
143 | | expected_error = [
|
142 | else:
|
||||||
144 | | "<stdin>:1:5: Generator expression must be parenthesized",
|
|
||||||
145 | | "max(1 for i in range(10), key=lambda x: x+1)",
|
|
||||||
146 | | " ^",
|
|
||||||
147 | | ]
|
|
||||||
| |_____^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -588,23 +512,12 @@ UP036_0.py:140:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
149 146 |
|
149 146 |
|
||||||
150 147 | if sys.version_info > (3,0):
|
150 147 | if sys.version_info > (3,0):
|
||||||
|
|
||||||
UP036_0.py:150:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:150:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
150 | / if sys.version_info > (3,0):
|
150 | if sys.version_info > (3,0):
|
||||||
151 | | """this
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
152 | | is valid"""
|
151 | """this
|
||||||
153 | |
|
152 | is valid"""
|
||||||
154 | | """the indentation on
|
|
||||||
155 | | this line is significant"""
|
|
||||||
156 | |
|
|
||||||
157 | | "this is" \
|
|
||||||
158 | | "allowed too"
|
|
||||||
159 | |
|
|
||||||
160 | | ("so is"
|
|
||||||
161 | | "this for some reason")
|
|
||||||
| |____________________________^ UP036
|
|
||||||
162 |
|
|
||||||
163 | if sys.version_info > (3, 0): expected_error = \
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -633,15 +546,13 @@ UP036_0.py:150:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
163 162 | if sys.version_info > (3, 0): expected_error = \
|
163 162 | if sys.version_info > (3, 0): expected_error = \
|
||||||
164 163 | []
|
164 163 | []
|
||||||
|
|
||||||
UP036_0.py:163:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:163:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
161 | "this for some reason")
|
161 | "this for some reason")
|
||||||
162 |
|
162 |
|
||||||
163 | / if sys.version_info > (3, 0): expected_error = \
|
163 | if sys.version_info > (3, 0): expected_error = \
|
||||||
164 | | []
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
| |______^ UP036
|
164 | []
|
||||||
165 |
|
|
||||||
166 | if sys.version_info > (3, 0): expected_error = []
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -655,12 +566,12 @@ UP036_0.py:163:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
165 165 |
|
165 165 |
|
||||||
166 166 | if sys.version_info > (3, 0): expected_error = []
|
166 166 | if sys.version_info > (3, 0): expected_error = []
|
||||||
|
|
||||||
UP036_0.py:166:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:166:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
164 | []
|
164 | []
|
||||||
165 |
|
165 |
|
||||||
166 | if sys.version_info > (3, 0): expected_error = []
|
166 | if sys.version_info > (3, 0): expected_error = []
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
167 |
|
167 |
|
||||||
168 | if sys.version_info > (3, 0): \
|
168 | if sys.version_info > (3, 0): \
|
||||||
|
|
|
|
||||||
|
@ -676,15 +587,13 @@ UP036_0.py:166:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
168 168 | if sys.version_info > (3, 0): \
|
168 168 | if sys.version_info > (3, 0): \
|
||||||
169 169 | expected_error = []
|
169 169 | expected_error = []
|
||||||
|
|
||||||
UP036_0.py:168:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:168:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
166 | if sys.version_info > (3, 0): expected_error = []
|
166 | if sys.version_info > (3, 0): expected_error = []
|
||||||
167 |
|
167 |
|
||||||
168 | / if sys.version_info > (3, 0): \
|
168 | if sys.version_info > (3, 0): \
|
||||||
169 | | expected_error = []
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
| |_______________________^ UP036
|
169 | expected_error = []
|
||||||
170 |
|
|
||||||
171 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -699,15 +608,12 @@ UP036_0.py:168:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
171 170 | if True:
|
171 170 | if True:
|
||||||
172 171 | if sys.version_info > (3, 0): expected_error = \
|
172 171 | if sys.version_info > (3, 0): expected_error = \
|
||||||
|
|
||||||
UP036_0.py:172:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:172:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
171 | if True:
|
171 | if True:
|
||||||
172 | if sys.version_info > (3, 0): expected_error = \
|
172 | if sys.version_info > (3, 0): expected_error = \
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
173 | | []
|
173 | []
|
||||||
| |______^ UP036
|
|
||||||
174 |
|
|
||||||
175 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -721,11 +627,11 @@ UP036_0.py:172:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
174 174 |
|
174 174 |
|
||||||
175 175 | if True:
|
175 175 | if True:
|
||||||
|
|
||||||
UP036_0.py:176:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:176:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
175 | if True:
|
175 | if True:
|
||||||
176 | if sys.version_info > (3, 0): expected_error = []
|
176 | if sys.version_info > (3, 0): expected_error = []
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
177 |
|
177 |
|
||||||
178 | if True:
|
178 | if True:
|
||||||
|
|
|
|
||||||
|
@ -741,13 +647,12 @@ UP036_0.py:176:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
178 178 | if True:
|
178 178 | if True:
|
||||||
179 179 | if sys.version_info > (3, 0): \
|
179 179 | if sys.version_info > (3, 0): \
|
||||||
|
|
||||||
UP036_0.py:179:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_0.py:179:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
178 | if True:
|
178 | if True:
|
||||||
179 | if sys.version_info > (3, 0): \
|
179 | if sys.version_info > (3, 0): \
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
180 | | expected_error = []
|
180 | expected_error = []
|
||||||
| |_______________________^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
||||||
---
|
---
|
||||||
UP036_1.py:3:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:3:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
1 | import sys
|
1 | import sys
|
||||||
2 |
|
2 |
|
||||||
3 | / if sys.version_info == 2:
|
3 | if sys.version_info == 2:
|
||||||
4 | | 2
|
| ^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
5 | | else:
|
4 | 2
|
||||||
6 | | 3
|
5 | else:
|
||||||
| |_____^ UP036
|
|
||||||
7 |
|
|
||||||
8 | if sys.version_info < (3,):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -27,17 +24,14 @@ UP036_1.py:3:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
8 5 | if sys.version_info < (3,):
|
8 5 | if sys.version_info < (3,):
|
||||||
9 6 | 2
|
9 6 | 2
|
||||||
|
|
||||||
UP036_1.py:8:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:8:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
6 | 3
|
6 | 3
|
||||||
7 |
|
7 |
|
||||||
8 | / if sys.version_info < (3,):
|
8 | if sys.version_info < (3,):
|
||||||
9 | | 2
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
10 | | else:
|
9 | 2
|
||||||
11 | | 3
|
10 | else:
|
||||||
| |_____^ UP036
|
|
||||||
12 |
|
|
||||||
13 | if sys.version_info < (3,0):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -54,17 +48,14 @@ UP036_1.py:8:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
13 10 | if sys.version_info < (3,0):
|
13 10 | if sys.version_info < (3,0):
|
||||||
14 11 | 2
|
14 11 | 2
|
||||||
|
|
||||||
UP036_1.py:13:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:13:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
11 | 3
|
11 | 3
|
||||||
12 |
|
12 |
|
||||||
13 | / if sys.version_info < (3,0):
|
13 | if sys.version_info < (3,0):
|
||||||
14 | | 2
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
15 | | else:
|
14 | 2
|
||||||
16 | | 3
|
15 | else:
|
||||||
| |_____^ UP036
|
|
||||||
17 |
|
|
||||||
18 | if sys.version_info == 3:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -81,17 +72,14 @@ UP036_1.py:13:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
18 15 | if sys.version_info == 3:
|
18 15 | if sys.version_info == 3:
|
||||||
19 16 | 3
|
19 16 | 3
|
||||||
|
|
||||||
UP036_1.py:18:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:18:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
16 | 3
|
16 | 3
|
||||||
17 |
|
17 |
|
||||||
18 | / if sys.version_info == 3:
|
18 | if sys.version_info == 3:
|
||||||
19 | | 3
|
| ^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
20 | | else:
|
19 | 3
|
||||||
21 | | 2
|
20 | else:
|
||||||
| |_____^ UP036
|
|
||||||
22 |
|
|
||||||
23 | if sys.version_info > (3,):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -108,17 +96,14 @@ UP036_1.py:18:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
23 20 | if sys.version_info > (3,):
|
23 20 | if sys.version_info > (3,):
|
||||||
24 21 | 3
|
24 21 | 3
|
||||||
|
|
||||||
UP036_1.py:23:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:23:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
21 | 2
|
21 | 2
|
||||||
22 |
|
22 |
|
||||||
23 | / if sys.version_info > (3,):
|
23 | if sys.version_info > (3,):
|
||||||
24 | | 3
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
25 | | else:
|
24 | 3
|
||||||
26 | | 2
|
25 | else:
|
||||||
| |_____^ UP036
|
|
||||||
27 |
|
|
||||||
28 | if sys.version_info >= (3,):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -135,17 +120,14 @@ UP036_1.py:23:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
28 25 | if sys.version_info >= (3,):
|
28 25 | if sys.version_info >= (3,):
|
||||||
29 26 | 3
|
29 26 | 3
|
||||||
|
|
||||||
UP036_1.py:28:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:28:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
26 | 2
|
26 | 2
|
||||||
27 |
|
27 |
|
||||||
28 | / if sys.version_info >= (3,):
|
28 | if sys.version_info >= (3,):
|
||||||
29 | | 3
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
30 | | else:
|
29 | 3
|
||||||
31 | | 2
|
30 | else:
|
||||||
| |_____^ UP036
|
|
||||||
32 |
|
|
||||||
33 | from sys import version_info
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -162,17 +144,14 @@ UP036_1.py:28:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
33 30 | from sys import version_info
|
33 30 | from sys import version_info
|
||||||
34 31 |
|
34 31 |
|
||||||
|
|
||||||
UP036_1.py:35:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:35:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
33 | from sys import version_info
|
33 | from sys import version_info
|
||||||
34 |
|
34 |
|
||||||
35 | / if version_info > (3,):
|
35 | if version_info > (3,):
|
||||||
36 | | 3
|
| ^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
37 | | else:
|
36 | 3
|
||||||
38 | | 2
|
37 | else:
|
||||||
| |_____^ UP036
|
|
||||||
39 |
|
|
||||||
40 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -189,17 +168,14 @@ UP036_1.py:35:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
40 37 | if True:
|
40 37 | if True:
|
||||||
41 38 | print(1)
|
41 38 | print(1)
|
||||||
|
|
||||||
UP036_1.py:42:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:42:6: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
40 | if True:
|
40 | if True:
|
||||||
41 | print(1)
|
41 | print(1)
|
||||||
42 | / elif sys.version_info < (3,0):
|
42 | elif sys.version_info < (3,0):
|
||||||
43 | | print(2)
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
44 | | else:
|
43 | print(2)
|
||||||
45 | | print(3)
|
44 | else:
|
||||||
| |____________^ UP036
|
|
||||||
46 |
|
|
||||||
47 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -213,17 +189,14 @@ UP036_1.py:42:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
45 43 | print(3)
|
45 43 | print(3)
|
||||||
46 44 |
|
46 44 |
|
||||||
|
|
||||||
UP036_1.py:49:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:49:6: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
47 | if True:
|
47 | if True:
|
||||||
48 | print(1)
|
48 | print(1)
|
||||||
49 | / elif sys.version_info > (3,):
|
49 | elif sys.version_info > (3,):
|
||||||
50 | | print(3)
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
51 | | else:
|
50 | print(3)
|
||||||
52 | | print(2)
|
51 | else:
|
||||||
| |____________^ UP036
|
|
||||||
53 |
|
|
||||||
54 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -240,15 +213,13 @@ UP036_1.py:49:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
54 52 | if True:
|
54 52 | if True:
|
||||||
55 53 | print(1)
|
55 53 | print(1)
|
||||||
|
|
||||||
UP036_1.py:56:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:56:6: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
54 | if True:
|
54 | if True:
|
||||||
55 | print(1)
|
55 | print(1)
|
||||||
56 | / elif sys.version_info > (3,):
|
56 | elif sys.version_info > (3,):
|
||||||
57 | | print(3)
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
| |____________^ UP036
|
57 | print(3)
|
||||||
58 |
|
|
||||||
59 | def f():
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -262,16 +233,13 @@ UP036_1.py:56:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
58 58 |
|
58 58 |
|
||||||
59 59 | def f():
|
59 59 | def f():
|
||||||
|
|
||||||
UP036_1.py:62:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:62:10: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
60 | if True:
|
60 | if True:
|
||||||
61 | print(1)
|
61 | print(1)
|
||||||
62 | elif sys.version_info > (3,):
|
62 | elif sys.version_info > (3,):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
63 | | print(3)
|
63 | print(3)
|
||||||
| |________________^ UP036
|
|
||||||
64 |
|
|
||||||
65 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -285,17 +253,14 @@ UP036_1.py:62:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
64 64 |
|
64 64 |
|
||||||
65 65 | if True:
|
65 65 | if True:
|
||||||
|
|
||||||
UP036_1.py:67:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:67:6: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
65 | if True:
|
65 | if True:
|
||||||
66 | print(1)
|
66 | print(1)
|
||||||
67 | / elif sys.version_info < (3,0):
|
67 | elif sys.version_info < (3,0):
|
||||||
68 | | print(2)
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
69 | | else:
|
68 | print(2)
|
||||||
70 | | print(3)
|
69 | else:
|
||||||
| |____________^ UP036
|
|
||||||
71 |
|
|
||||||
72 | def f():
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -309,14 +274,13 @@ UP036_1.py:67:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
70 68 | print(3)
|
70 68 | print(3)
|
||||||
71 69 |
|
71 69 |
|
||||||
|
|
||||||
UP036_1.py:75:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_1.py:75:10: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
73 | if True:
|
73 | if True:
|
||||||
74 | print(1)
|
74 | print(1)
|
||||||
75 | elif sys.version_info > (3,):
|
75 | elif sys.version_info > (3,):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
76 | | print(3)
|
76 | print(3)
|
||||||
| |________________^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
||||||
---
|
---
|
||||||
UP036_2.py:4:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:4:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
2 | from sys import version_info
|
2 | from sys import version_info
|
||||||
3 |
|
3 |
|
||||||
4 | / if sys.version_info > (3, 5):
|
4 | if sys.version_info > (3, 5):
|
||||||
5 | | 3+6
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
6 | | else:
|
5 | 3+6
|
||||||
7 | | 3-5
|
6 | else:
|
||||||
| |_______^ UP036
|
|
||||||
8 |
|
|
||||||
9 | if version_info > (3, 5):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -28,17 +25,14 @@ UP036_2.py:4:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
9 6 | if version_info > (3, 5):
|
9 6 | if version_info > (3, 5):
|
||||||
10 7 | 3+6
|
10 7 | 3+6
|
||||||
|
|
||||||
UP036_2.py:9:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:9:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
7 | 3-5
|
7 | 3-5
|
||||||
8 |
|
8 |
|
||||||
9 | / if version_info > (3, 5):
|
9 | if version_info > (3, 5):
|
||||||
10 | | 3+6
|
| ^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
11 | | else:
|
10 | 3+6
|
||||||
12 | | 3-5
|
11 | else:
|
||||||
| |_______^ UP036
|
|
||||||
13 |
|
|
||||||
14 | if sys.version_info >= (3,6):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -55,17 +49,14 @@ UP036_2.py:9:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
14 11 | if sys.version_info >= (3,6):
|
14 11 | if sys.version_info >= (3,6):
|
||||||
15 12 | 3+6
|
15 12 | 3+6
|
||||||
|
|
||||||
UP036_2.py:14:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:14:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
12 | 3-5
|
12 | 3-5
|
||||||
13 |
|
13 |
|
||||||
14 | / if sys.version_info >= (3,6):
|
14 | if sys.version_info >= (3,6):
|
||||||
15 | | 3+6
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
16 | | else:
|
15 | 3+6
|
||||||
17 | | 3-5
|
16 | else:
|
||||||
| |_______^ UP036
|
|
||||||
18 |
|
|
||||||
19 | if version_info >= (3,6):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -82,17 +73,14 @@ UP036_2.py:14:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
19 16 | if version_info >= (3,6):
|
19 16 | if version_info >= (3,6):
|
||||||
20 17 | 3+6
|
20 17 | 3+6
|
||||||
|
|
||||||
UP036_2.py:19:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:19:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
17 | 3-5
|
17 | 3-5
|
||||||
18 |
|
18 |
|
||||||
19 | / if version_info >= (3,6):
|
19 | if version_info >= (3,6):
|
||||||
20 | | 3+6
|
| ^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
21 | | else:
|
20 | 3+6
|
||||||
22 | | 3-5
|
21 | else:
|
||||||
| |_______^ UP036
|
|
||||||
23 |
|
|
||||||
24 | if sys.version_info < (3,6):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -109,17 +97,14 @@ UP036_2.py:19:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
24 21 | if sys.version_info < (3,6):
|
24 21 | if sys.version_info < (3,6):
|
||||||
25 22 | 3-5
|
25 22 | 3-5
|
||||||
|
|
||||||
UP036_2.py:24:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:24:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
22 | 3-5
|
22 | 3-5
|
||||||
23 |
|
23 |
|
||||||
24 | / if sys.version_info < (3,6):
|
24 | if sys.version_info < (3,6):
|
||||||
25 | | 3-5
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
26 | | else:
|
25 | 3-5
|
||||||
27 | | 3+6
|
26 | else:
|
||||||
| |_______^ UP036
|
|
||||||
28 |
|
|
||||||
29 | if sys.version_info <= (3,5):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -136,17 +121,14 @@ UP036_2.py:24:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
29 26 | if sys.version_info <= (3,5):
|
29 26 | if sys.version_info <= (3,5):
|
||||||
30 27 | 3-5
|
30 27 | 3-5
|
||||||
|
|
||||||
UP036_2.py:29:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:29:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
27 | 3+6
|
27 | 3+6
|
||||||
28 |
|
28 |
|
||||||
29 | / if sys.version_info <= (3,5):
|
29 | if sys.version_info <= (3,5):
|
||||||
30 | | 3-5
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
31 | | else:
|
30 | 3-5
|
||||||
32 | | 3+6
|
31 | else:
|
||||||
| |_______^ UP036
|
|
||||||
33 |
|
|
||||||
34 | if sys.version_info <= (3, 5):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -163,17 +145,14 @@ UP036_2.py:29:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
34 31 | if sys.version_info <= (3, 5):
|
34 31 | if sys.version_info <= (3, 5):
|
||||||
35 32 | 3-5
|
35 32 | 3-5
|
||||||
|
|
||||||
UP036_2.py:34:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:34:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
32 | 3+6
|
32 | 3+6
|
||||||
33 |
|
33 |
|
||||||
34 | / if sys.version_info <= (3, 5):
|
34 | if sys.version_info <= (3, 5):
|
||||||
35 | | 3-5
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
36 | | else:
|
35 | 3-5
|
||||||
37 | | 3+6
|
36 | else:
|
||||||
| |_______^ UP036
|
|
||||||
38 |
|
|
||||||
39 | if sys.version_info >= (3, 5):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -190,15 +169,13 @@ UP036_2.py:34:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
39 36 | if sys.version_info >= (3, 5):
|
39 36 | if sys.version_info >= (3, 5):
|
||||||
40 37 | pass
|
40 37 | pass
|
||||||
|
|
||||||
UP036_2.py:39:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:39:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
37 | 3+6
|
37 | 3+6
|
||||||
38 |
|
38 |
|
||||||
39 | / if sys.version_info >= (3, 5):
|
39 | if sys.version_info >= (3, 5):
|
||||||
40 | | pass
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
| |________^ UP036
|
40 | pass
|
||||||
41 |
|
|
||||||
42 | if sys.version_info < (3,0):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -213,15 +190,13 @@ UP036_2.py:39:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
42 41 | if sys.version_info < (3,0):
|
42 41 | if sys.version_info < (3,0):
|
||||||
43 42 | pass
|
43 42 | pass
|
||||||
|
|
||||||
UP036_2.py:42:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:42:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
40 | pass
|
40 | pass
|
||||||
41 |
|
41 |
|
||||||
42 | / if sys.version_info < (3,0):
|
42 | if sys.version_info < (3,0):
|
||||||
43 | | pass
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
| |________^ UP036
|
43 | pass
|
||||||
44 |
|
|
||||||
45 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -235,15 +210,12 @@ UP036_2.py:42:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
45 43 | if True:
|
45 43 | if True:
|
||||||
46 44 | if sys.version_info < (3,0):
|
46 44 | if sys.version_info < (3,0):
|
||||||
|
|
||||||
UP036_2.py:46:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:46:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
45 | if True:
|
45 | if True:
|
||||||
46 | if sys.version_info < (3,0):
|
46 | if sys.version_info < (3,0):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
47 | | pass
|
47 | pass
|
||||||
| |____________^ UP036
|
|
||||||
48 |
|
|
||||||
49 | if sys.version_info < (3,0):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -258,17 +230,14 @@ UP036_2.py:46:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
49 48 | if sys.version_info < (3,0):
|
49 48 | if sys.version_info < (3,0):
|
||||||
50 49 | pass
|
50 49 | pass
|
||||||
|
|
||||||
UP036_2.py:49:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:49:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
47 | pass
|
47 | pass
|
||||||
48 |
|
48 |
|
||||||
49 | / if sys.version_info < (3,0):
|
49 | if sys.version_info < (3,0):
|
||||||
50 | | pass
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
51 | | elif False:
|
50 | pass
|
||||||
52 | | pass
|
51 | elif False:
|
||||||
| |________^ UP036
|
|
||||||
53 |
|
|
||||||
54 | if sys.version_info > (3,):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -284,17 +253,14 @@ UP036_2.py:49:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
53 51 |
|
53 51 |
|
||||||
54 52 | if sys.version_info > (3,):
|
54 52 | if sys.version_info > (3,):
|
||||||
|
|
||||||
UP036_2.py:54:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_2.py:54:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
52 | pass
|
52 | pass
|
||||||
53 |
|
53 |
|
||||||
54 | / if sys.version_info > (3,):
|
54 | if sys.version_info > (3,):
|
||||||
55 | | pass
|
| ^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
56 | | elif False:
|
55 | pass
|
||||||
57 | | pass
|
56 | elif False:
|
||||||
| |________^ UP036
|
|
||||||
58 |
|
|
||||||
59 | if sys.version_info[0] > "2":
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,16 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
||||||
---
|
---
|
||||||
UP036_3.py:3:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_3.py:3:15: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
1 | import sys
|
1 | import sys
|
||||||
2 |
|
2 |
|
||||||
3 | / if sys.version_info < (3,0):
|
3 | if sys.version_info < (3,0):
|
||||||
4 | | print("py2")
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
5 | | for item in range(10):
|
4 | print("py2")
|
||||||
6 | | print(f"PY2-{item}")
|
5 | for item in range(10):
|
||||||
7 | | else :
|
|
|
||||||
8 | | print("py3")
|
= help: Remove outdated version block
|
||||||
9 | | for item in range(10):
|
|
||||||
10 | | print(f"PY3-{item}")
|
|
||||||
| |____________________________^ UP036
|
|
||||||
11 |
|
|
||||||
12 | if False:
|
|
||||||
|
|
|
||||||
= help: Remove outdated version block
|
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
1 1 | import sys
|
1 1 | import sys
|
||||||
|
@ -37,19 +30,13 @@ UP036_3.py:3:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
12 7 | if False:
|
12 7 | if False:
|
||||||
13 8 | if sys.version_info < (3,0):
|
13 8 | if sys.version_info < (3,0):
|
||||||
|
|
||||||
UP036_3.py:13:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_3.py:13:19: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
12 | if False:
|
12 | if False:
|
||||||
13 | if sys.version_info < (3,0):
|
13 | if sys.version_info < (3,0):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
14 | | print("py2")
|
14 | print("py2")
|
||||||
15 | | for item in range(10):
|
15 | for item in range(10):
|
||||||
16 | | print(f"PY2-{item}")
|
|
||||||
17 | | else :
|
|
||||||
18 | | print("py3")
|
|
||||||
19 | | for item in range(10):
|
|
||||||
20 | | print(f"PY3-{item}")
|
|
||||||
| |________________________________^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -72,11 +59,11 @@ UP036_3.py:13:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
22 17 |
|
22 17 |
|
||||||
23 18 | if sys.version_info < (3,0): print("PY2!")
|
23 18 | if sys.version_info < (3,0): print("PY2!")
|
||||||
|
|
||||||
UP036_3.py:23:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_3.py:23:15: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
23 | / if sys.version_info < (3,0): print("PY2!")
|
23 | if sys.version_info < (3,0): print("PY2!")
|
||||||
24 | | else : print("PY3!")
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
| |__________________________________________________^ UP036
|
24 | else : print("PY3!")
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
||||||
---
|
---
|
||||||
UP036_4.py:4:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_4.py:4:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
3 | if True:
|
3 | if True:
|
||||||
4 | if sys.version_info < (3, 3):
|
4 | if sys.version_info < (3, 3):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
5 | | cmd = [sys.executable, "-m", "test.regrtest"]
|
5 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
| |_____________________________________________________^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -22,87 +21,76 @@ UP036_4.py:4:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
7 6 |
|
7 6 |
|
||||||
8 7 | if True:
|
8 7 | if True:
|
||||||
|
|
||||||
UP036_4.py:11:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_4.py:11:10: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
9 | if foo:
|
9 | if foo:
|
||||||
10 | pass
|
10 | print()
|
||||||
11 | elif sys.version_info < (3, 3):
|
11 | elif sys.version_info < (3, 3):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
12 | | cmd = [sys.executable, "-m", "test.regrtest"]
|
12 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
| |_____________________________________________________^ UP036
|
|
||||||
13 |
|
|
||||||
14 | if True:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
8 8 | if True:
|
8 8 | if True:
|
||||||
9 9 | if foo:
|
9 9 | if foo:
|
||||||
10 10 | pass
|
10 10 | print()
|
||||||
11 |- elif sys.version_info < (3, 3):
|
11 |- elif sys.version_info < (3, 3):
|
||||||
12 |- cmd = [sys.executable, "-m", "test.regrtest"]
|
12 |- cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
13 11 |
|
11 |+
|
||||||
14 12 | if True:
|
13 12 |
|
||||||
15 13 | if foo:
|
14 13 | if True:
|
||||||
|
15 14 | if foo:
|
||||||
|
|
||||||
UP036_4.py:17:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_4.py:17:10: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
15 | if foo:
|
15 | if foo:
|
||||||
16 | pass
|
16 | print()
|
||||||
17 | elif sys.version_info < (3, 3):
|
17 | elif sys.version_info < (3, 3):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
18 | | cmd = [sys.executable, "-m", "test.regrtest"]
|
18 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
19 | | elif foo:
|
19 | elif foo:
|
||||||
20 | | cmd = [sys.executable, "-m", "test", "-j0"]
|
|
||||||
| |___________________________________________________^ UP036
|
|
||||||
21 |
|
|
||||||
22 | if foo:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
14 14 | if True:
|
14 14 | if True:
|
||||||
15 15 | if foo:
|
15 15 | if foo:
|
||||||
16 16 | pass
|
16 16 | print()
|
||||||
17 |- elif sys.version_info < (3, 3):
|
17 |- elif sys.version_info < (3, 3):
|
||||||
18 |- cmd = [sys.executable, "-m", "test.regrtest"]
|
18 |- cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
19 17 | elif foo:
|
19 17 | elif foo:
|
||||||
20 18 | cmd = [sys.executable, "-m", "test", "-j0"]
|
20 18 | cmd = [sys.executable, "-m", "test", "-j0"]
|
||||||
21 19 |
|
21 19 |
|
||||||
|
|
||||||
UP036_4.py:24:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_4.py:24:10: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
22 | if foo:
|
22 | if foo:
|
||||||
23 | pass
|
23 | print()
|
||||||
24 | elif sys.version_info < (3, 3):
|
24 | elif sys.version_info < (3, 3):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
25 | | cmd = [sys.executable, "-m", "test.regrtest"]
|
25 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
| |_____________________________________________________^ UP036
|
|
||||||
26 |
|
|
||||||
27 | if sys.version_info < (3, 3):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
21 21 |
|
21 21 |
|
||||||
22 22 | if foo:
|
22 22 | if foo:
|
||||||
23 23 | pass
|
23 23 | print()
|
||||||
24 |- elif sys.version_info < (3, 3):
|
24 |- elif sys.version_info < (3, 3):
|
||||||
25 |- cmd = [sys.executable, "-m", "test.regrtest"]
|
25 |- cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
26 24 |
|
24 |+
|
||||||
27 25 | if sys.version_info < (3, 3):
|
26 25 |
|
||||||
28 26 | cmd = [sys.executable, "-m", "test.regrtest"]
|
27 26 | if sys.version_info < (3, 3):
|
||||||
|
28 27 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
|
|
||||||
UP036_4.py:27:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_4.py:27:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
25 | cmd = [sys.executable, "-m", "test.regrtest"]
|
25 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
26 |
|
26 |
|
||||||
27 | if sys.version_info < (3, 3):
|
27 | if sys.version_info < (3, 3):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
28 | | cmd = [sys.executable, "-m", "test.regrtest"]
|
28 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
| |_____________________________________________________^ UP036
|
|
||||||
29 |
|
|
||||||
30 | if foo:
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -114,45 +102,37 @@ UP036_4.py:27:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
28 |- cmd = [sys.executable, "-m", "test.regrtest"]
|
28 |- cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
29 27 |
|
29 27 |
|
||||||
30 28 | if foo:
|
30 28 | if foo:
|
||||||
31 29 | pass
|
31 29 | print()
|
||||||
|
|
||||||
UP036_4.py:32:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_4.py:32:10: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
30 | if foo:
|
30 | if foo:
|
||||||
31 | pass
|
31 | print()
|
||||||
32 | elif sys.version_info < (3, 3):
|
32 | elif sys.version_info < (3, 3):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
33 | | cmd = [sys.executable, "-m", "test.regrtest"]
|
33 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
34 | | else:
|
34 | else:
|
||||||
35 | | cmd = [sys.executable, "-m", "test", "-j0"]
|
|
||||||
| |___________________________________________________^ UP036
|
|
||||||
36 |
|
|
||||||
37 | if sys.version_info < (3, 3):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
29 29 |
|
29 29 |
|
||||||
30 30 | if foo:
|
30 30 | if foo:
|
||||||
31 31 | pass
|
31 31 | print()
|
||||||
32 |- elif sys.version_info < (3, 3):
|
32 |- elif sys.version_info < (3, 3):
|
||||||
33 |- cmd = [sys.executable, "-m", "test.regrtest"]
|
33 |- cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
34 32 | else:
|
34 32 | else:
|
||||||
35 33 | cmd = [sys.executable, "-m", "test", "-j0"]
|
35 33 | cmd = [sys.executable, "-m", "test", "-j0"]
|
||||||
36 34 |
|
36 34 |
|
||||||
|
|
||||||
UP036_4.py:37:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_4.py:37:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
35 | cmd = [sys.executable, "-m", "test", "-j0"]
|
35 | cmd = [sys.executable, "-m", "test", "-j0"]
|
||||||
36 |
|
36 |
|
||||||
37 | if sys.version_info < (3, 3):
|
37 | if sys.version_info < (3, 3):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
38 | | cmd = [sys.executable, "-m", "test.regrtest"]
|
38 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
39 | | else:
|
39 | else:
|
||||||
40 | | cmd = [sys.executable, "-m", "test", "-j0"]
|
|
||||||
| |___________________________________________________^ UP036
|
|
||||||
41 |
|
|
||||||
42 | if sys.version_info < (3, 3):
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
@ -169,16 +149,14 @@ UP036_4.py:37:5: UP036 [*] Version block is outdated for minimum Python version
|
||||||
42 39 | if sys.version_info < (3, 3):
|
42 39 | if sys.version_info < (3, 3):
|
||||||
43 40 | cmd = [sys.executable, "-m", "test.regrtest"]
|
43 40 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
|
|
||||||
UP036_4.py:42:5: UP036 [*] Version block is outdated for minimum Python version
|
UP036_4.py:42:8: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
40 | cmd = [sys.executable, "-m", "test", "-j0"]
|
40 | cmd = [sys.executable, "-m", "test", "-j0"]
|
||||||
41 |
|
41 |
|
||||||
42 | if sys.version_info < (3, 3):
|
42 | if sys.version_info < (3, 3):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
43 | | cmd = [sys.executable, "-m", "test.regrtest"]
|
43 | cmd = [sys.executable, "-m", "test.regrtest"]
|
||||||
44 | | elif foo:
|
44 | elif foo:
|
||||||
45 | | cmd = [sys.executable, "-m", "test", "-j0"]
|
|
||||||
| |___________________________________________________^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,16 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
source: crates/ruff/src/rules/pyupgrade/mod.rs
|
||||||
---
|
---
|
||||||
UP036_5.py:3:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_5.py:3:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
1 | import sys
|
1 | import sys
|
||||||
2 |
|
2 |
|
||||||
3 | / if sys.version_info < (3, 8):
|
3 | if sys.version_info < (3, 8):
|
||||||
4 | |
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
5 | | def a():
|
4 |
|
||||||
6 | | if b:
|
5 | def a():
|
||||||
7 | | print(1)
|
|
|
||||||
8 | | elif c:
|
= help: Remove outdated version block
|
||||||
9 | | print(2)
|
|
||||||
10 | | return None
|
|
||||||
11 | |
|
|
||||||
12 | | else:
|
|
||||||
13 | | pass
|
|
||||||
| |________^ UP036
|
|
||||||
|
|
|
||||||
= help: Remove outdated version block
|
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
1 1 | import sys
|
1 1 | import sys
|
||||||
|
@ -39,24 +31,13 @@ UP036_5.py:3:1: UP036 [*] Version block is outdated for minimum Python version
|
||||||
15 5 |
|
15 5 |
|
||||||
16 6 | import sys
|
16 6 | import sys
|
||||||
|
|
||||||
UP036_5.py:18:1: UP036 [*] Version block is outdated for minimum Python version
|
UP036_5.py:18:4: UP036 [*] Version block is outdated for minimum Python version
|
||||||
|
|
|
|
||||||
16 | import sys
|
16 | import sys
|
||||||
17 |
|
17 |
|
||||||
18 | / if sys.version_info < (3, 8):
|
18 | if sys.version_info < (3, 8):
|
||||||
19 | | pass
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||||
20 | |
|
19 | pass
|
||||||
21 | | else:
|
|
||||||
22 | |
|
|
||||||
23 | | def a():
|
|
||||||
24 | | if b:
|
|
||||||
25 | | print(1)
|
|
||||||
26 | | elif c:
|
|
||||||
27 | | print(2)
|
|
||||||
28 | | else:
|
|
||||||
29 | | print(3)
|
|
||||||
30 | | return None
|
|
||||||
| |___________________^ UP036
|
|
||||||
|
|
|
|
||||||
= help: Remove outdated version block
|
= help: Remove outdated version block
|
||||||
|
|
||||||
|
|
|
@ -286,9 +286,11 @@ flowchart TD
|
||||||
start(("Start"))
|
start(("Start"))
|
||||||
return(("End"))
|
return(("End"))
|
||||||
block0["return 1\n"]
|
block0["return 1\n"]
|
||||||
block1["return 2\n"]
|
block1["return 0\n"]
|
||||||
block2["return 0\n"]
|
block2["return 2\n"]
|
||||||
block3["elif False:
|
block3["if True:
|
||||||
|
return 1
|
||||||
|
elif False:
|
||||||
return 2
|
return 2
|
||||||
else:
|
else:
|
||||||
return 0\n"]
|
return 0\n"]
|
||||||
|
@ -302,8 +304,8 @@ flowchart TD
|
||||||
start --> block4
|
start --> block4
|
||||||
block4 -- "True" --> block0
|
block4 -- "True" --> block0
|
||||||
block4 -- "else" --> block3
|
block4 -- "else" --> block3
|
||||||
block3 -- "False" --> block1
|
block3 -- "False" --> block2
|
||||||
block3 -- "else" --> block2
|
block3 -- "else" --> block1
|
||||||
block2 --> return
|
block2 --> return
|
||||||
block1 --> return
|
block1 --> return
|
||||||
block0 --> return
|
block0 --> return
|
||||||
|
@ -327,9 +329,11 @@ flowchart TD
|
||||||
start(("Start"))
|
start(("Start"))
|
||||||
return(("End"))
|
return(("End"))
|
||||||
block0["return 1\n"]
|
block0["return 1\n"]
|
||||||
block1["return 2\n"]
|
block1["return 0\n"]
|
||||||
block2["return 0\n"]
|
block2["return 2\n"]
|
||||||
block3["elif True:
|
block3["if False:
|
||||||
|
return 1
|
||||||
|
elif True:
|
||||||
return 2
|
return 2
|
||||||
else:
|
else:
|
||||||
return 0\n"]
|
return 0\n"]
|
||||||
|
@ -343,8 +347,8 @@ flowchart TD
|
||||||
start --> block4
|
start --> block4
|
||||||
block4 -- "False" --> block0
|
block4 -- "False" --> block0
|
||||||
block4 -- "else" --> block3
|
block4 -- "else" --> block3
|
||||||
block3 -- "True" --> block1
|
block3 -- "True" --> block2
|
||||||
block3 -- "else" --> block2
|
block3 -- "else" --> block1
|
||||||
block2 --> return
|
block2 --> return
|
||||||
block1 --> return
|
block1 --> return
|
||||||
block0 --> return
|
block0 --> return
|
||||||
|
@ -377,9 +381,11 @@ flowchart TD
|
||||||
block0["return 6\n"]
|
block0["return 6\n"]
|
||||||
block1["return 3\n"]
|
block1["return 3\n"]
|
||||||
block2["return 0\n"]
|
block2["return 0\n"]
|
||||||
block3["return 1\n"]
|
block3["return 2\n"]
|
||||||
block4["return 2\n"]
|
block4["return 1\n"]
|
||||||
block5["elif True:
|
block5["if False:
|
||||||
|
return 0
|
||||||
|
elif True:
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
return 2\n"]
|
return 2\n"]
|
||||||
|
@ -389,9 +395,17 @@ flowchart TD
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
return 2\n"]
|
return 2\n"]
|
||||||
block7["return 4\n"]
|
block7["return 5\n"]
|
||||||
block8["return 5\n"]
|
block8["return 4\n"]
|
||||||
block9["elif True:
|
block9["if True:
|
||||||
|
if False:
|
||||||
|
return 0
|
||||||
|
elif True:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 2
|
||||||
|
return 3
|
||||||
|
elif True:
|
||||||
return 4
|
return 4
|
||||||
else:
|
else:
|
||||||
return 5\n"]
|
return 5\n"]
|
||||||
|
@ -411,14 +425,14 @@ flowchart TD
|
||||||
start --> block10
|
start --> block10
|
||||||
block10 -- "True" --> block6
|
block10 -- "True" --> block6
|
||||||
block10 -- "else" --> block9
|
block10 -- "else" --> block9
|
||||||
block9 -- "True" --> block7
|
block9 -- "True" --> block8
|
||||||
block9 -- "else" --> block8
|
block9 -- "else" --> block7
|
||||||
block8 --> return
|
block8 --> return
|
||||||
block7 --> return
|
block7 --> return
|
||||||
block6 -- "False" --> block2
|
block6 -- "False" --> block2
|
||||||
block6 -- "else" --> block5
|
block6 -- "else" --> block5
|
||||||
block5 -- "True" --> block3
|
block5 -- "True" --> block4
|
||||||
block5 -- "else" --> block4
|
block5 -- "else" --> block3
|
||||||
block4 --> return
|
block4 --> return
|
||||||
block3 --> return
|
block3 --> return
|
||||||
block2 --> return
|
block2 --> return
|
||||||
|
@ -445,7 +459,9 @@ flowchart TD
|
||||||
block0["return #quot;reached#quot;\n"]
|
block0["return #quot;reached#quot;\n"]
|
||||||
block1["return #quot;unreached#quot;\n"]
|
block1["return #quot;unreached#quot;\n"]
|
||||||
block2["return #quot;also unreached#quot;\n"]
|
block2["return #quot;also unreached#quot;\n"]
|
||||||
block3["elif False:
|
block3["if False:
|
||||||
|
return #quot;unreached#quot;
|
||||||
|
elif False:
|
||||||
return #quot;also unreached#quot;\n"]
|
return #quot;also unreached#quot;\n"]
|
||||||
block4["if False:
|
block4["if False:
|
||||||
return #quot;unreached#quot;
|
return #quot;unreached#quot;
|
||||||
|
@ -490,14 +506,16 @@ flowchart TD
|
||||||
return(("End"))
|
return(("End"))
|
||||||
block0["return buffer.data\n"]
|
block0["return buffer.data\n"]
|
||||||
block1["return base64.b64decode(data)\n"]
|
block1["return base64.b64decode(data)\n"]
|
||||||
block2["buffer = data\n"]
|
block2["buffer = self._buffers[id]\n"]
|
||||||
block3["buffer = self._buffers[id]\n"]
|
block3["self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"]
|
||||||
block4["self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"]
|
block4["id = data[#quot;id#quot;]\nif id in self._buffers:
|
||||||
block5["id = data[#quot;id#quot;]\nif id in self._buffers:
|
|
||||||
buffer = self._buffers[id]
|
buffer = self._buffers[id]
|
||||||
else:
|
else:
|
||||||
self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"]
|
self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"]
|
||||||
block6["elif isinstance(data, Buffer):
|
block5["buffer = data\n"]
|
||||||
|
block6["if isinstance(data, str):
|
||||||
|
return base64.b64decode(data)
|
||||||
|
elif isinstance(data, Buffer):
|
||||||
buffer = data
|
buffer = data
|
||||||
else:
|
else:
|
||||||
id = data[#quot;id#quot;]
|
id = data[#quot;id#quot;]
|
||||||
|
@ -521,11 +539,11 @@ flowchart TD
|
||||||
start --> block7
|
start --> block7
|
||||||
block7 -- "isinstance(data, str)" --> block1
|
block7 -- "isinstance(data, str)" --> block1
|
||||||
block7 -- "else" --> block6
|
block7 -- "else" --> block6
|
||||||
block6 -- "isinstance(data, Buffer)" --> block2
|
block6 -- "isinstance(data, Buffer)" --> block5
|
||||||
block6 -- "else" --> block5
|
block6 -- "else" --> block4
|
||||||
block5 -- "id in self._buffers" --> block3
|
block5 --> block0
|
||||||
block5 -- "else" --> block4
|
block4 -- "id in self._buffers" --> block2
|
||||||
block4 --> block0
|
block4 -- "else" --> block3
|
||||||
block3 --> block0
|
block3 --> block0
|
||||||
block2 --> block0
|
block2 --> block0
|
||||||
block1 --> return
|
block1 --> return
|
||||||
|
|
|
@ -484,17 +484,44 @@ impl<'stmt> BasicBlocksBuilder<'stmt> {
|
||||||
self.unconditional_next_block(after)
|
self.unconditional_next_block(after)
|
||||||
}
|
}
|
||||||
// Statements that (can) divert the control flow.
|
// Statements that (can) divert the control flow.
|
||||||
Stmt::If(stmt) => {
|
Stmt::If(stmt_if) => {
|
||||||
let next_after_block =
|
let after_consequent_block =
|
||||||
self.maybe_next_block_index(after, || needs_next_block(&stmt.body));
|
self.maybe_next_block_index(after, || needs_next_block(&stmt_if.body));
|
||||||
let orelse_after_block =
|
let after_alternate_block = self.maybe_next_block_index(after, || {
|
||||||
self.maybe_next_block_index(after, || needs_next_block(&stmt.orelse));
|
stmt_if
|
||||||
let next = self.append_blocks_if_not_empty(&stmt.body, next_after_block);
|
.elif_else_clauses
|
||||||
let orelse = self.append_blocks_if_not_empty(&stmt.orelse, orelse_after_block);
|
.last()
|
||||||
|
.map_or(true, |clause| needs_next_block(&clause.body))
|
||||||
|
});
|
||||||
|
|
||||||
|
let consequent =
|
||||||
|
self.append_blocks_if_not_empty(&stmt_if.body, after_consequent_block);
|
||||||
|
|
||||||
|
// Block ID of the next elif or else clause.
|
||||||
|
let mut next_branch = after_alternate_block;
|
||||||
|
|
||||||
|
for clause in stmt_if.elif_else_clauses.iter().rev() {
|
||||||
|
let consequent =
|
||||||
|
self.append_blocks_if_not_empty(&clause.body, after_consequent_block);
|
||||||
|
|
||||||
|
next_branch = if let Some(test) = &clause.test {
|
||||||
|
let next = NextBlock::If {
|
||||||
|
condition: Condition::Test(test),
|
||||||
|
next: consequent,
|
||||||
|
orelse: next_branch,
|
||||||
|
};
|
||||||
|
let stmts = std::slice::from_ref(stmt);
|
||||||
|
let block = BasicBlock { stmts, next };
|
||||||
|
self.blocks.push(block)
|
||||||
|
} else {
|
||||||
|
consequent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
NextBlock::If {
|
NextBlock::If {
|
||||||
condition: Condition::Test(&stmt.test),
|
condition: Condition::Test(&stmt_if.test),
|
||||||
next,
|
next: consequent,
|
||||||
orelse,
|
orelse: next_branch,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::While(StmtWhile {
|
Stmt::While(StmtWhile {
|
||||||
|
@ -648,6 +675,7 @@ impl<'stmt> BasicBlocksBuilder<'stmt> {
|
||||||
}
|
}
|
||||||
// The tough branches are done, here is an easy one.
|
// The tough branches are done, here is an easy one.
|
||||||
Stmt::Return(_) => NextBlock::Terminate,
|
Stmt::Return(_) => NextBlock::Terminate,
|
||||||
|
Stmt::TypeAlias(_) => todo!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Include any statements in the block that don't divert the control flow.
|
// Include any statements in the block that don't divert the control flow.
|
||||||
|
@ -867,7 +895,7 @@ fn needs_next_block(stmts: &[Stmt]) -> bool {
|
||||||
|
|
||||||
match last {
|
match last {
|
||||||
Stmt::Return(_) | Stmt::Raise(_) => false,
|
Stmt::Return(_) | Stmt::Raise(_) => false,
|
||||||
Stmt::If(stmt) => needs_next_block(&stmt.body) || needs_next_block(&stmt.orelse),
|
Stmt::If(stmt) => needs_next_block(&stmt.body) || stmt.elif_else_clauses.last().map_or(true, |clause| needs_next_block(&clause.body)),
|
||||||
Stmt::FunctionDef(_)
|
Stmt::FunctionDef(_)
|
||||||
| Stmt::AsyncFunctionDef(_)
|
| Stmt::AsyncFunctionDef(_)
|
||||||
| Stmt::Import(_)
|
| Stmt::Import(_)
|
||||||
|
@ -893,6 +921,7 @@ fn needs_next_block(stmts: &[Stmt]) -> bool {
|
||||||
| Stmt::Try(_)
|
| Stmt::Try(_)
|
||||||
| Stmt::TryStar(_)
|
| Stmt::TryStar(_)
|
||||||
| Stmt::Assert(_) => true,
|
| Stmt::Assert(_) => true,
|
||||||
|
Stmt::TypeAlias(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -927,6 +956,7 @@ fn is_control_flow_stmt(stmt: &Stmt) -> bool {
|
||||||
| Stmt::Assert(_)
|
| Stmt::Assert(_)
|
||||||
| Stmt::Break(_)
|
| Stmt::Break(_)
|
||||||
| Stmt::Continue(_) => true,
|
| Stmt::Continue(_) => true,
|
||||||
|
Stmt::TypeAlias(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1063,6 +1093,11 @@ mod tests {
|
||||||
"first block should always terminate"
|
"first block should always terminate"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let got_mermaid = MermaidGraph {
|
||||||
|
graph: &got,
|
||||||
|
source: &source,
|
||||||
|
};
|
||||||
|
|
||||||
// All block index should be valid.
|
// All block index should be valid.
|
||||||
let valid = BlockIndex::from_usize(got.blocks.len());
|
let valid = BlockIndex::from_usize(got.blocks.len());
|
||||||
for block in &got.blocks {
|
for block in &got.blocks {
|
||||||
|
@ -1076,11 +1111,6 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let got_mermaid = MermaidGraph {
|
|
||||||
graph: &got,
|
|
||||||
source: &source,
|
|
||||||
};
|
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"## Function {i}\n### Source\n```python\n{}\n```\n\n### Control Flow Graph\n```mermaid\n{}```\n",
|
"## Function {i}\n### Source\n```python\n{}\n```\n\n### Control Flow Graph\n```mermaid\n{}```\n",
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use rustpython_parser::ast::{self, Expr, Ranged, Stmt};
|
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
|
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
|
||||||
|
use rustpython_parser::ast::{self, Expr, Ranged, Stmt, StmtIf};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
@ -164,34 +163,18 @@ fn check_body(checker: &mut Checker, body: &[Stmt]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search the orelse of an if-condition for raises.
|
|
||||||
fn check_orelse(checker: &mut Checker, body: &[Stmt]) {
|
|
||||||
for item in body {
|
|
||||||
if has_control_flow(item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
match item {
|
|
||||||
Stmt::If(ast::StmtIf { test, .. }) => {
|
|
||||||
if !check_type_check_test(checker, test) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Stmt::Raise(ast::StmtRaise { exc: Some(exc), .. }) => {
|
|
||||||
check_raise(checker, exc, item);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TRY004
|
/// TRY004
|
||||||
pub(crate) fn type_check_without_type_error(
|
pub(crate) fn type_check_without_type_error(
|
||||||
checker: &mut Checker,
|
checker: &mut Checker,
|
||||||
body: &[Stmt],
|
stmt_if: &StmtIf,
|
||||||
test: &Expr,
|
|
||||||
orelse: &[Stmt],
|
|
||||||
parent: Option<&Stmt>,
|
parent: Option<&Stmt>,
|
||||||
) {
|
) {
|
||||||
|
let StmtIf {
|
||||||
|
body,
|
||||||
|
test,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
} = stmt_if;
|
||||||
if let Some(Stmt::If(ast::StmtIf { test, .. })) = parent {
|
if let Some(Stmt::If(ast::StmtIf { test, .. })) = parent {
|
||||||
if !check_type_check_test(checker, test) {
|
if !check_type_check_test(checker, test) {
|
||||||
return;
|
return;
|
||||||
|
@ -199,8 +182,20 @@ pub(crate) fn type_check_without_type_error(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only consider the body when the `if` condition is all type-related
|
// Only consider the body when the `if` condition is all type-related
|
||||||
if check_type_check_test(checker, test) {
|
if !check_type_check_test(checker, test) {
|
||||||
check_body(checker, body);
|
return;
|
||||||
check_orelse(checker, orelse);
|
}
|
||||||
|
check_body(checker, body);
|
||||||
|
|
||||||
|
for clause in elif_else_clauses {
|
||||||
|
if let Some(test) = &clause.test {
|
||||||
|
// If there are any `elif`, they must all also be type-related
|
||||||
|
if !check_type_check_test(checker, test) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The `elif` or `else` body raises the wrong exception
|
||||||
|
check_body(checker, &clause.body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -252,32 +252,48 @@ TRY004.py:230:9: TRY004 Prefer `TypeError` exception for invalid type
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^ TRY004
|
| ^^^^^^^^^^^^^^^^^^^^^^ TRY004
|
||||||
|
|
|
|
||||||
|
|
||||||
TRY004.py:267:9: TRY004 Prefer `TypeError` exception for invalid type
|
TRY004.py:239:9: TRY004 Prefer `TypeError` exception for invalid type
|
||||||
|
|
|
|
||||||
265 | def check_body(some_args):
|
237 | pass
|
||||||
266 | if isinstance(some_args, int):
|
238 | else:
|
||||||
267 | raise ValueError("...") # should be typeerror
|
239 | raise Exception("...") # should be typeerror
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^ TRY004
|
||||||
|
|
|
||||||
|
|
||||||
|
TRY004.py:276:9: TRY004 Prefer `TypeError` exception for invalid type
|
||||||
|
|
|
||||||
|
274 | def check_body(some_args):
|
||||||
|
275 | if isinstance(some_args, int):
|
||||||
|
276 | raise ValueError("...") # should be typeerror
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^ TRY004
|
| ^^^^^^^^^^^^^^^^^^^^^^^ TRY004
|
||||||
|
|
|
|
||||||
|
|
||||||
TRY004.py:277:9: TRY004 Prefer `TypeError` exception for invalid type
|
TRY004.py:286:9: TRY004 Prefer `TypeError` exception for invalid type
|
||||||
|
|
|
|
||||||
275 | def multiple_elifs(some_args):
|
284 | def multiple_elifs(some_args):
|
||||||
276 | if not isinstance(some_args, int):
|
285 | if not isinstance(some_args, int):
|
||||||
277 | raise ValueError("...") # should be typerror
|
286 | raise ValueError("...") # should be typerror
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^ TRY004
|
| ^^^^^^^^^^^^^^^^^^^^^^^ TRY004
|
||||||
278 | elif some_args < 3:
|
287 | elif some_args < 3:
|
||||||
279 | raise ValueError("...") # this is ok
|
288 | raise ValueError("...") # this is ok
|
||||||
|
|
|
|
||||||
|
|
||||||
TRY004.py:288:9: TRY004 Prefer `TypeError` exception for invalid type
|
TRY004.py:297:9: TRY004 Prefer `TypeError` exception for invalid type
|
||||||
|
|
|
|
||||||
286 | def multiple_ifs(some_args):
|
295 | def multiple_ifs(some_args):
|
||||||
287 | if not isinstance(some_args, int):
|
296 | if not isinstance(some_args, int):
|
||||||
288 | raise ValueError("...") # should be typerror
|
297 | raise ValueError("...") # should be typerror
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^ TRY004
|
| ^^^^^^^^^^^^^^^^^^^^^^^ TRY004
|
||||||
289 | else:
|
298 | else:
|
||||||
290 | if some_args < 3:
|
299 | if some_args < 3:
|
||||||
|
|
|
||||||
|
|
||||||
|
TRY004.py:316:9: TRY004 Prefer `TypeError` exception for invalid type
|
||||||
|
|
|
||||||
|
314 | return "CronExpression"
|
||||||
|
315 | else:
|
||||||
|
316 | raise Exception(f"Unknown object type: {obj.__class__.__name__}")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRY004
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -466,6 +466,26 @@ impl<'a> From<&'a ast::ExceptHandler> for ComparableExceptHandler<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ComparableElifElseClause<'a> {
|
||||||
|
test: Option<ComparableExpr<'a>>,
|
||||||
|
body: Vec<ComparableStmt<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a ast::ElifElseClause> for ComparableElifElseClause<'a> {
|
||||||
|
fn from(elif_else_clause: &'a ast::ElifElseClause) -> Self {
|
||||||
|
let ast::ElifElseClause {
|
||||||
|
range: _,
|
||||||
|
test,
|
||||||
|
body,
|
||||||
|
} = elif_else_clause;
|
||||||
|
Self {
|
||||||
|
test: test.as_ref().map(Into::into),
|
||||||
|
body: body.iter().map(Into::into).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct ExprBoolOp<'a> {
|
pub struct ExprBoolOp<'a> {
|
||||||
op: ComparableBoolOp,
|
op: ComparableBoolOp,
|
||||||
|
@ -999,7 +1019,7 @@ pub struct StmtWhile<'a> {
|
||||||
pub struct StmtIf<'a> {
|
pub struct StmtIf<'a> {
|
||||||
test: ComparableExpr<'a>,
|
test: ComparableExpr<'a>,
|
||||||
body: Vec<ComparableStmt<'a>>,
|
body: Vec<ComparableStmt<'a>>,
|
||||||
orelse: Vec<ComparableStmt<'a>>,
|
elif_else_clauses: Vec<ComparableElifElseClause<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
@ -1118,7 +1138,8 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
|
||||||
decorator_list,
|
decorator_list,
|
||||||
returns,
|
returns,
|
||||||
type_comment,
|
type_comment,
|
||||||
range: _range,
|
range: _,
|
||||||
|
type_params: _,
|
||||||
}) => Self::FunctionDef(StmtFunctionDef {
|
}) => Self::FunctionDef(StmtFunctionDef {
|
||||||
name: name.as_str(),
|
name: name.as_str(),
|
||||||
args: args.into(),
|
args: args.into(),
|
||||||
|
@ -1134,7 +1155,8 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
|
||||||
decorator_list,
|
decorator_list,
|
||||||
returns,
|
returns,
|
||||||
type_comment,
|
type_comment,
|
||||||
range: _range,
|
range: _,
|
||||||
|
type_params: _,
|
||||||
}) => Self::AsyncFunctionDef(StmtAsyncFunctionDef {
|
}) => Self::AsyncFunctionDef(StmtAsyncFunctionDef {
|
||||||
name: name.as_str(),
|
name: name.as_str(),
|
||||||
args: args.into(),
|
args: args.into(),
|
||||||
|
@ -1149,7 +1171,8 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
|
||||||
keywords,
|
keywords,
|
||||||
body,
|
body,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
range: _range,
|
range: _,
|
||||||
|
type_params: _,
|
||||||
}) => Self::ClassDef(StmtClassDef {
|
}) => Self::ClassDef(StmtClassDef {
|
||||||
name: name.as_str(),
|
name: name.as_str(),
|
||||||
bases: bases.iter().map(Into::into).collect(),
|
bases: bases.iter().map(Into::into).collect(),
|
||||||
|
@ -1242,12 +1265,12 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
|
||||||
ast::Stmt::If(ast::StmtIf {
|
ast::Stmt::If(ast::StmtIf {
|
||||||
test,
|
test,
|
||||||
body,
|
body,
|
||||||
orelse,
|
elif_else_clauses,
|
||||||
range: _range,
|
range: _range,
|
||||||
}) => Self::If(StmtIf {
|
}) => Self::If(StmtIf {
|
||||||
test: test.into(),
|
test: test.into(),
|
||||||
body: body.iter().map(Into::into).collect(),
|
body: body.iter().map(Into::into).collect(),
|
||||||
orelse: orelse.iter().map(Into::into).collect(),
|
elif_else_clauses: elif_else_clauses.iter().map(Into::into).collect(),
|
||||||
}),
|
}),
|
||||||
ast::Stmt::With(ast::StmtWith {
|
ast::Stmt::With(ast::StmtWith {
|
||||||
items,
|
items,
|
||||||
|
@ -1354,6 +1377,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
|
||||||
ast::Stmt::Pass(_) => Self::Pass,
|
ast::Stmt::Pass(_) => Self::Pass,
|
||||||
ast::Stmt::Break(_) => Self::Break,
|
ast::Stmt::Break(_) => Self::Break,
|
||||||
ast::Stmt::Continue(_) => Self::Continue,
|
ast::Stmt::Continue(_) => Self::Continue,
|
||||||
|
ast::Stmt::TypeAlias(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -437,9 +437,19 @@ where
|
||||||
Stmt::If(ast::StmtIf {
|
Stmt::If(ast::StmtIf {
|
||||||
test,
|
test,
|
||||||
body,
|
body,
|
||||||
orelse,
|
elif_else_clauses,
|
||||||
range: _range,
|
range: _range,
|
||||||
}) => any_over_expr(test, func) || any_over_body(body, func) || any_over_body(orelse, func),
|
}) => {
|
||||||
|
any_over_expr(test, func)
|
||||||
|
|| any_over_body(body, func)
|
||||||
|
|| elif_else_clauses.iter().any(|clause| {
|
||||||
|
clause
|
||||||
|
.test
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |test| any_over_expr(test, func))
|
||||||
|
|| any_over_body(&clause.body, func)
|
||||||
|
})
|
||||||
|
}
|
||||||
Stmt::With(ast::StmtWith { items, body, .. })
|
Stmt::With(ast::StmtWith { items, body, .. })
|
||||||
| Stmt::AsyncWith(ast::StmtAsyncWith { items, body, .. }) => {
|
| Stmt::AsyncWith(ast::StmtAsyncWith { items, body, .. }) => {
|
||||||
items.iter().any(|with_item| {
|
items.iter().any(|with_item| {
|
||||||
|
@ -529,6 +539,7 @@ where
|
||||||
range: _range,
|
range: _range,
|
||||||
}) => any_over_expr(value, func),
|
}) => any_over_expr(value, func),
|
||||||
Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) => false,
|
Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) => false,
|
||||||
|
Stmt::TypeAlias(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -944,9 +955,15 @@ where
|
||||||
| Stmt::AsyncFunctionDef(_)
|
| Stmt::AsyncFunctionDef(_)
|
||||||
| Stmt::Try(_)
|
| Stmt::Try(_)
|
||||||
| Stmt::TryStar(_) => {}
|
| Stmt::TryStar(_) => {}
|
||||||
Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
Stmt::If(ast::StmtIf {
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
walk_body(self, body);
|
walk_body(self, body);
|
||||||
walk_body(self, orelse);
|
for clause in elif_else_clauses {
|
||||||
|
self.visit_elif_else_clause(clause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::While(ast::StmtWhile { body, .. })
|
Stmt::While(ast::StmtWhile { body, .. })
|
||||||
| Stmt::With(ast::StmtWith { body, .. })
|
| Stmt::With(ast::StmtWith { body, .. })
|
||||||
|
@ -1063,25 +1080,6 @@ pub fn first_colon_range(range: TextRange, locator: &Locator) -> Option<TextRang
|
||||||
range
|
range
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the `Range` of the first `Elif` or `Else` token in an `If` statement.
|
|
||||||
pub fn elif_else_range(stmt: &ast::StmtIf, locator: &Locator) -> Option<TextRange> {
|
|
||||||
let ast::StmtIf { body, orelse, .. } = stmt;
|
|
||||||
|
|
||||||
let start = body.last().expect("Expected body to be non-empty").end();
|
|
||||||
|
|
||||||
let end = match &orelse[..] {
|
|
||||||
[Stmt::If(ast::StmtIf { test, .. })] => test.start(),
|
|
||||||
[stmt, ..] => stmt.start(),
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let contents = &locator.contents()[TextRange::new(start, end)];
|
|
||||||
lexer::lex_starts_at(contents, Mode::Module, start)
|
|
||||||
.flatten()
|
|
||||||
.find(|(kind, _)| matches!(kind, Tok::Elif | Tok::Else))
|
|
||||||
.map(|(_, range)| range)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given an offset at the end of a line (including newlines), return the offset of the
|
/// Given an offset at the end of a line (including newlines), return the offset of the
|
||||||
/// continuation at the end of that line.
|
/// continuation at the end of that line.
|
||||||
fn find_continuation(offset: TextSize, locator: &Locator, indexer: &Indexer) -> Option<TextSize> {
|
fn find_continuation(offset: TextSize, locator: &Locator, indexer: &Indexer) -> Option<TextSize> {
|
||||||
|
@ -1568,13 +1566,13 @@ mod tests {
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||||
use rustpython_ast::{CmpOp, Expr, Ranged, Stmt};
|
use rustpython_ast::{CmpOp, Expr, Ranged};
|
||||||
use rustpython_parser::ast::Suite;
|
use rustpython_parser::ast::Suite;
|
||||||
use rustpython_parser::Parse;
|
use rustpython_parser::Parse;
|
||||||
|
|
||||||
use crate::helpers::{
|
use crate::helpers::{
|
||||||
elif_else_range, first_colon_range, has_trailing_content, locate_cmp_ops,
|
first_colon_range, has_trailing_content, locate_cmp_ops, resolve_imported_module_path,
|
||||||
resolve_imported_module_path, LocatedCmpOp,
|
LocatedCmpOp,
|
||||||
};
|
};
|
||||||
use crate::source_code::Locator;
|
use crate::source_code::Locator;
|
||||||
|
|
||||||
|
@ -1667,35 +1665,6 @@ y = 2
|
||||||
assert_eq!(range, TextRange::new(TextSize::from(6), TextSize::from(7)));
|
assert_eq!(range, TextRange::new(TextSize::from(6), TextSize::from(7)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn extract_elif_else_range() -> Result<()> {
|
|
||||||
let contents = "if a:
|
|
||||||
...
|
|
||||||
elif b:
|
|
||||||
...
|
|
||||||
";
|
|
||||||
let stmt = Stmt::parse(contents, "<filename>")?;
|
|
||||||
let stmt = Stmt::as_if_stmt(&stmt).unwrap();
|
|
||||||
let locator = Locator::new(contents);
|
|
||||||
let range = elif_else_range(stmt, &locator).unwrap();
|
|
||||||
assert_eq!(range.start(), TextSize::from(14));
|
|
||||||
assert_eq!(range.end(), TextSize::from(18));
|
|
||||||
|
|
||||||
let contents = "if a:
|
|
||||||
...
|
|
||||||
else:
|
|
||||||
...
|
|
||||||
";
|
|
||||||
let stmt = Stmt::parse(contents, "<filename>")?;
|
|
||||||
let stmt = Stmt::as_if_stmt(&stmt).unwrap();
|
|
||||||
let locator = Locator::new(contents);
|
|
||||||
let range = elif_else_range(stmt, &locator).unwrap();
|
|
||||||
assert_eq!(range.start(), TextSize::from(14));
|
|
||||||
assert_eq!(range.end(), TextSize::from(18));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn extract_cmp_op_location() -> Result<()> {
|
fn extract_cmp_op_location() -> Result<()> {
|
||||||
let contents = "x == 1";
|
let contents = "x == 1";
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub mod node;
|
||||||
pub mod relocate;
|
pub mod relocate;
|
||||||
pub mod source_code;
|
pub mod source_code;
|
||||||
pub mod statement_visitor;
|
pub mod statement_visitor;
|
||||||
|
pub mod stmt_if;
|
||||||
pub mod str;
|
pub mod str;
|
||||||
pub mod token_kind;
|
pub mod token_kind;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
|
@ -98,6 +98,7 @@ pub enum AnyNode {
|
||||||
WithItem(WithItem),
|
WithItem(WithItem),
|
||||||
MatchCase(MatchCase),
|
MatchCase(MatchCase),
|
||||||
Decorator(Decorator),
|
Decorator(Decorator),
|
||||||
|
ElifElseClause(ast::ElifElseClause),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnyNode {
|
impl AnyNode {
|
||||||
|
@ -180,7 +181,8 @@ impl AnyNode {
|
||||||
| AnyNode::Alias(_)
|
| AnyNode::Alias(_)
|
||||||
| AnyNode::WithItem(_)
|
| AnyNode::WithItem(_)
|
||||||
| AnyNode::MatchCase(_)
|
| AnyNode::MatchCase(_)
|
||||||
| AnyNode::Decorator(_) => None,
|
| AnyNode::Decorator(_)
|
||||||
|
| AnyNode::ElifElseClause(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +265,8 @@ impl AnyNode {
|
||||||
| AnyNode::Alias(_)
|
| AnyNode::Alias(_)
|
||||||
| AnyNode::WithItem(_)
|
| AnyNode::WithItem(_)
|
||||||
| AnyNode::MatchCase(_)
|
| AnyNode::MatchCase(_)
|
||||||
| AnyNode::Decorator(_) => None,
|
| AnyNode::Decorator(_)
|
||||||
|
| AnyNode::ElifElseClause(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,7 +349,8 @@ impl AnyNode {
|
||||||
| AnyNode::Alias(_)
|
| AnyNode::Alias(_)
|
||||||
| AnyNode::WithItem(_)
|
| AnyNode::WithItem(_)
|
||||||
| AnyNode::MatchCase(_)
|
| AnyNode::MatchCase(_)
|
||||||
| AnyNode::Decorator(_) => None,
|
| AnyNode::Decorator(_)
|
||||||
|
| AnyNode::ElifElseClause(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,7 +433,8 @@ impl AnyNode {
|
||||||
| AnyNode::Alias(_)
|
| AnyNode::Alias(_)
|
||||||
| AnyNode::WithItem(_)
|
| AnyNode::WithItem(_)
|
||||||
| AnyNode::MatchCase(_)
|
| AnyNode::MatchCase(_)
|
||||||
| AnyNode::Decorator(_) => None,
|
| AnyNode::Decorator(_)
|
||||||
|
| AnyNode::ElifElseClause(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,7 +517,8 @@ impl AnyNode {
|
||||||
| AnyNode::Alias(_)
|
| AnyNode::Alias(_)
|
||||||
| AnyNode::WithItem(_)
|
| AnyNode::WithItem(_)
|
||||||
| AnyNode::MatchCase(_)
|
| AnyNode::MatchCase(_)
|
||||||
| AnyNode::Decorator(_) => None,
|
| AnyNode::Decorator(_)
|
||||||
|
| AnyNode::ElifElseClause(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,7 +601,8 @@ impl AnyNode {
|
||||||
| AnyNode::Alias(_)
|
| AnyNode::Alias(_)
|
||||||
| AnyNode::WithItem(_)
|
| AnyNode::WithItem(_)
|
||||||
| AnyNode::MatchCase(_)
|
| AnyNode::MatchCase(_)
|
||||||
| AnyNode::Decorator(_) => None,
|
| AnyNode::Decorator(_)
|
||||||
|
| AnyNode::ElifElseClause(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -702,6 +709,7 @@ impl AnyNode {
|
||||||
Self::WithItem(node) => AnyNodeRef::WithItem(node),
|
Self::WithItem(node) => AnyNodeRef::WithItem(node),
|
||||||
Self::MatchCase(node) => AnyNodeRef::MatchCase(node),
|
Self::MatchCase(node) => AnyNodeRef::MatchCase(node),
|
||||||
Self::Decorator(node) => AnyNodeRef::Decorator(node),
|
Self::Decorator(node) => AnyNodeRef::Decorator(node),
|
||||||
|
Self::ElifElseClause(node) => AnyNodeRef::ElifElseClause(node),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1159,6 +1167,34 @@ impl AstNode for ast::StmtIf {
|
||||||
AnyNode::from(self)
|
AnyNode::from(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AstNode for ast::ElifElseClause {
|
||||||
|
fn cast(kind: AnyNode) -> Option<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
if let AnyNode::ElifElseClause(node) = kind {
|
||||||
|
Some(node)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_ref(kind: AnyNodeRef) -> Option<&Self> {
|
||||||
|
if let AnyNodeRef::ElifElseClause(node) = kind {
|
||||||
|
Some(node)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_node_ref(&self) -> AnyNodeRef {
|
||||||
|
AnyNodeRef::from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_any_node(self) -> AnyNode {
|
||||||
|
AnyNode::from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl AstNode for ast::StmtWith {
|
impl AstNode for ast::StmtWith {
|
||||||
fn cast(kind: AnyNode) -> Option<Self>
|
fn cast(kind: AnyNode) -> Option<Self>
|
||||||
where
|
where
|
||||||
|
@ -2900,6 +2936,7 @@ impl From<Stmt> for AnyNode {
|
||||||
Stmt::Pass(node) => AnyNode::StmtPass(node),
|
Stmt::Pass(node) => AnyNode::StmtPass(node),
|
||||||
Stmt::Break(node) => AnyNode::StmtBreak(node),
|
Stmt::Break(node) => AnyNode::StmtBreak(node),
|
||||||
Stmt::Continue(node) => AnyNode::StmtContinue(node),
|
Stmt::Continue(node) => AnyNode::StmtContinue(node),
|
||||||
|
Stmt::TypeAlias(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3076,6 +3113,12 @@ impl From<ast::StmtIf> for AnyNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ast::ElifElseClause> for AnyNode {
|
||||||
|
fn from(node: ast::ElifElseClause) -> Self {
|
||||||
|
AnyNode::ElifElseClause(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<ast::StmtWith> for AnyNode {
|
impl From<ast::StmtWith> for AnyNode {
|
||||||
fn from(node: ast::StmtWith) -> Self {
|
fn from(node: ast::StmtWith) -> Self {
|
||||||
AnyNode::StmtWith(node)
|
AnyNode::StmtWith(node)
|
||||||
|
@ -3514,6 +3557,7 @@ impl Ranged for AnyNode {
|
||||||
AnyNode::WithItem(node) => node.range(),
|
AnyNode::WithItem(node) => node.range(),
|
||||||
AnyNode::MatchCase(node) => node.range(),
|
AnyNode::MatchCase(node) => node.range(),
|
||||||
AnyNode::Decorator(node) => node.range(),
|
AnyNode::Decorator(node) => node.range(),
|
||||||
|
AnyNode::ElifElseClause(node) => node.range(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3597,6 +3641,7 @@ pub enum AnyNodeRef<'a> {
|
||||||
WithItem(&'a WithItem),
|
WithItem(&'a WithItem),
|
||||||
MatchCase(&'a MatchCase),
|
MatchCase(&'a MatchCase),
|
||||||
Decorator(&'a Decorator),
|
Decorator(&'a Decorator),
|
||||||
|
ElifElseClause(&'a ast::ElifElseClause),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnyNodeRef<'_> {
|
impl AnyNodeRef<'_> {
|
||||||
|
@ -3679,6 +3724,7 @@ impl AnyNodeRef<'_> {
|
||||||
AnyNodeRef::WithItem(node) => NonNull::from(*node).cast(),
|
AnyNodeRef::WithItem(node) => NonNull::from(*node).cast(),
|
||||||
AnyNodeRef::MatchCase(node) => NonNull::from(*node).cast(),
|
AnyNodeRef::MatchCase(node) => NonNull::from(*node).cast(),
|
||||||
AnyNodeRef::Decorator(node) => NonNull::from(*node).cast(),
|
AnyNodeRef::Decorator(node) => NonNull::from(*node).cast(),
|
||||||
|
AnyNodeRef::ElifElseClause(node) => NonNull::from(*node).cast(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3767,6 +3813,7 @@ impl AnyNodeRef<'_> {
|
||||||
AnyNodeRef::WithItem(_) => NodeKind::WithItem,
|
AnyNodeRef::WithItem(_) => NodeKind::WithItem,
|
||||||
AnyNodeRef::MatchCase(_) => NodeKind::MatchCase,
|
AnyNodeRef::MatchCase(_) => NodeKind::MatchCase,
|
||||||
AnyNodeRef::Decorator(_) => NodeKind::Decorator,
|
AnyNodeRef::Decorator(_) => NodeKind::Decorator,
|
||||||
|
AnyNodeRef::ElifElseClause(_) => NodeKind::ElifElseClause,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3849,7 +3896,8 @@ impl AnyNodeRef<'_> {
|
||||||
| AnyNodeRef::Alias(_)
|
| AnyNodeRef::Alias(_)
|
||||||
| AnyNodeRef::WithItem(_)
|
| AnyNodeRef::WithItem(_)
|
||||||
| AnyNodeRef::MatchCase(_)
|
| AnyNodeRef::MatchCase(_)
|
||||||
| AnyNodeRef::Decorator(_) => false,
|
| AnyNodeRef::Decorator(_)
|
||||||
|
| AnyNodeRef::ElifElseClause(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3932,7 +3980,8 @@ impl AnyNodeRef<'_> {
|
||||||
| AnyNodeRef::Alias(_)
|
| AnyNodeRef::Alias(_)
|
||||||
| AnyNodeRef::WithItem(_)
|
| AnyNodeRef::WithItem(_)
|
||||||
| AnyNodeRef::MatchCase(_)
|
| AnyNodeRef::MatchCase(_)
|
||||||
| AnyNodeRef::Decorator(_) => false,
|
| AnyNodeRef::Decorator(_)
|
||||||
|
| AnyNodeRef::ElifElseClause(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4015,7 +4064,8 @@ impl AnyNodeRef<'_> {
|
||||||
| AnyNodeRef::Alias(_)
|
| AnyNodeRef::Alias(_)
|
||||||
| AnyNodeRef::WithItem(_)
|
| AnyNodeRef::WithItem(_)
|
||||||
| AnyNodeRef::MatchCase(_)
|
| AnyNodeRef::MatchCase(_)
|
||||||
| AnyNodeRef::Decorator(_) => false,
|
| AnyNodeRef::Decorator(_)
|
||||||
|
| AnyNodeRef::ElifElseClause(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4098,7 +4148,8 @@ impl AnyNodeRef<'_> {
|
||||||
| AnyNodeRef::Alias(_)
|
| AnyNodeRef::Alias(_)
|
||||||
| AnyNodeRef::WithItem(_)
|
| AnyNodeRef::WithItem(_)
|
||||||
| AnyNodeRef::MatchCase(_)
|
| AnyNodeRef::MatchCase(_)
|
||||||
| AnyNodeRef::Decorator(_) => false,
|
| AnyNodeRef::Decorator(_)
|
||||||
|
| AnyNodeRef::ElifElseClause(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4181,7 +4232,8 @@ impl AnyNodeRef<'_> {
|
||||||
| AnyNodeRef::Alias(_)
|
| AnyNodeRef::Alias(_)
|
||||||
| AnyNodeRef::WithItem(_)
|
| AnyNodeRef::WithItem(_)
|
||||||
| AnyNodeRef::MatchCase(_)
|
| AnyNodeRef::MatchCase(_)
|
||||||
| AnyNodeRef::Decorator(_) => false,
|
| AnyNodeRef::Decorator(_)
|
||||||
|
| AnyNodeRef::ElifElseClause(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4264,7 +4316,8 @@ impl AnyNodeRef<'_> {
|
||||||
| AnyNodeRef::Alias(_)
|
| AnyNodeRef::Alias(_)
|
||||||
| AnyNodeRef::WithItem(_)
|
| AnyNodeRef::WithItem(_)
|
||||||
| AnyNodeRef::MatchCase(_)
|
| AnyNodeRef::MatchCase(_)
|
||||||
| AnyNodeRef::Decorator(_) => false,
|
| AnyNodeRef::Decorator(_)
|
||||||
|
| AnyNodeRef::ElifElseClause(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4383,6 +4436,12 @@ impl<'a> From<&'a ast::StmtIf> for AnyNodeRef<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a ast::ElifElseClause> for AnyNodeRef<'a> {
|
||||||
|
fn from(node: &'a ast::ElifElseClause) -> Self {
|
||||||
|
AnyNodeRef::ElifElseClause(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ast::StmtWith> for AnyNodeRef<'a> {
|
impl<'a> From<&'a ast::StmtWith> for AnyNodeRef<'a> {
|
||||||
fn from(node: &'a ast::StmtWith) -> Self {
|
fn from(node: &'a ast::StmtWith) -> Self {
|
||||||
AnyNodeRef::StmtWith(node)
|
AnyNodeRef::StmtWith(node)
|
||||||
|
@ -4731,6 +4790,7 @@ impl<'a> From<&'a Stmt> for AnyNodeRef<'a> {
|
||||||
Stmt::Pass(node) => AnyNodeRef::StmtPass(node),
|
Stmt::Pass(node) => AnyNodeRef::StmtPass(node),
|
||||||
Stmt::Break(node) => AnyNodeRef::StmtBreak(node),
|
Stmt::Break(node) => AnyNodeRef::StmtBreak(node),
|
||||||
Stmt::Continue(node) => AnyNodeRef::StmtContinue(node),
|
Stmt::Continue(node) => AnyNodeRef::StmtContinue(node),
|
||||||
|
Stmt::TypeAlias(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4934,6 +4994,7 @@ impl Ranged for AnyNodeRef<'_> {
|
||||||
AnyNodeRef::WithItem(node) => node.range(),
|
AnyNodeRef::WithItem(node) => node.range(),
|
||||||
AnyNodeRef::MatchCase(node) => node.range(),
|
AnyNodeRef::MatchCase(node) => node.range(),
|
||||||
AnyNodeRef::Decorator(node) => node.range(),
|
AnyNodeRef::Decorator(node) => node.range(),
|
||||||
|
AnyNodeRef::ElifElseClause(node) => node.range(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5017,4 +5078,5 @@ pub enum NodeKind {
|
||||||
WithItem,
|
WithItem,
|
||||||
MatchCase,
|
MatchCase,
|
||||||
Decorator,
|
Decorator,
|
||||||
|
ElifElseClause,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use itertools::Itertools;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
@ -25,6 +26,21 @@ impl CommentRanges {
|
||||||
})
|
})
|
||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the comments who are within the range
|
||||||
|
pub fn comments_in_range(&self, range: TextRange) -> &[TextRange] {
|
||||||
|
let start = self
|
||||||
|
.raw
|
||||||
|
.partition_point(|comment| comment.start() < range.start());
|
||||||
|
// We expect there are few comments, so switching to find should be faster
|
||||||
|
match self.raw[start..]
|
||||||
|
.iter()
|
||||||
|
.find_position(|comment| comment.end() > range.end())
|
||||||
|
{
|
||||||
|
Some((in_range, _element)) => &self.raw[start..start + in_range],
|
||||||
|
None => &self.raw[start..],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for CommentRanges {
|
impl Deref for CommentRanges {
|
||||||
|
|
|
@ -271,7 +271,8 @@ impl<'a> Generator<'a> {
|
||||||
keywords,
|
keywords,
|
||||||
body,
|
body,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
range: _range,
|
range: _,
|
||||||
|
type_params: _,
|
||||||
}) => {
|
}) => {
|
||||||
self.newlines(if self.indent_depth == 0 { 2 } else { 1 });
|
self.newlines(if self.indent_depth == 0 { 2 } else { 1 });
|
||||||
for decorator in decorator_list {
|
for decorator in decorator_list {
|
||||||
|
@ -457,7 +458,7 @@ impl<'a> Generator<'a> {
|
||||||
Stmt::If(ast::StmtIf {
|
Stmt::If(ast::StmtIf {
|
||||||
test,
|
test,
|
||||||
body,
|
body,
|
||||||
orelse,
|
elif_else_clauses,
|
||||||
range: _range,
|
range: _range,
|
||||||
}) => {
|
}) => {
|
||||||
statement!({
|
statement!({
|
||||||
|
@ -467,33 +468,19 @@ impl<'a> Generator<'a> {
|
||||||
});
|
});
|
||||||
self.body(body);
|
self.body(body);
|
||||||
|
|
||||||
let mut orelse_: &[Stmt] = orelse;
|
for clause in elif_else_clauses {
|
||||||
loop {
|
if let Some(test) = &clause.test {
|
||||||
if orelse_.len() == 1 && matches!(orelse_[0], Stmt::If(_)) {
|
statement!({
|
||||||
if let Stmt::If(ast::StmtIf {
|
self.p("elif ");
|
||||||
body,
|
self.unparse_expr(test, precedence::IF);
|
||||||
test,
|
self.p(":");
|
||||||
orelse,
|
});
|
||||||
range: _range,
|
|
||||||
}) = &orelse_[0]
|
|
||||||
{
|
|
||||||
statement!({
|
|
||||||
self.p("elif ");
|
|
||||||
self.unparse_expr(test, precedence::IF);
|
|
||||||
self.p(":");
|
|
||||||
});
|
|
||||||
self.body(body);
|
|
||||||
orelse_ = orelse;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if !orelse_.is_empty() {
|
statement!({
|
||||||
statement!({
|
self.p("else:");
|
||||||
self.p("else:");
|
});
|
||||||
});
|
|
||||||
self.body(orelse_);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
self.body(&clause.body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::With(ast::StmtWith { items, body, .. }) => {
|
Stmt::With(ast::StmtWith { items, body, .. }) => {
|
||||||
|
@ -715,6 +702,7 @@ impl<'a> Generator<'a> {
|
||||||
self.p("continue");
|
self.p("continue");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Stmt::TypeAlias(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,18 @@ impl Indexer {
|
||||||
&self.comment_ranges
|
&self.comment_ranges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the comments in the given range as source code slices
|
||||||
|
pub fn comments_in_range<'a>(
|
||||||
|
&'a self,
|
||||||
|
range: TextRange,
|
||||||
|
locator: &'a Locator,
|
||||||
|
) -> impl Iterator<Item = &'a str> {
|
||||||
|
self.comment_ranges
|
||||||
|
.comments_in_range(range)
|
||||||
|
.iter()
|
||||||
|
.map(move |comment_range| locator.slice(*comment_range))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the line start positions of continuations (backslash).
|
/// Returns the line start positions of continuations (backslash).
|
||||||
pub fn continuation_line_starts(&self) -> &[TextSize] {
|
pub fn continuation_line_starts(&self) -> &[TextSize] {
|
||||||
&self.continuation_lines
|
&self.continuation_lines
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! Specialized AST visitor trait and walk functions that only visit statements.
|
//! Specialized AST visitor trait and walk functions that only visit statements.
|
||||||
|
|
||||||
|
use rustpython_ast::ElifElseClause;
|
||||||
use rustpython_parser::ast::{self, ExceptHandler, MatchCase, Stmt};
|
use rustpython_parser::ast::{self, ExceptHandler, MatchCase, Stmt};
|
||||||
|
|
||||||
/// A trait for AST visitors that only need to visit statements.
|
/// A trait for AST visitors that only need to visit statements.
|
||||||
|
@ -13,6 +14,9 @@ pub trait StatementVisitor<'a> {
|
||||||
fn visit_except_handler(&mut self, except_handler: &'a ExceptHandler) {
|
fn visit_except_handler(&mut self, except_handler: &'a ExceptHandler) {
|
||||||
walk_except_handler(self, except_handler);
|
walk_except_handler(self, except_handler);
|
||||||
}
|
}
|
||||||
|
fn visit_elif_else_clause(&mut self, elif_else_clause: &'a ElifElseClause) {
|
||||||
|
walk_elif_else_clause(self, elif_else_clause);
|
||||||
|
}
|
||||||
fn visit_match_case(&mut self, match_case: &'a MatchCase) {
|
fn visit_match_case(&mut self, match_case: &'a MatchCase) {
|
||||||
walk_match_case(self, match_case);
|
walk_match_case(self, match_case);
|
||||||
}
|
}
|
||||||
|
@ -47,9 +51,15 @@ pub fn walk_stmt<'a, V: StatementVisitor<'a> + ?Sized>(visitor: &mut V, stmt: &'
|
||||||
visitor.visit_body(body);
|
visitor.visit_body(body);
|
||||||
visitor.visit_body(orelse);
|
visitor.visit_body(orelse);
|
||||||
}
|
}
|
||||||
Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
Stmt::If(ast::StmtIf {
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
visitor.visit_body(body);
|
visitor.visit_body(body);
|
||||||
visitor.visit_body(orelse);
|
for clause in elif_else_clauses {
|
||||||
|
visitor.visit_elif_else_clause(clause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::With(ast::StmtWith { body, .. }) => {
|
Stmt::With(ast::StmtWith { body, .. }) => {
|
||||||
visitor.visit_body(body);
|
visitor.visit_body(body);
|
||||||
|
@ -105,6 +115,13 @@ pub fn walk_except_handler<'a, V: StatementVisitor<'a> + ?Sized>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn walk_elif_else_clause<'a, V: StatementVisitor<'a> + ?Sized>(
|
||||||
|
visitor: &mut V,
|
||||||
|
elif_else_clause: &'a ElifElseClause,
|
||||||
|
) {
|
||||||
|
visitor.visit_body(&elif_else_clause.body);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn walk_match_case<'a, V: StatementVisitor<'a> + ?Sized>(
|
pub fn walk_match_case<'a, V: StatementVisitor<'a> + ?Sized>(
|
||||||
visitor: &mut V,
|
visitor: &mut V,
|
||||||
match_case: &'a MatchCase,
|
match_case: &'a MatchCase,
|
||||||
|
|
87
crates/ruff_python_ast/src/stmt_if.rs
Normal file
87
crates/ruff_python_ast/src/stmt_if.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
use crate::source_code::Locator;
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
|
use rustpython_ast::{ElifElseClause, Expr, Ranged, Stmt, StmtIf};
|
||||||
|
use rustpython_parser::{lexer, Mode, Tok};
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
/// Return the `Range` of the first `Elif` or `Else` token in an `If` statement.
|
||||||
|
pub fn elif_else_range(clause: &ElifElseClause, locator: &Locator) -> Option<TextRange> {
|
||||||
|
let contents = &locator.contents()[clause.range];
|
||||||
|
let token = lexer::lex_starts_at(contents, Mode::Module, clause.range.start())
|
||||||
|
.flatten()
|
||||||
|
.next()?;
|
||||||
|
if matches!(token.0, Tok::Elif | Tok::Else) {
|
||||||
|
Some(token.1)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum BranchKind {
|
||||||
|
If,
|
||||||
|
Elif,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IfElifBranch<'a> {
|
||||||
|
pub kind: BranchKind,
|
||||||
|
pub test: &'a Expr,
|
||||||
|
pub body: &'a [Stmt],
|
||||||
|
pub range: TextRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn if_elif_branches(stmt_if: &StmtIf) -> impl Iterator<Item = IfElifBranch> {
|
||||||
|
iter::once(IfElifBranch {
|
||||||
|
kind: BranchKind::If,
|
||||||
|
test: stmt_if.test.as_ref(),
|
||||||
|
body: stmt_if.body.as_slice(),
|
||||||
|
range: TextRange::new(stmt_if.start(), stmt_if.body.last().unwrap().end()),
|
||||||
|
})
|
||||||
|
.chain(stmt_if.elif_else_clauses.iter().filter_map(|clause| {
|
||||||
|
Some(IfElifBranch {
|
||||||
|
kind: BranchKind::Elif,
|
||||||
|
test: clause.test.as_ref()?,
|
||||||
|
body: clause.body.as_slice(),
|
||||||
|
range: clause.range,
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::source_code::Locator;
|
||||||
|
use crate::stmt_if::elif_else_range;
|
||||||
|
use anyhow::Result;
|
||||||
|
use ruff_text_size::TextSize;
|
||||||
|
use rustpython_ast::Stmt;
|
||||||
|
use rustpython_parser::Parse;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_elif_else_range() -> Result<()> {
|
||||||
|
let contents = "if a:
|
||||||
|
...
|
||||||
|
elif b:
|
||||||
|
...
|
||||||
|
";
|
||||||
|
let stmt = Stmt::parse(contents, "<filename>")?;
|
||||||
|
let stmt = Stmt::as_if_stmt(&stmt).unwrap();
|
||||||
|
let locator = Locator::new(contents);
|
||||||
|
let range = elif_else_range(&stmt.elif_else_clauses[0], &locator).unwrap();
|
||||||
|
assert_eq!(range.start(), TextSize::from(14));
|
||||||
|
assert_eq!(range.end(), TextSize::from(18));
|
||||||
|
|
||||||
|
let contents = "if a:
|
||||||
|
...
|
||||||
|
else:
|
||||||
|
...
|
||||||
|
";
|
||||||
|
let stmt = Stmt::parse(contents, "<filename>")?;
|
||||||
|
let stmt = Stmt::as_if_stmt(&stmt).unwrap();
|
||||||
|
let locator = Locator::new(contents);
|
||||||
|
let range = elif_else_range(&stmt.elif_else_clauses[0], &locator).unwrap();
|
||||||
|
assert_eq!(range.start(), TextSize::from(14));
|
||||||
|
assert_eq!(range.end(), TextSize::from(18));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -431,6 +431,8 @@ impl TokenKind {
|
||||||
Tok::StartModule => TokenKind::StartModule,
|
Tok::StartModule => TokenKind::StartModule,
|
||||||
Tok::StartInteractive => TokenKind::StartInteractive,
|
Tok::StartInteractive => TokenKind::StartInteractive,
|
||||||
Tok::StartExpression => TokenKind::StartExpression,
|
Tok::StartExpression => TokenKind::StartExpression,
|
||||||
|
Tok::MagicCommand { .. } => todo!(),
|
||||||
|
Tok::Type => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
pub mod preorder;
|
pub mod preorder;
|
||||||
|
|
||||||
|
use rustpython_ast::ElifElseClause;
|
||||||
use rustpython_parser::ast::{
|
use rustpython_parser::ast::{
|
||||||
self, Alias, Arg, Arguments, BoolOp, CmpOp, Comprehension, Decorator, ExceptHandler, Expr,
|
self, Alias, Arg, Arguments, BoolOp, CmpOp, Comprehension, Decorator, ExceptHandler, Expr,
|
||||||
ExprContext, Keyword, MatchCase, Operator, Pattern, Stmt, UnaryOp, WithItem,
|
ExprContext, Keyword, MatchCase, Operator, Pattern, Stmt, UnaryOp, WithItem,
|
||||||
|
@ -75,6 +76,9 @@ pub trait Visitor<'a> {
|
||||||
fn visit_body(&mut self, body: &'a [Stmt]) {
|
fn visit_body(&mut self, body: &'a [Stmt]) {
|
||||||
walk_body(self, body);
|
walk_body(self, body);
|
||||||
}
|
}
|
||||||
|
fn visit_elif_else_clause(&mut self, elif_else_clause: &'a ElifElseClause) {
|
||||||
|
walk_elif_else_clause(self, elif_else_clause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn walk_body<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, body: &'a [Stmt]) {
|
pub fn walk_body<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, body: &'a [Stmt]) {
|
||||||
|
@ -83,6 +87,16 @@ pub fn walk_body<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, body: &'a [Stmt])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn walk_elif_else_clause<'a, V: Visitor<'a> + ?Sized>(
|
||||||
|
visitor: &mut V,
|
||||||
|
elif_else_clause: &'a ElifElseClause,
|
||||||
|
) {
|
||||||
|
if let Some(test) = &elif_else_clause.test {
|
||||||
|
visitor.visit_expr(test);
|
||||||
|
}
|
||||||
|
visitor.visit_body(&elif_else_clause.body);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||||
|
@ -216,12 +230,17 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||||
Stmt::If(ast::StmtIf {
|
Stmt::If(ast::StmtIf {
|
||||||
test,
|
test,
|
||||||
body,
|
body,
|
||||||
orelse,
|
elif_else_clauses,
|
||||||
range: _range,
|
range: _range,
|
||||||
}) => {
|
}) => {
|
||||||
visitor.visit_expr(test);
|
visitor.visit_expr(test);
|
||||||
visitor.visit_body(body);
|
visitor.visit_body(body);
|
||||||
visitor.visit_body(orelse);
|
for clause in elif_else_clauses {
|
||||||
|
if let Some(test) = &clause.test {
|
||||||
|
visitor.visit_expr(test);
|
||||||
|
}
|
||||||
|
walk_elif_else_clause(visitor, clause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::With(ast::StmtWith { items, body, .. }) => {
|
Stmt::With(ast::StmtWith { items, body, .. }) => {
|
||||||
for with_item in items {
|
for with_item in items {
|
||||||
|
@ -315,6 +334,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||||
range: _range,
|
range: _range,
|
||||||
}) => visitor.visit_expr(value),
|
}) => visitor.visit_expr(value),
|
||||||
Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) => {}
|
Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) => {}
|
||||||
|
Stmt::TypeAlias(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use rustpython_ast::{ArgWithDefault, Mod, TypeIgnore};
|
use rustpython_ast::{ArgWithDefault, ElifElseClause, Mod, TypeIgnore};
|
||||||
use rustpython_parser::ast::{
|
use rustpython_parser::ast::{
|
||||||
self, Alias, Arg, Arguments, BoolOp, CmpOp, Comprehension, Constant, Decorator, ExceptHandler,
|
self, Alias, Arg, Arguments, BoolOp, CmpOp, Comprehension, Constant, Decorator, ExceptHandler,
|
||||||
Expr, Keyword, MatchCase, Operator, Pattern, Stmt, UnaryOp, WithItem,
|
Expr, Keyword, MatchCase, Operator, Pattern, Stmt, UnaryOp, WithItem,
|
||||||
|
@ -95,6 +95,10 @@ pub trait PreorderVisitor<'a> {
|
||||||
fn visit_type_ignore(&mut self, type_ignore: &'a TypeIgnore) {
|
fn visit_type_ignore(&mut self, type_ignore: &'a TypeIgnore) {
|
||||||
walk_type_ignore(self, type_ignore);
|
walk_type_ignore(self, type_ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_elif_else_clause(&mut self, elif_else_clause: &'a ElifElseClause) {
|
||||||
|
walk_elif_else_clause(self, elif_else_clause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn walk_module<'a, V>(visitor: &mut V, module: &'a Mod)
|
pub fn walk_module<'a, V>(visitor: &mut V, module: &'a Mod)
|
||||||
|
@ -286,12 +290,14 @@ where
|
||||||
Stmt::If(ast::StmtIf {
|
Stmt::If(ast::StmtIf {
|
||||||
test,
|
test,
|
||||||
body,
|
body,
|
||||||
orelse,
|
elif_else_clauses,
|
||||||
range: _range,
|
range: _range,
|
||||||
}) => {
|
}) => {
|
||||||
visitor.visit_expr(test);
|
visitor.visit_expr(test);
|
||||||
visitor.visit_body(body);
|
visitor.visit_body(body);
|
||||||
visitor.visit_body(orelse);
|
for clause in elif_else_clauses {
|
||||||
|
visitor.visit_elif_else_clause(clause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt::With(ast::StmtWith {
|
Stmt::With(ast::StmtWith {
|
||||||
|
@ -394,6 +400,7 @@ where
|
||||||
| Stmt::Continue(_)
|
| Stmt::Continue(_)
|
||||||
| Stmt::Global(_)
|
| Stmt::Global(_)
|
||||||
| Stmt::Nonlocal(_) => {}
|
| Stmt::Nonlocal(_) => {}
|
||||||
|
Stmt::TypeAlias(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -703,6 +710,16 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn walk_elif_else_clause<'a, V>(visitor: &mut V, elif_else_clause: &'a ElifElseClause)
|
||||||
|
where
|
||||||
|
V: PreorderVisitor<'a> + ?Sized,
|
||||||
|
{
|
||||||
|
if let Some(test) = &elif_else_clause.test {
|
||||||
|
visitor.visit_expr(test);
|
||||||
|
}
|
||||||
|
visitor.visit_body(&elif_else_clause.body);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn walk_except_handler<'a, V>(visitor: &mut V, except_handler: &'a ExceptHandler)
|
pub fn walk_except_handler<'a, V>(visitor: &mut V, except_handler: &'a ExceptHandler)
|
||||||
where
|
where
|
||||||
V: PreorderVisitor<'a> + ?Sized,
|
V: PreorderVisitor<'a> + ?Sized,
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
if x == y: # trailing if condition
|
# 1 leading if comment
|
||||||
pass # trailing `pass` comment
|
if x == y: # 2 trailing if condition
|
||||||
# Root `if` trailing comment
|
# 3 leading pass
|
||||||
|
pass # 4 end-of-line trailing `pass` comment
|
||||||
|
# 5 Root `if` trailing comment
|
||||||
|
|
||||||
# Leading elif comment
|
# 6 Leading elif comment
|
||||||
elif x < y: # trailing elif condition
|
elif x < y: # 7 trailing elif condition
|
||||||
pass
|
# 8 leading pass
|
||||||
# `elif` trailing comment
|
pass # 9 end-of-line trailing `pass` comment
|
||||||
|
# 10 `elif` trailing comment
|
||||||
|
|
||||||
# Leading else comment
|
# 11 Leading else comment
|
||||||
else: # trailing else condition
|
else: # 12 trailing else condition
|
||||||
pass
|
# 13 leading pass
|
||||||
# `else` trailing comment
|
pass # 14 end-of-line trailing `pass` comment
|
||||||
|
# 15 `else` trailing comment
|
||||||
|
|
||||||
|
|
||||||
if x == y:
|
if x == y:
|
||||||
|
@ -71,3 +75,14 @@ else: # Comment
|
||||||
if False:
|
if False:
|
||||||
pass
|
pass
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Regression test for `last_child_in_body` special casing of `StmtIf`
|
||||||
|
# https://github.com/python/cpython/blob/aecf6aca515a203a823a87c711f15cbb82097c8b/Lib/test/test_pty.py#L260-L275
|
||||||
|
def f():
|
||||||
|
if True:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# comment
|
||||||
|
|
|
@ -292,12 +292,26 @@ fn handle_in_between_bodies_own_line_comment<'a>(
|
||||||
// if x == y:
|
// if x == y:
|
||||||
// pass
|
// pass
|
||||||
// # I'm a leading comment of the `elif` statement.
|
// # I'm a leading comment of the `elif` statement.
|
||||||
// elif:
|
// elif True:
|
||||||
// print("nooop")
|
// print("nooop")
|
||||||
// ```
|
// ```
|
||||||
if following.is_stmt_if() || following.is_except_handler() {
|
if following.is_except_handler() {
|
||||||
// The `elif` or except handlers have their own body to which we can attach the leading comment
|
// The except handlers have their own body to which we can attach the leading comment
|
||||||
CommentPlacement::leading(following, comment)
|
CommentPlacement::leading(following, comment)
|
||||||
|
} else if let AnyNodeRef::StmtIf(stmt_if) = comment.enclosing_node() {
|
||||||
|
if let Some(clause) = stmt_if
|
||||||
|
.elif_else_clauses
|
||||||
|
.iter()
|
||||||
|
.find(|clause| are_same_optional(following, clause.test.as_ref()))
|
||||||
|
{
|
||||||
|
CommentPlacement::leading(clause.into(), comment)
|
||||||
|
} else {
|
||||||
|
// Since we know we're between bodies and we know that the following node is
|
||||||
|
// not the condition of any `elif`, we know the next node must be the `else`
|
||||||
|
let else_clause = stmt_if.elif_else_clauses.last().unwrap();
|
||||||
|
debug_assert!(else_clause.test.is_none());
|
||||||
|
CommentPlacement::leading(else_clause.into(), comment)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// There are no bodies for the "else" branch and other bodies that are represented as a `Vec<Stmt>`.
|
// There are no bodies for the "else" branch and other bodies that are represented as a `Vec<Stmt>`.
|
||||||
// This means, there's no good place to attach the comments to.
|
// This means, there's no good place to attach the comments to.
|
||||||
|
@ -356,42 +370,42 @@ fn handle_in_between_bodies_end_of_line_comment<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
if locator.contains_line_break(TextRange::new(preceding.end(), comment.slice().start())) {
|
if locator.contains_line_break(TextRange::new(preceding.end(), comment.slice().start())) {
|
||||||
// The `elif` or except handlers have their own body to which we can attach the trailing comment
|
// The except handlers have their own body to which we can attach the trailing comment
|
||||||
// ```python
|
// ```python
|
||||||
// if test:
|
// try:
|
||||||
// a
|
// f() # comment
|
||||||
// elif c: # comment
|
// except RuntimeError:
|
||||||
// b
|
// raise
|
||||||
// ```
|
// ```
|
||||||
if following.is_except_handler() {
|
if following.is_except_handler() {
|
||||||
return CommentPlacement::trailing(following, comment);
|
return CommentPlacement::trailing(following, comment);
|
||||||
} else if following.is_stmt_if() {
|
}
|
||||||
// We have to exclude for following if statements that are not elif by checking the
|
|
||||||
// indentation
|
// Handle the `else` of an `if`. It is special because we don't have a test but unlike other
|
||||||
// ```python
|
// `else` (e.g. for `while`), we have a dedicated node.
|
||||||
// if True:
|
// ```python
|
||||||
// pass
|
// if x == y:
|
||||||
// else: # Comment
|
// pass
|
||||||
// if False:
|
// elif x < y:
|
||||||
// pass
|
// pass
|
||||||
// pass
|
// else: # 12 trailing else condition
|
||||||
// ```
|
// pass
|
||||||
let base_if_indent =
|
// ```
|
||||||
whitespace::indentation_at_offset(locator, following.range().start());
|
if let AnyNodeRef::StmtIf(stmt_if) = comment.enclosing_node() {
|
||||||
let maybe_elif_indent = whitespace::indentation_at_offset(
|
if let Some(else_clause) = stmt_if.elif_else_clauses.last() {
|
||||||
locator,
|
if else_clause.test.is_none()
|
||||||
comment.enclosing_node().range().start(),
|
&& following.ptr_eq(else_clause.body.first().unwrap().into())
|
||||||
);
|
{
|
||||||
if base_if_indent == maybe_elif_indent {
|
return CommentPlacement::dangling(else_clause.into(), comment);
|
||||||
return CommentPlacement::trailing(following, comment);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// There are no bodies for the "else" branch and other bodies that are represented as a `Vec<Stmt>`.
|
|
||||||
// This means, there's no good place to attach the comments to.
|
// There are no bodies for the "else" branch (only `Vec<Stmt>`) expect for StmtIf, so
|
||||||
// Make this a dangling comments and manually format the comment in
|
// we make this a dangling comments of the node containing the alternate branch and
|
||||||
// in the enclosing node's formatting logic. For `try`, it's the formatters responsibility
|
// manually format the comment in that node's formatting logic. For `try`, it's the
|
||||||
// to correctly identify the comments for the `finally` and `orelse` block by looking
|
// formatters responsibility to correctly identify the comments for the `finally` and
|
||||||
// at the comment's range.
|
// `orelse` block by looking at the comment's range.
|
||||||
//
|
//
|
||||||
// ```python
|
// ```python
|
||||||
// while x == y:
|
// while x == y:
|
||||||
|
@ -425,6 +439,64 @@ fn handle_in_between_bodies_end_of_line_comment<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Without the `StmtIf` special, this function would just be the following:
|
||||||
|
/// ```ignore
|
||||||
|
/// if let Some(preceding_node) = comment.preceding_node() {
|
||||||
|
/// Some((preceding_node, last_child_in_body(preceding_node)?))
|
||||||
|
/// } else {
|
||||||
|
/// None
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// We handle two special cases here:
|
||||||
|
/// ```python
|
||||||
|
/// if True:
|
||||||
|
/// pass
|
||||||
|
/// # Comment between if and elif/else clause, needs to be manually attached to the `StmtIf`
|
||||||
|
/// else:
|
||||||
|
/// pass
|
||||||
|
/// # Comment after the `StmtIf`, needs to be manually attached to the ElifElseClause
|
||||||
|
/// ```
|
||||||
|
/// The problem is that `StmtIf` spans the whole range (there is no "inner if" node), so the first
|
||||||
|
/// comment doesn't see it as preceding node, and the second comment takes the entire `StmtIf` when
|
||||||
|
/// it should only take the `ElifElseClause`
|
||||||
|
fn find_preceding_and_handle_stmt_if_special_cases<'a>(
|
||||||
|
comment: &DecoratedComment<'a>,
|
||||||
|
) -> Option<(AnyNodeRef<'a>, AnyNodeRef<'a>)> {
|
||||||
|
if let (stmt_if @ AnyNodeRef::StmtIf(stmt_if_inner), Some(AnyNodeRef::ElifElseClause(..))) =
|
||||||
|
(comment.enclosing_node(), comment.following_node())
|
||||||
|
{
|
||||||
|
if let Some(preceding_node @ AnyNodeRef::ElifElseClause(..)) = comment.preceding_node() {
|
||||||
|
// We're already after and elif or else, defaults work
|
||||||
|
Some((preceding_node, last_child_in_body(preceding_node)?))
|
||||||
|
} else {
|
||||||
|
// Special case 1: The comment is between if body and an elif/else clause. We have
|
||||||
|
// to handle this separately since StmtIf spans the entire range, so it's not the
|
||||||
|
// preceding node
|
||||||
|
Some((
|
||||||
|
stmt_if,
|
||||||
|
AnyNodeRef::from(stmt_if_inner.body.last().unwrap()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else if let Some(preceding_node @ AnyNodeRef::StmtIf(stmt_if_inner)) =
|
||||||
|
comment.preceding_node()
|
||||||
|
{
|
||||||
|
if let Some(clause) = stmt_if_inner.elif_else_clauses.last() {
|
||||||
|
// Special case 2: We're after an if statement and need to narrow the preceding
|
||||||
|
// down to the elif/else clause
|
||||||
|
Some((clause.into(), last_child_in_body(clause.into())?))
|
||||||
|
} else {
|
||||||
|
// After an if without any elif/else, defaults work
|
||||||
|
Some((preceding_node, last_child_in_body(preceding_node)?))
|
||||||
|
}
|
||||||
|
} else if let Some(preceding_node) = comment.preceding_node() {
|
||||||
|
// The normal case
|
||||||
|
Some((preceding_node, last_child_in_body(preceding_node)?))
|
||||||
|
} else {
|
||||||
|
// Only do something if the preceding node has a body (has indented statements).
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles trailing comments at the end of a body block (or any other block that is indented).
|
/// Handles trailing comments at the end of a body block (or any other block that is indented).
|
||||||
/// ```python
|
/// ```python
|
||||||
/// def test():
|
/// def test():
|
||||||
|
@ -442,12 +514,9 @@ fn handle_trailing_body_comment<'a>(
|
||||||
return CommentPlacement::Default(comment);
|
return CommentPlacement::Default(comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only do something if the preceding node has a body (has indented statements).
|
let Some((preceding_node, last_child)) =
|
||||||
let Some(preceding_node) = comment.preceding_node() else {
|
find_preceding_and_handle_stmt_if_special_cases(&comment)
|
||||||
return CommentPlacement::Default(comment);
|
else {
|
||||||
};
|
|
||||||
|
|
||||||
let Some(last_child) = last_child_in_body(preceding_node) else {
|
|
||||||
return CommentPlacement::Default(comment);
|
return CommentPlacement::Default(comment);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -566,6 +635,22 @@ fn handle_trailing_end_of_line_body_comment<'a>(
|
||||||
return CommentPlacement::Default(comment);
|
return CommentPlacement::Default(comment);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle the StmtIf special case
|
||||||
|
// ```python
|
||||||
|
// if True:
|
||||||
|
// pass
|
||||||
|
// elif True:
|
||||||
|
// pass # 14 end-of-line trailing `pass` comment, set preceding to the ElifElseClause
|
||||||
|
// ```
|
||||||
|
let preceding = if let AnyNodeRef::StmtIf(stmt_if) = preceding {
|
||||||
|
stmt_if
|
||||||
|
.elif_else_clauses
|
||||||
|
.last()
|
||||||
|
.map_or(preceding, AnyNodeRef::from)
|
||||||
|
} else {
|
||||||
|
preceding
|
||||||
|
};
|
||||||
|
|
||||||
// Recursively get the last child of statements with a body.
|
// Recursively get the last child of statements with a body.
|
||||||
let last_children = std::iter::successors(last_child_in_body(preceding), |parent| {
|
let last_children = std::iter::successors(last_child_in_body(preceding), |parent| {
|
||||||
last_child_in_body(*parent)
|
last_child_in_body(*parent)
|
||||||
|
@ -600,20 +685,40 @@ fn handle_trailing_end_of_line_condition_comment<'a>(
|
||||||
return CommentPlacement::Default(comment);
|
return CommentPlacement::Default(comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We handle trailing else comments separately because we the preceding node is None for their
|
||||||
|
// case
|
||||||
|
// ```python
|
||||||
|
// if True:
|
||||||
|
// pass
|
||||||
|
// else: # 12 trailing else condition
|
||||||
|
// pass
|
||||||
|
// ```
|
||||||
|
if let AnyNodeRef::ElifElseClause(ast::ElifElseClause {
|
||||||
|
body, test: None, ..
|
||||||
|
}) = comment.enclosing_node()
|
||||||
|
{
|
||||||
|
if comment.start() < body.first().unwrap().start() {
|
||||||
|
return CommentPlacement::dangling(comment.enclosing_node(), comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Must be between the condition expression and the first body element
|
// Must be between the condition expression and the first body element
|
||||||
let (Some(preceding), Some(following)) = (comment.preceding_node(), comment.following_node())
|
let (Some(preceding), Some(following)) = (comment.preceding_node(), comment.following_node())
|
||||||
else {
|
else {
|
||||||
return CommentPlacement::Default(comment);
|
return CommentPlacement::Default(comment);
|
||||||
};
|
};
|
||||||
|
|
||||||
let expression_before_colon = match comment.enclosing_node() {
|
let enclosing_node = comment.enclosing_node();
|
||||||
|
let expression_before_colon = match enclosing_node {
|
||||||
|
AnyNodeRef::ElifElseClause(ast::ElifElseClause {
|
||||||
|
test: Some(expr), ..
|
||||||
|
}) => Some(AnyNodeRef::from(expr)),
|
||||||
AnyNodeRef::StmtIf(ast::StmtIf { test: expr, .. })
|
AnyNodeRef::StmtIf(ast::StmtIf { test: expr, .. })
|
||||||
| AnyNodeRef::StmtWhile(ast::StmtWhile { test: expr, .. })
|
| AnyNodeRef::StmtWhile(ast::StmtWhile { test: expr, .. })
|
||||||
| AnyNodeRef::StmtFor(ast::StmtFor { iter: expr, .. })
|
| AnyNodeRef::StmtFor(ast::StmtFor { iter: expr, .. })
|
||||||
| AnyNodeRef::StmtAsyncFor(ast::StmtAsyncFor { iter: expr, .. }) => {
|
| AnyNodeRef::StmtAsyncFor(ast::StmtAsyncFor { iter: expr, .. }) => {
|
||||||
Some(AnyNodeRef::from(expr.as_ref()))
|
Some(AnyNodeRef::from(expr.as_ref()))
|
||||||
}
|
}
|
||||||
|
|
||||||
AnyNodeRef::StmtWith(ast::StmtWith { items, .. })
|
AnyNodeRef::StmtWith(ast::StmtWith { items, .. })
|
||||||
| AnyNodeRef::StmtAsyncWith(ast::StmtAsyncWith { items, .. }) => {
|
| AnyNodeRef::StmtAsyncWith(ast::StmtAsyncWith { items, .. }) => {
|
||||||
items.last().map(AnyNodeRef::from)
|
items.last().map(AnyNodeRef::from)
|
||||||
|
@ -656,7 +761,7 @@ fn handle_trailing_end_of_line_condition_comment<'a>(
|
||||||
// while a: # comment
|
// while a: # comment
|
||||||
// ...
|
// ...
|
||||||
// ```
|
// ```
|
||||||
return CommentPlacement::dangling(comment.enclosing_node(), comment);
|
return CommentPlacement::dangling(enclosing_node, comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment comes before the colon
|
// Comment comes before the colon
|
||||||
|
@ -1439,10 +1544,15 @@ fn last_child_in_body(node: AnyNodeRef) -> Option<AnyNodeRef> {
|
||||||
| AnyNodeRef::MatchCase(ast::MatchCase { body, .. })
|
| AnyNodeRef::MatchCase(ast::MatchCase { body, .. })
|
||||||
| AnyNodeRef::ExceptHandlerExceptHandler(ast::ExceptHandlerExceptHandler {
|
| AnyNodeRef::ExceptHandlerExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||||
body, ..
|
body, ..
|
||||||
}) => body,
|
})
|
||||||
|
| AnyNodeRef::ElifElseClause(ast::ElifElseClause { body, .. }) => body,
|
||||||
|
AnyNodeRef::StmtIf(ast::StmtIf {
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => elif_else_clauses.last().map_or(body, |clause| &clause.body),
|
||||||
|
|
||||||
AnyNodeRef::StmtIf(ast::StmtIf { body, orelse, .. })
|
AnyNodeRef::StmtFor(ast::StmtFor { body, orelse, .. })
|
||||||
| AnyNodeRef::StmtFor(ast::StmtFor { body, orelse, .. })
|
|
||||||
| AnyNodeRef::StmtAsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
| AnyNodeRef::StmtAsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
||||||
| AnyNodeRef::StmtWhile(ast::StmtWhile { body, orelse, .. }) => {
|
| AnyNodeRef::StmtWhile(ast::StmtWhile { body, orelse, .. }) => {
|
||||||
if orelse.is_empty() {
|
if orelse.is_empty() {
|
||||||
|
@ -1453,7 +1563,7 @@ fn last_child_in_body(node: AnyNodeRef) -> Option<AnyNodeRef> {
|
||||||
}
|
}
|
||||||
|
|
||||||
AnyNodeRef::StmtMatch(ast::StmtMatch { cases, .. }) => {
|
AnyNodeRef::StmtMatch(ast::StmtMatch { cases, .. }) => {
|
||||||
return cases.last().map(AnyNodeRef::from)
|
return cases.last().map(AnyNodeRef::from);
|
||||||
}
|
}
|
||||||
|
|
||||||
AnyNodeRef::StmtTry(ast::StmtTry {
|
AnyNodeRef::StmtTry(ast::StmtTry {
|
||||||
|
@ -1498,8 +1608,26 @@ fn is_first_statement_in_enclosing_alternate_body(
|
||||||
enclosing: AnyNodeRef,
|
enclosing: AnyNodeRef,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match enclosing {
|
match enclosing {
|
||||||
AnyNodeRef::StmtIf(ast::StmtIf { orelse, .. })
|
AnyNodeRef::StmtIf(ast::StmtIf {
|
||||||
| AnyNodeRef::StmtFor(ast::StmtFor { orelse, .. })
|
elif_else_clauses, ..
|
||||||
|
}) => {
|
||||||
|
for clause in elif_else_clauses {
|
||||||
|
if let Some(test) = &clause.test {
|
||||||
|
// `elif`, the following node is the test
|
||||||
|
if following.ptr_eq(test.into()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// `else`, there is no test and the following node is the first entry in the
|
||||||
|
// body
|
||||||
|
if following.ptr_eq(clause.body.first().unwrap().into()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
AnyNodeRef::StmtFor(ast::StmtFor { orelse, .. })
|
||||||
| AnyNodeRef::StmtAsyncFor(ast::StmtAsyncFor { orelse, .. })
|
| AnyNodeRef::StmtAsyncFor(ast::StmtAsyncFor { orelse, .. })
|
||||||
| AnyNodeRef::StmtWhile(ast::StmtWhile { orelse, .. }) => {
|
| AnyNodeRef::StmtWhile(ast::StmtWhile { orelse, .. }) => {
|
||||||
are_same_optional(following, orelse.first())
|
are_same_optional(following, orelse.first())
|
||||||
|
|
|
@ -34,8 +34,8 @@ expression: comments.debug(test_case.source_code)
|
||||||
"trailing": [],
|
"trailing": [],
|
||||||
},
|
},
|
||||||
Node {
|
Node {
|
||||||
kind: StmtIf,
|
kind: ElifElseClause,
|
||||||
range: 144..212,
|
range: 144..177,
|
||||||
source: `elif x < y:⏎`,
|
source: `elif x < y:⏎`,
|
||||||
}: {
|
}: {
|
||||||
"leading": [
|
"leading": [
|
||||||
|
|
|
@ -24,8 +24,8 @@ expression: comments.debug(test_case.source_code)
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Node {
|
Node {
|
||||||
kind: StmtIf,
|
kind: ElifElseClause,
|
||||||
range: 104..192,
|
range: 104..124,
|
||||||
source: `elif x < y:⏎`,
|
source: `elif x < y:⏎`,
|
||||||
}: {
|
}: {
|
||||||
"leading": [
|
"leading": [
|
||||||
|
@ -35,13 +35,7 @@ expression: comments.debug(test_case.source_code)
|
||||||
formatted: false,
|
formatted: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"dangling": [
|
"dangling": [],
|
||||||
SourceComment {
|
|
||||||
text: "# Leading else comment",
|
|
||||||
position: OwnLine,
|
|
||||||
formatted: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"trailing": [],
|
"trailing": [],
|
||||||
},
|
},
|
||||||
Node {
|
Node {
|
||||||
|
@ -59,6 +53,21 @@ expression: comments.debug(test_case.source_code)
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Node {
|
||||||
|
kind: ElifElseClause,
|
||||||
|
range: 178..192,
|
||||||
|
source: `else:⏎`,
|
||||||
|
}: {
|
||||||
|
"leading": [
|
||||||
|
SourceComment {
|
||||||
|
text: "# Leading else comment",
|
||||||
|
position: OwnLine,
|
||||||
|
formatted: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"dangling": [],
|
||||||
|
"trailing": [],
|
||||||
|
},
|
||||||
Node {
|
Node {
|
||||||
kind: StmtPass,
|
kind: StmtPass,
|
||||||
range: 188..192,
|
range: 188..192,
|
||||||
|
|
|
@ -3,21 +3,6 @@ source: crates/ruff_python_formatter/src/comments/mod.rs
|
||||||
expression: comments.debug(test_case.source_code)
|
expression: comments.debug(test_case.source_code)
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
Node {
|
|
||||||
kind: StmtIf,
|
|
||||||
range: 21..128,
|
|
||||||
source: `elif x < y:⏎`,
|
|
||||||
}: {
|
|
||||||
"leading": [],
|
|
||||||
"dangling": [
|
|
||||||
SourceComment {
|
|
||||||
text: "# Leading else comment",
|
|
||||||
position: OwnLine,
|
|
||||||
formatted: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"trailing": [],
|
|
||||||
},
|
|
||||||
Node {
|
Node {
|
||||||
kind: StmtIf,
|
kind: StmtIf,
|
||||||
range: 37..60,
|
range: 37..60,
|
||||||
|
@ -33,4 +18,19 @@ expression: comments.debug(test_case.source_code)
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Node {
|
||||||
|
kind: ElifElseClause,
|
||||||
|
range: 114..128,
|
||||||
|
source: `else:⏎`,
|
||||||
|
}: {
|
||||||
|
"leading": [
|
||||||
|
SourceComment {
|
||||||
|
text: "# Leading else comment",
|
||||||
|
position: OwnLine,
|
||||||
|
formatted: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"dangling": [],
|
||||||
|
"trailing": [],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ use std::iter::Peekable;
|
||||||
|
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
use rustpython_parser::ast::{
|
use rustpython_parser::ast::{
|
||||||
Alias, Arg, ArgWithDefault, Arguments, Comprehension, Decorator, ExceptHandler, Expr, Keyword,
|
Alias, Arg, ArgWithDefault, Arguments, Comprehension, Decorator, ElifElseClause, ExceptHandler,
|
||||||
MatchCase, Mod, Pattern, Ranged, Stmt, WithItem,
|
Expr, Keyword, MatchCase, Mod, Pattern, Ranged, Stmt, WithItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ruff_formatter::{SourceCode, SourceCodeSlice};
|
use ruff_formatter::{SourceCode, SourceCodeSlice};
|
||||||
|
@ -284,6 +284,13 @@ impl<'ast> PreorderVisitor<'ast> for CommentsVisitor<'ast> {
|
||||||
}
|
}
|
||||||
self.finish_node(pattern);
|
self.finish_node(pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_elif_else_clause(&mut self, elif_else_clause: &'ast ElifElseClause) {
|
||||||
|
if self.start_node(elif_else_clause).is_traverse() {
|
||||||
|
walk_elif_else_clause(self, elif_else_clause);
|
||||||
|
}
|
||||||
|
self.finish_node(elif_else_clause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_position(comment_range: TextRange, source_code: SourceCode) -> CommentLinePosition {
|
fn text_position(comment_range: TextRange, source_code: SourceCode) -> CommentLinePosition {
|
||||||
|
|
|
@ -617,6 +617,46 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::StmtIf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FormatRule<ast::ElifElseClause, PyFormatContext<'_>>
|
||||||
|
for crate::statement::stmt_if::FormatElifElseClause
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn fmt(
|
||||||
|
&self,
|
||||||
|
node: &ast::ElifElseClause,
|
||||||
|
f: &mut Formatter<PyFormatContext<'_>>,
|
||||||
|
) -> FormatResult<()> {
|
||||||
|
FormatNodeRule::<ast::ElifElseClause>::fmt(self, node, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'ast> AsFormat<PyFormatContext<'ast>> for ast::ElifElseClause {
|
||||||
|
type Format<'a> = FormatRefWithRule<
|
||||||
|
'a,
|
||||||
|
ast::ElifElseClause,
|
||||||
|
crate::statement::stmt_if::FormatElifElseClause,
|
||||||
|
PyFormatContext<'ast>,
|
||||||
|
>;
|
||||||
|
fn format(&self) -> Self::Format<'_> {
|
||||||
|
FormatRefWithRule::new(
|
||||||
|
self,
|
||||||
|
crate::statement::stmt_if::FormatElifElseClause::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::ElifElseClause {
|
||||||
|
type Format = FormatOwnedWithRule<
|
||||||
|
ast::ElifElseClause,
|
||||||
|
crate::statement::stmt_if::FormatElifElseClause,
|
||||||
|
PyFormatContext<'ast>,
|
||||||
|
>;
|
||||||
|
fn into_format(self) -> Self::Format {
|
||||||
|
FormatOwnedWithRule::new(
|
||||||
|
self,
|
||||||
|
crate::statement::stmt_if::FormatElifElseClause::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FormatRule<ast::StmtWith, PyFormatContext<'_>>
|
impl FormatRule<ast::StmtWith, PyFormatContext<'_>>
|
||||||
for crate::statement::stmt_with::FormatStmtWith
|
for crate::statement::stmt_with::FormatStmtWith
|
||||||
{
|
{
|
||||||
|
|
|
@ -64,6 +64,7 @@ impl FormatRule<Stmt, PyFormatContext<'_>> for FormatStmt {
|
||||||
Stmt::Pass(x) => x.format().fmt(f),
|
Stmt::Pass(x) => x.format().fmt(f),
|
||||||
Stmt::Break(x) => x.format().fmt(f),
|
Stmt::Break(x) => x.format().fmt(f),
|
||||||
Stmt::Continue(x) => x.format().fmt(f),
|
Stmt::Continue(x) => x.format().fmt(f),
|
||||||
|
Stmt::TypeAlias(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
|
||||||
bases,
|
bases,
|
||||||
keywords,
|
keywords,
|
||||||
body,
|
body,
|
||||||
|
type_params: _,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
|
|
|
@ -1,91 +1,43 @@
|
||||||
use crate::comments::{leading_alternate_branch_comments, trailing_comments, SourceComment};
|
use crate::comments::{leading_alternate_branch_comments, trailing_comments};
|
||||||
use crate::expression::maybe_parenthesize_expression;
|
use crate::expression::maybe_parenthesize_expression;
|
||||||
use crate::expression::parentheses::Parenthesize;
|
use crate::expression::parentheses::Parenthesize;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::FormatNodeRule;
|
use crate::FormatNodeRule;
|
||||||
use ruff_formatter::{write, FormatError};
|
use ruff_formatter::write;
|
||||||
use rustpython_parser::ast::{Ranged, Stmt, StmtIf, Suite};
|
use ruff_python_ast::node::AnyNodeRef;
|
||||||
|
use rustpython_parser::ast::{ElifElseClause, StmtIf};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FormatStmtIf;
|
pub struct FormatStmtIf;
|
||||||
|
|
||||||
impl FormatNodeRule<StmtIf> for FormatStmtIf {
|
impl FormatNodeRule<StmtIf> for FormatStmtIf {
|
||||||
fn fmt_fields(&self, item: &StmtIf, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt_fields(&self, item: &StmtIf, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
|
let StmtIf {
|
||||||
|
range: _,
|
||||||
|
test,
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
} = item;
|
||||||
|
|
||||||
let comments = f.context().comments().clone();
|
let comments = f.context().comments().clone();
|
||||||
|
let trailing_colon_comment = comments.dangling_comments(item);
|
||||||
|
|
||||||
let mut current = IfOrElIf::If(item);
|
write!(
|
||||||
let mut else_comments: &[SourceComment];
|
f,
|
||||||
let mut last_node_of_previous_body = None;
|
[
|
||||||
|
text("if"),
|
||||||
|
space(),
|
||||||
|
maybe_parenthesize_expression(test, item, Parenthesize::IfBreaks),
|
||||||
|
text(":"),
|
||||||
|
trailing_comments(trailing_colon_comment),
|
||||||
|
block_indent(&body.format())
|
||||||
|
]
|
||||||
|
)?;
|
||||||
|
|
||||||
loop {
|
let mut last_node = body.last().unwrap().into();
|
||||||
let current_statement = current.statement();
|
for clause in elif_else_clauses {
|
||||||
let StmtIf {
|
format_elif_else_clause(clause, f, Some(last_node))?;
|
||||||
test, body, orelse, ..
|
last_node = clause.body.last().unwrap().into();
|
||||||
} = current_statement;
|
|
||||||
|
|
||||||
let first_statement = body.first().ok_or(FormatError::SyntaxError)?;
|
|
||||||
let trailing = comments.dangling_comments(current_statement);
|
|
||||||
|
|
||||||
let trailing_if_comments_end = trailing
|
|
||||||
.partition_point(|comment| comment.slice().start() < first_statement.start());
|
|
||||||
|
|
||||||
let (if_trailing_comments, trailing_alternate_comments) =
|
|
||||||
trailing.split_at(trailing_if_comments_end);
|
|
||||||
|
|
||||||
if current.is_elif() {
|
|
||||||
let elif_leading = comments.leading_comments(current_statement);
|
|
||||||
// Manually format the leading comments because the formatting bypasses `NodeRule::fmt`
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
[
|
|
||||||
leading_alternate_branch_comments(elif_leading, last_node_of_previous_body),
|
|
||||||
source_position(current_statement.start())
|
|
||||||
]
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
[
|
|
||||||
text(current.keyword()),
|
|
||||||
space(),
|
|
||||||
maybe_parenthesize_expression(test, current_statement, Parenthesize::IfBreaks),
|
|
||||||
text(":"),
|
|
||||||
trailing_comments(if_trailing_comments),
|
|
||||||
block_indent(&body.format())
|
|
||||||
]
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// RustPython models `elif` by setting the body to a single `if` statement. The `orelse`
|
|
||||||
// of the most inner `if` statement then becomes the `else` of the whole `if` chain.
|
|
||||||
// That's why it's necessary to take the comments here from the most inner `elif`.
|
|
||||||
else_comments = trailing_alternate_comments;
|
|
||||||
last_node_of_previous_body = body.last();
|
|
||||||
|
|
||||||
if let Some(elif) = else_if(orelse) {
|
|
||||||
current = elif;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let orelse = ¤t.statement().orelse;
|
|
||||||
|
|
||||||
if !orelse.is_empty() {
|
|
||||||
// Leading comments are always own line comments
|
|
||||||
let leading_else_comments_end =
|
|
||||||
else_comments.partition_point(|comment| comment.line_position().is_own_line());
|
|
||||||
let (else_leading, else_trailing) = else_comments.split_at(leading_else_comments_end);
|
|
||||||
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
[
|
|
||||||
leading_alternate_branch_comments(else_leading, last_node_of_previous_body),
|
|
||||||
text("else:"),
|
|
||||||
trailing_comments(else_trailing),
|
|
||||||
block_indent(&orelse.format())
|
|
||||||
]
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -97,35 +49,56 @@ impl FormatNodeRule<StmtIf> for FormatStmtIf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn else_if(or_else: &Suite) -> Option<IfOrElIf> {
|
/// Note that this implementation misses the leading newlines before the leading comments because
|
||||||
if let [Stmt::If(if_stmt)] = or_else.as_slice() {
|
/// it does not have access to the last node of the previous branch. The `StmtIf` therefore doesn't
|
||||||
Some(IfOrElIf::ElIf(if_stmt))
|
/// call this but `format_elif_else_clause` directly.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FormatElifElseClause;
|
||||||
|
|
||||||
|
impl FormatNodeRule<ElifElseClause> for FormatElifElseClause {
|
||||||
|
fn fmt_fields(&self, item: &ElifElseClause, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
|
format_elif_else_clause(item, f, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracted so we can implement `FormatElifElseClause` but also pass in `last_node` from
|
||||||
|
/// `FormatStmtIf`
|
||||||
|
fn format_elif_else_clause(
|
||||||
|
item: &ElifElseClause,
|
||||||
|
f: &mut PyFormatter,
|
||||||
|
last_node: Option<AnyNodeRef>,
|
||||||
|
) -> FormatResult<()> {
|
||||||
|
let ElifElseClause {
|
||||||
|
range: _,
|
||||||
|
test,
|
||||||
|
body,
|
||||||
|
} = item;
|
||||||
|
|
||||||
|
let comments = f.context().comments().clone();
|
||||||
|
let trailing_colon_comment = comments.dangling_comments(item);
|
||||||
|
let leading_comments = comments.leading_comments(item);
|
||||||
|
|
||||||
|
leading_alternate_branch_comments(leading_comments, last_node).fmt(f)?;
|
||||||
|
|
||||||
|
if let Some(test) = test {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
text("elif"),
|
||||||
|
space(),
|
||||||
|
maybe_parenthesize_expression(test, item, Parenthesize::IfBreaks),
|
||||||
|
]
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
None
|
text("else").fmt(f)?;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum IfOrElIf<'a> {
|
|
||||||
If(&'a StmtIf),
|
|
||||||
ElIf(&'a StmtIf),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IfOrElIf<'a> {
|
|
||||||
const fn statement(&self) -> &'a StmtIf {
|
|
||||||
match self {
|
|
||||||
IfOrElIf::If(statement) => statement,
|
|
||||||
IfOrElIf::ElIf(statement) => statement,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn keyword(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
IfOrElIf::If(_) => "if",
|
|
||||||
IfOrElIf::ElIf(_) => "elif",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn is_elif(&self) -> bool {
|
|
||||||
matches!(self, IfOrElIf::ElIf(_))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
text(":"),
|
||||||
|
trailing_comments(trailing_colon_comment),
|
||||||
|
block_indent(&body.format())
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,7 +353,7 @@ else:
|
||||||
if True:
|
if True:
|
||||||
def f2():
|
def f2():
|
||||||
pass
|
pass
|
||||||
# a
|
# a
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,23 @@ input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/
|
||||||
---
|
---
|
||||||
## Input
|
## Input
|
||||||
```py
|
```py
|
||||||
if x == y: # trailing if condition
|
# 1 leading if comment
|
||||||
pass # trailing `pass` comment
|
if x == y: # 2 trailing if condition
|
||||||
# Root `if` trailing comment
|
# 3 leading pass
|
||||||
|
pass # 4 end-of-line trailing `pass` comment
|
||||||
|
# 5 Root `if` trailing comment
|
||||||
|
|
||||||
# Leading elif comment
|
# 6 Leading elif comment
|
||||||
elif x < y: # trailing elif condition
|
elif x < y: # 7 trailing elif condition
|
||||||
pass
|
# 8 leading pass
|
||||||
# `elif` trailing comment
|
pass # 9 end-of-line trailing `pass` comment
|
||||||
|
# 10 `elif` trailing comment
|
||||||
|
|
||||||
# Leading else comment
|
# 11 Leading else comment
|
||||||
else: # trailing else condition
|
else: # 12 trailing else condition
|
||||||
pass
|
# 13 leading pass
|
||||||
# `else` trailing comment
|
pass # 14 end-of-line trailing `pass` comment
|
||||||
|
# 15 `else` trailing comment
|
||||||
|
|
||||||
|
|
||||||
if x == y:
|
if x == y:
|
||||||
|
@ -77,23 +81,38 @@ else: # Comment
|
||||||
if False:
|
if False:
|
||||||
pass
|
pass
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Regression test for `last_child_in_body` special casing of `StmtIf`
|
||||||
|
# https://github.com/python/cpython/blob/aecf6aca515a203a823a87c711f15cbb82097c8b/Lib/test/test_pty.py#L260-L275
|
||||||
|
def f():
|
||||||
|
if True:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# comment
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
```py
|
```py
|
||||||
if x == y: # trailing if condition
|
# 1 leading if comment
|
||||||
pass # trailing `pass` comment
|
if x == y: # 2 trailing if condition
|
||||||
# Root `if` trailing comment
|
# 3 leading pass
|
||||||
|
pass # 4 end-of-line trailing `pass` comment
|
||||||
|
# 5 Root `if` trailing comment
|
||||||
|
|
||||||
# Leading elif comment
|
# 6 Leading elif comment
|
||||||
elif x < y: # trailing elif condition
|
elif x < y: # 7 trailing elif condition
|
||||||
pass
|
# 8 leading pass
|
||||||
# `elif` trailing comment
|
pass # 9 end-of-line trailing `pass` comment
|
||||||
|
# 10 `elif` trailing comment
|
||||||
|
|
||||||
# Leading else comment
|
# 11 Leading else comment
|
||||||
else: # trailing else condition
|
else: # 12 trailing else condition
|
||||||
pass
|
# 13 leading pass
|
||||||
# `else` trailing comment
|
pass # 14 end-of-line trailing `pass` comment
|
||||||
|
# 15 `else` trailing comment
|
||||||
|
|
||||||
|
|
||||||
if x == y:
|
if x == y:
|
||||||
|
@ -153,6 +172,17 @@ else: # Comment
|
||||||
if False:
|
if False:
|
||||||
pass
|
pass
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Regression test for `last_child_in_body` special casing of `StmtIf`
|
||||||
|
# https://github.com/python/cpython/blob/aecf6aca515a203a823a87c711f15cbb82097c8b/Lib/test/test_pty.py#L260-L275
|
||||||
|
def f():
|
||||||
|
if True:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# comment
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
use rustpython_parser::ast::{self, ExceptHandler, Stmt};
|
use rustpython_parser::ast::{self, ExceptHandler, Stmt};
|
||||||
|
|
||||||
|
@ -42,7 +43,17 @@ fn common_ancestor(
|
||||||
/// Return the alternative branches for a given node.
|
/// Return the alternative branches for a given node.
|
||||||
fn alternatives(stmt: &Stmt) -> Vec<Vec<&Stmt>> {
|
fn alternatives(stmt: &Stmt) -> Vec<Vec<&Stmt>> {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::If(ast::StmtIf { body, .. }) => vec![body.iter().collect()],
|
Stmt::If(ast::StmtIf {
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => iter::once(body.iter().collect())
|
||||||
|
.chain(
|
||||||
|
elif_else_clauses
|
||||||
|
.iter()
|
||||||
|
.map(|clause| clause.body.iter().collect()),
|
||||||
|
)
|
||||||
|
.collect(),
|
||||||
Stmt::Try(ast::StmtTry {
|
Stmt::Try(ast::StmtTry {
|
||||||
body,
|
body,
|
||||||
handlers,
|
handlers,
|
||||||
|
|
|
@ -29,11 +29,11 @@ fn empty_config() {
|
||||||
message: "If test is a tuple, which is always `True`".to_string(),
|
message: "If test is a tuple, which is always `True`".to_string(),
|
||||||
location: SourceLocation {
|
location: SourceLocation {
|
||||||
row: OneIndexed::from_zero_indexed(0),
|
row: OneIndexed::from_zero_indexed(0),
|
||||||
column: OneIndexed::from_zero_indexed(0)
|
column: OneIndexed::from_zero_indexed(3)
|
||||||
},
|
},
|
||||||
end_location: SourceLocation {
|
end_location: SourceLocation {
|
||||||
row: OneIndexed::from_zero_indexed(1),
|
row: OneIndexed::from_zero_indexed(0),
|
||||||
column: OneIndexed::from_zero_indexed(8)
|
column: OneIndexed::from_zero_indexed(9)
|
||||||
},
|
},
|
||||||
fix: None,
|
fix: None,
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -24,8 +24,8 @@ ruff_python_ast = { path = "../crates/ruff_python_ast" }
|
||||||
ruff_python_formatter = { path = "../crates/ruff_python_formatter" }
|
ruff_python_formatter = { path = "../crates/ruff_python_formatter" }
|
||||||
similar = { version = "2.2.1" }
|
similar = { version = "2.2.1" }
|
||||||
|
|
||||||
# Current tag: v0.0.7
|
# Current tag: v0.0.9
|
||||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "b996b21ffca562ecb2086f632a6a0b05c245c24a" , default-features = false, features = ["full-lexer", "num-bigint"] }
|
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "db04fd415774032e1e2ceb03bcbf5305e0d22c8c" , default-features = false, features = ["full-lexer", "num-bigint"] }
|
||||||
|
|
||||||
# Prevent this from interfering with workspaces
|
# Prevent this from interfering with workspaces
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue