Pyupgrade: Extraneous parenthesis (#1926)

This commit is contained in:
Colin Delahunty
2023-01-20 00:04:07 -05:00
committed by GitHub
parent cf56955ba6
commit 81db00a3c4
10 changed files with 421 additions and 11 deletions

View File

@@ -54,6 +54,7 @@ mod tests {
#[test_case(Rule::FormatLiterals, Path::new("UP030_1.py"); "UP030_1")]
#[test_case(Rule::FString, Path::new("UP032.py"); "UP032")]
#[test_case(Rule::FunctoolsCache, Path::new("UP033.py"); "UP033")]
#[test_case(Rule::ExtraneousParentheses, Path::new("UP034.py"); "UP034")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -0,0 +1,143 @@
use rustpython_parser::lexer::{LexResult, Tok};
use crate::ast::types::Range;
use crate::fix::Fix;
use crate::registry::{Diagnostic, Rule};
use crate::settings::{flags, Settings};
use crate::source_code::Locator;
use crate::violations;
// See: https://github.com/asottile/pyupgrade/blob/97ed6fb3cf2e650d4f762ba231c3f04c41797710/pyupgrade/_main.py#L148
fn match_extraneous_parentheses(tokens: &[LexResult], mut i: usize) -> Option<(usize, usize)> {
i += 1;
loop {
if i >= tokens.len() {
return None;
}
let Ok((_, tok, _)) = &tokens[i] else {
return None;
};
match tok {
Tok::Comment(..) | Tok::NonLogicalNewline => {
i += 1;
}
Tok::Lpar => {
break;
}
_ => {
return None;
}
}
}
// Store the location of the extraneous opening parenthesis.
let start = i;
// Verify that we're not in a tuple or coroutine.
let mut depth = 1;
while depth > 0 {
i += 1;
if i >= tokens.len() {
return None;
}
let Ok((_, tok, _)) = &tokens[i] else {
return None;
};
// If we find a comma or a yield at depth 1 or 2, it's a tuple or coroutine.
if depth == 1 && matches!(tok, Tok::Comma | Tok::Yield) {
return None;
} else if matches!(tok, Tok::Lpar | Tok::Lbrace | Tok::Lsqb) {
depth += 1;
} else if matches!(tok, Tok::Rpar | Tok::Rbrace | Tok::Rsqb) {
depth -= 1;
}
}
// Store the location of the extraneous closing parenthesis.
let end = i;
// Verify that we're not in an empty tuple.
if (start + 1..i).all(|i| {
matches!(
tokens[i],
Ok((_, Tok::Comment(..) | Tok::NonLogicalNewline, _))
)
}) {
return None;
}
// Find the next non-coding token.
i += 1;
loop {
if i >= tokens.len() {
return None;
}
let Ok((_, tok, _)) = &tokens[i] else {
return None;
};
match tok {
Tok::Comment(..) | Tok::NonLogicalNewline => {
i += 1;
}
_ => {
break;
}
}
}
if i >= tokens.len() {
return None;
}
let Ok((_, tok, _)) = &tokens[i] else {
return None;
};
if matches!(tok, Tok::Rpar) {
Some((start, end))
} else {
None
}
}
/// UP034
pub fn extraneous_parentheses(
tokens: &[LexResult],
locator: &Locator,
settings: &Settings,
autofix: flags::Autofix,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
let mut i = 0;
while i < tokens.len() {
if matches!(tokens[i], Ok((_, Tok::Lpar, _))) {
if let Some((start, end)) = match_extraneous_parentheses(tokens, i) {
i = end + 1;
let Ok((start, ..)) = &tokens[start] else {
return diagnostics;
};
let Ok((.., end)) = &tokens[end] else {
return diagnostics;
};
let mut diagnostic =
Diagnostic::new(violations::ExtraneousParentheses, Range::new(*start, *end));
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::ExtraneousParentheses)
{
let contents = locator.slice_source_code_range(&Range::new(*start, *end));
diagnostic.amend(Fix::replacement(
contents[1..contents.len() - 1].to_string(),
*start,
*end,
));
}
diagnostics.push(diagnostic);
} else {
i += 1;
}
} else {
i += 1;
}
}
diagnostics
}

View File

@@ -2,6 +2,7 @@ pub(crate) use convert_named_tuple_functional_to_class::convert_named_tuple_func
pub(crate) use convert_typed_dict_functional_to_class::convert_typed_dict_functional_to_class;
pub(crate) use datetime_utc_alias::datetime_utc_alias;
pub(crate) use deprecated_unittest_alias::deprecated_unittest_alias;
pub(crate) use extraneous_parentheses::extraneous_parentheses;
pub(crate) use f_strings::f_strings;
pub(crate) use format_literals::format_literals;
pub(crate) use functools_cache::functools_cache;
@@ -43,6 +44,7 @@ mod convert_named_tuple_functional_to_class;
mod convert_typed_dict_functional_to_class;
mod datetime_utc_alias;
mod deprecated_unittest_alias;
mod extraneous_parentheses;
mod f_strings;
mod format_literals;
mod functools_cache;

View File

@@ -0,0 +1,175 @@
---
source: src/rules/pyupgrade/mod.rs
expression: diagnostics
---
- kind:
ExtraneousParentheses: ~
location:
row: 2
column: 6
end_location:
row: 2
column: 13
fix:
content: "\"foo\""
location:
row: 2
column: 6
end_location:
row: 2
column: 13
parent: ~
- kind:
ExtraneousParentheses: ~
location:
row: 5
column: 6
end_location:
row: 5
column: 26
fix:
content: "\"hell((goodybe))o\""
location:
row: 5
column: 6
end_location:
row: 5
column: 26
parent: ~
- kind:
ExtraneousParentheses: ~
location:
row: 8
column: 6
end_location:
row: 8
column: 15
fix:
content: "(\"foo\")"
location:
row: 8
column: 6
end_location:
row: 8
column: 15
parent: ~
- kind:
ExtraneousParentheses: ~
location:
row: 11
column: 6
end_location:
row: 11
column: 13
fix:
content: ((1))
location:
row: 11
column: 6
end_location:
row: 11
column: 13
parent: ~
- kind:
ExtraneousParentheses: ~
location:
row: 14
column: 6
end_location:
row: 14
column: 25
fix:
content: "\"foo{}\".format(1)"
location:
row: 14
column: 6
end_location:
row: 14
column: 25
parent: ~
- kind:
ExtraneousParentheses: ~
location:
row: 18
column: 4
end_location:
row: 18
column: 23
fix:
content: "\"foo{}\".format(1)"
location:
row: 18
column: 4
end_location:
row: 18
column: 23
parent: ~
- kind:
ExtraneousParentheses: ~
location:
row: 23
column: 4
end_location:
row: 25
column: 5
fix:
content: "\n \"foo\"\n "
location:
row: 23
column: 4
end_location:
row: 25
column: 5
parent: ~
- kind:
ExtraneousParentheses: ~
location:
row: 30
column: 12
end_location:
row: 30
column: 23
fix:
content: (yield 1)
location:
row: 30
column: 12
end_location:
row: 30
column: 23
parent: ~
- kind:
ExtraneousParentheses: ~
location:
row: 35
column: 8
end_location:
row: 35
column: 27
fix:
content: "\"foo{}\".format(1)"
location:
row: 35
column: 8
end_location:
row: 35
column: 27
parent: ~
- kind:
ExtraneousParentheses: ~
location:
row: 39
column: 6
end_location:
row: 39
column: 27
fix:
content: x for x in range(3)
location:
row: 39
column: 6
end_location:
row: 39
column: 27
parent: ~