refactor: use remove_argument helper in pyupgrade

This commit is contained in:
Simon Brugman 2023-01-27 18:49:40 +01:00 committed by Charlie Marsh
parent d76a47d366
commit 63fc912ed8
9 changed files with 521 additions and 271 deletions

View file

@ -14,3 +14,60 @@ def ok_other_scope():
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def error(): def error():
... ...
@pytest.fixture(scope="function", name="my_fixture")
def error_multiple_args():
...
@pytest.fixture(name="my_fixture", scope="function")
def error_multiple_args():
...
@pytest.fixture(name="my_fixture", scope="function", **kwargs)
def error_second_arg():
...
# pytest.fixture does not take positional arguments, however this
# tests the general case as we use a helper function that should
# work for all cases.
@pytest.fixture("my_fixture", scope="function")
def error_arg():
...
@pytest.fixture(
scope="function",
name="my_fixture",
)
def error_multiple_args():
...
@pytest.fixture(
name="my_fixture",
scope="function",
)
def error_multiple_args():
...
@pytest.fixture(
"hello",
name,
*args
,
# another comment ,)
scope=\
"function" # some comment ),
,
name2=name, name3="my_fixture", **kwargs
)
def error_multiple_args():
...

View file

@ -3,7 +3,9 @@ use itertools::Itertools;
use libcst_native::{ use libcst_native::{
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement, Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
}; };
use rustpython_parser::ast::{ExcepthandlerKind, Location, Stmt, StmtKind}; use rustpython_parser::ast::{ExcepthandlerKind, Expr, Keyword, Location, Stmt, StmtKind};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use crate::ast::helpers; use crate::ast::helpers;
use crate::ast::helpers::to_absolute; use crate::ast::helpers::to_absolute;
@ -321,6 +323,108 @@ pub fn remove_unused_imports<'a>(
} }
} }
/// Generic function te remove (keyword)arguments in function calls
/// and class definitions. (For classes `args` should be considered `bases`)
///
/// Supports the removal of parentheses when this is the only (kw)arg left.
/// For this behaviour set `remove_parentheses` to `true`.
pub fn remove_argument(
locator: &Locator,
stmt_at: Location,
expr_at: Location,
expr_end: Location,
args: &[Expr],
keywords: &[Keyword],
remove_parentheses: bool,
) -> Result<Fix> {
// TODO: preserve trailing comments
let contents = locator.slice_source_code_at(stmt_at);
let mut fix_start = None;
let mut fix_end = None;
let n_keywords = keywords.len() + args.len();
if n_keywords == 0 {
bail!("No arguments or keywords to remove");
}
if n_keywords == 1 {
// Case 1: there is only one argument.
let mut count: usize = 0;
for (start, tok, end) in lexer::make_tokenizer_located(contents, stmt_at).flatten() {
if matches!(tok, Tok::Lpar) {
if count == 0 {
fix_start = Some(if remove_parentheses {
start
} else {
Location::new(start.row(), start.column() + 1)
})
}
count += 1;
}
if matches!(tok, Tok::Rpar) {
count -= 1;
if count == 0 {
fix_end = Some(if remove_parentheses {
end
} else {
Location::new(end.row(), end.column() - 1)
});
break;
}
}
}
} else if args
.iter()
.map(|node| node.location)
.chain(keywords.iter().map(|node| node.location))
.any(|location| location > expr_at)
{
// Case 2: argument or keyword is _not_ the last node.
let mut seen_comma = false;
for (start, tok, end) in lexer::make_tokenizer_located(contents, stmt_at).flatten() {
if seen_comma {
if matches!(tok, Tok::NonLogicalNewline) {
// Also delete any non-logical newlines after the comma.
continue;
}
fix_end = Some(if matches!(tok, Tok::Newline) {
end
} else {
start
});
break;
}
if start == expr_at {
fix_start = Some(start);
}
if fix_start.is_some() && matches!(tok, Tok::Comma) {
seen_comma = true;
}
}
} else {
// Case 3: argument or keyword is the last node, so we have to find the last
// comma in the stmt.
for (start, tok, _) in lexer::make_tokenizer_located(contents, stmt_at).flatten() {
if start == expr_at {
fix_end = Some(expr_end);
break;
}
if matches!(tok, Tok::Comma) {
fix_start = Some(start);
}
}
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Ok(Fix::deletion(start, end)),
_ => {
bail!("No fix could be constructed")
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use anyhow::Result; use anyhow::Result;

View file

@ -386,11 +386,11 @@ ruff_macros::define_rule_mapping!(
EM102 => violations::FStringInException, EM102 => violations::FStringInException,
EM103 => violations::DotFormatInException, EM103 => violations::DotFormatInException,
// flake8-pytest-style // flake8-pytest-style
PT001 => violations::IncorrectFixtureParenthesesStyle, PT001 => rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
PT002 => violations::FixturePositionalArgs, PT002 => rules::flake8_pytest_style::rules::FixturePositionalArgs,
PT003 => violations::ExtraneousScopeFunction, PT003 => rules::flake8_pytest_style::rules::ExtraneousScopeFunction,
PT004 => violations::MissingFixtureNameUnderscore, PT004 => rules::flake8_pytest_style::rules::MissingFixtureNameUnderscore,
PT005 => violations::IncorrectFixtureNameUnderscore, PT005 => rules::flake8_pytest_style::rules::IncorrectFixtureNameUnderscore,
PT006 => violations::ParametrizeNamesWrongType, PT006 => violations::ParametrizeNamesWrongType,
PT007 => violations::ParametrizeValuesWrongType, PT007 => violations::ParametrizeValuesWrongType,
PT008 => violations::PatchWithLambda, PT008 => violations::PatchWithLambda,
@ -403,13 +403,13 @@ ruff_macros::define_rule_mapping!(
PT016 => violations::FailWithoutMessage, PT016 => violations::FailWithoutMessage,
PT017 => violations::AssertInExcept, PT017 => violations::AssertInExcept,
PT018 => violations::CompositeAssertion, PT018 => violations::CompositeAssertion,
PT019 => violations::FixtureParamWithoutValue, PT019 => rules::flake8_pytest_style::rules::FixtureParamWithoutValue,
PT020 => violations::DeprecatedYieldFixture, PT020 => rules::flake8_pytest_style::rules::DeprecatedYieldFixture,
PT021 => violations::FixtureFinalizerCallback, PT021 => rules::flake8_pytest_style::rules::FixtureFinalizerCallback,
PT022 => violations::UselessYieldFixture, PT022 => rules::flake8_pytest_style::rules::UselessYieldFixture,
PT023 => violations::IncorrectMarkParenthesesStyle, PT023 => violations::IncorrectMarkParenthesesStyle,
PT024 => violations::UnnecessaryAsyncioMarkOnFixture, PT024 => rules::flake8_pytest_style::rules::UnnecessaryAsyncioMarkOnFixture,
PT025 => violations::ErroneousUseFixturesOnFixture, PT025 => rules::flake8_pytest_style::rules::ErroneousUseFixturesOnFixture,
PT026 => violations::UseFixturesWithoutParameters, PT026 => violations::UseFixturesWithoutParameters,
// flake8-pie // flake8-pie
PIE790 => rules::flake8_pie::rules::NoUnnecessaryPass, PIE790 => rules::flake8_pie::rules::NoUnnecessaryPass,

View file

@ -1,4 +1,7 @@
use rustpython_ast::{Arguments, Expr, ExprKind, Location, Stmt, StmtKind}; use anyhow::Result;
use log::error;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Arguments, Expr, ExprKind, Keyword, Location, Stmt, StmtKind};
use super::helpers::{ use super::helpers::{
get_mark_decorators, get_mark_name, is_abstractmethod_decorator, is_pytest_fixture, get_mark_decorators, get_mark_name, is_abstractmethod_decorator, is_pytest_fixture,
@ -8,10 +11,164 @@ use crate::ast::helpers::{collect_arg_names, collect_call_path};
use crate::ast::types::Range; use crate::ast::types::Range;
use crate::ast::visitor; use crate::ast::visitor;
use crate::ast::visitor::Visitor; use crate::ast::visitor::Visitor;
use crate::autofix::helpers::remove_argument;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::fix::Fix; use crate::fix::Fix;
use crate::registry::{Diagnostic, Rule}; use crate::registry::{Diagnostic, Rule};
use crate::violations; use crate::source_code::Locator;
use crate::violation::{AlwaysAutofixableViolation, Violation};
define_violation!(
pub struct IncorrectFixtureParenthesesStyle {
pub expected_parens: String,
pub actual_parens: String,
}
);
impl AlwaysAutofixableViolation for IncorrectFixtureParenthesesStyle {
#[derive_message_formats]
fn message(&self) -> String {
let IncorrectFixtureParenthesesStyle {
expected_parens,
actual_parens,
} = self;
format!("Use `@pytest.fixture{expected_parens}` over `@pytest.fixture{actual_parens}`")
}
fn autofix_title(&self) -> String {
"Add/remove parentheses".to_string()
}
}
define_violation!(
pub struct FixturePositionalArgs {
pub function: String,
}
);
impl Violation for FixturePositionalArgs {
#[derive_message_formats]
fn message(&self) -> String {
let FixturePositionalArgs { function } = self;
format!("Configuration for fixture `{function}` specified via positional args, use kwargs")
}
}
define_violation!(
pub struct ExtraneousScopeFunction;
);
impl Violation for ExtraneousScopeFunction {
#[derive_message_formats]
fn message(&self) -> String {
format!("`scope='function'` is implied in `@pytest.fixture()`")
}
}
define_violation!(
pub struct MissingFixtureNameUnderscore {
pub function: String,
}
);
impl Violation for MissingFixtureNameUnderscore {
#[derive_message_formats]
fn message(&self) -> String {
let MissingFixtureNameUnderscore { function } = self;
format!("Fixture `{function}` does not return anything, add leading underscore")
}
}
define_violation!(
pub struct IncorrectFixtureNameUnderscore {
pub function: String,
}
);
impl Violation for IncorrectFixtureNameUnderscore {
#[derive_message_formats]
fn message(&self) -> String {
let IncorrectFixtureNameUnderscore { function } = self;
format!("Fixture `{function}` returns a value, remove leading underscore")
}
}
define_violation!(
pub struct FixtureParamWithoutValue {
pub name: String,
}
);
impl Violation for FixtureParamWithoutValue {
#[derive_message_formats]
fn message(&self) -> String {
let FixtureParamWithoutValue { name } = self;
format!(
"Fixture `{name}` without value is injected as parameter, use \
`@pytest.mark.usefixtures` instead"
)
}
}
define_violation!(
pub struct DeprecatedYieldFixture;
);
impl Violation for DeprecatedYieldFixture {
#[derive_message_formats]
fn message(&self) -> String {
format!("`@pytest.yield_fixture` is deprecated, use `@pytest.fixture`")
}
}
define_violation!(
pub struct FixtureFinalizerCallback;
);
impl Violation for FixtureFinalizerCallback {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `yield` instead of `request.addfinalizer`")
}
}
define_violation!(
pub struct UselessYieldFixture {
pub name: String,
}
);
impl AlwaysAutofixableViolation for UselessYieldFixture {
#[derive_message_formats]
fn message(&self) -> String {
let UselessYieldFixture { name } = self;
format!("No teardown in fixture `{name}`, use `return` instead of `yield`")
}
fn autofix_title(&self) -> String {
"Replace `yield` with `return`".to_string()
}
}
define_violation!(
pub struct ErroneousUseFixturesOnFixture;
);
impl AlwaysAutofixableViolation for ErroneousUseFixturesOnFixture {
#[derive_message_formats]
fn message(&self) -> String {
format!("`pytest.mark.usefixtures` has no effect on fixtures")
}
fn autofix_title(&self) -> String {
"Remove `pytest.mark.usefixtures`".to_string()
}
}
define_violation!(
pub struct UnnecessaryAsyncioMarkOnFixture;
);
impl AlwaysAutofixableViolation for UnnecessaryAsyncioMarkOnFixture {
#[derive_message_formats]
fn message(&self) -> String {
format!("`pytest.mark.asyncio` is unnecessary for fixtures")
}
fn autofix_title(&self) -> String {
"Remove `pytest.mark.asyncio`".to_string()
}
}
#[derive(Default)] #[derive(Default)]
/// Visitor that skips functions /// Visitor that skips functions
@ -80,7 +237,7 @@ fn pytest_fixture_parentheses(
actual: &str, actual: &str,
) { ) {
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
violations::IncorrectFixtureParenthesesStyle { IncorrectFixtureParenthesesStyle {
expected_parens: preferred.to_string(), expected_parens: preferred.to_string(),
actual_parens: actual.to_string(), actual_parens: actual.to_string(),
}, },
@ -92,6 +249,17 @@ fn pytest_fixture_parentheses(
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
pub fn fix_extraneous_scope_function(
locator: &Locator,
stmt_at: Location,
expr_at: Location,
expr_end: Location,
args: &[Expr],
keywords: &[Keyword],
) -> Result<Fix> {
remove_argument(locator, stmt_at, expr_at, expr_end, args, keywords, false)
}
/// PT001, PT002, PT003 /// PT001, PT002, PT003
fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &Expr) { fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &Expr) {
match &decorator.node { match &decorator.node {
@ -116,7 +284,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &E
if checker.settings.rules.enabled(&Rule::FixturePositionalArgs) && !args.is_empty() { if checker.settings.rules.enabled(&Rule::FixturePositionalArgs) && !args.is_empty() {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
violations::FixturePositionalArgs { FixturePositionalArgs {
function: func_name.to_string(), function: func_name.to_string(),
}, },
Range::from_located(decorator), Range::from_located(decorator),
@ -134,10 +302,26 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &E
if let Some(scope_keyword) = scope_keyword { if let Some(scope_keyword) = scope_keyword {
if keyword_is_literal(scope_keyword, "function") { if keyword_is_literal(scope_keyword, "function") {
checker.diagnostics.push(Diagnostic::new( let mut diagnostic = Diagnostic::new(
violations::ExtraneousScopeFunction, ExtraneousScopeFunction,
Range::from_located(scope_keyword), Range::from_located(scope_keyword),
)); );
if checker.patch(diagnostic.kind.rule()) {
match fix_extraneous_scope_function(
checker.locator,
decorator.location,
diagnostic.location,
diagnostic.end_location,
args,
keywords,
) {
Ok(fix) => {
diagnostic.amend(fix);
}
Err(e) => error!("Failed to generate fix: {e}"),
}
}
checker.diagnostics.push(diagnostic);
} }
} }
} }
@ -172,7 +356,7 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo
&& func_name.starts_with('_') && func_name.starts_with('_')
{ {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
violations::IncorrectFixtureNameUnderscore { IncorrectFixtureNameUnderscore {
function: func_name.to_string(), function: func_name.to_string(),
}, },
Range::from_located(func), Range::from_located(func),
@ -186,7 +370,7 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo
&& !func_name.starts_with('_') && !func_name.starts_with('_')
{ {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
violations::MissingFixtureNameUnderscore { MissingFixtureNameUnderscore {
function: func_name.to_string(), function: func_name.to_string(),
}, },
Range::from_located(func), Range::from_located(func),
@ -199,7 +383,7 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo
if let ExprKind::Yield { .. } = value.node { if let ExprKind::Yield { .. } = value.node {
if visitor.yield_statements.len() == 1 { if visitor.yield_statements.len() == 1 {
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
violations::UselessYieldFixture { UselessYieldFixture {
name: func_name.to_string(), name: func_name.to_string(),
}, },
Range::from_located(stmt), Range::from_located(stmt),
@ -228,7 +412,7 @@ fn check_test_function_args(checker: &mut Checker, args: &Arguments) {
let name = &arg.node.arg; let name = &arg.node.arg;
if name.starts_with('_') { if name.starts_with('_') {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
violations::FixtureParamWithoutValue { FixtureParamWithoutValue {
name: name.to_string(), name: name.to_string(),
}, },
Range::from_located(arg), Range::from_located(arg),
@ -241,7 +425,7 @@ fn check_test_function_args(checker: &mut Checker, args: &Arguments) {
fn check_fixture_decorator_name(checker: &mut Checker, decorator: &Expr) { fn check_fixture_decorator_name(checker: &mut Checker, decorator: &Expr) {
if is_pytest_yield_fixture(decorator, checker) { if is_pytest_yield_fixture(decorator, checker) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
violations::DeprecatedYieldFixture, DeprecatedYieldFixture,
Range::from_located(decorator), Range::from_located(decorator),
)); ));
} }
@ -261,7 +445,7 @@ fn check_fixture_addfinalizer(checker: &mut Checker, args: &Arguments, body: &[S
if let Some(addfinalizer) = visitor.addfinalizer_call { if let Some(addfinalizer) = visitor.addfinalizer_call {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
violations::FixtureFinalizerCallback, FixtureFinalizerCallback,
Range::from_located(addfinalizer), Range::from_located(addfinalizer),
)); ));
} }
@ -278,10 +462,8 @@ fn check_fixture_marks(checker: &mut Checker, decorators: &[Expr]) {
.enabled(&Rule::UnnecessaryAsyncioMarkOnFixture) .enabled(&Rule::UnnecessaryAsyncioMarkOnFixture)
{ {
if name == "asyncio" { if name == "asyncio" {
let mut diagnostic = Diagnostic::new( let mut diagnostic =
violations::UnnecessaryAsyncioMarkOnFixture, Diagnostic::new(UnnecessaryAsyncioMarkOnFixture, Range::from_located(mark));
Range::from_located(mark),
);
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
let start = Location::new(mark.location.row(), 0); let start = Location::new(mark.location.row(), 0);
let end = Location::new(mark.end_location.unwrap().row() + 1, 0); let end = Location::new(mark.end_location.unwrap().row() + 1, 0);
@ -297,10 +479,8 @@ fn check_fixture_marks(checker: &mut Checker, decorators: &[Expr]) {
.enabled(&Rule::ErroneousUseFixturesOnFixture) .enabled(&Rule::ErroneousUseFixturesOnFixture)
{ {
if name == "usefixtures" { if name == "usefixtures" {
let mut diagnostic = Diagnostic::new( let mut diagnostic =
violations::ErroneousUseFixturesOnFixture, Diagnostic::new(ErroneousUseFixturesOnFixture, Range::from_located(mark));
Range::from_located(mark),
);
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
let start = Location::new(mark.location.row(), 0); let start = Location::new(mark.location.row(), 0);
let end = Location::new(mark.end_location.unwrap().row() + 1, 0); let end = Location::new(mark.end_location.unwrap().row() + 1, 0);

View file

@ -2,7 +2,12 @@ pub use assertion::{
assert_falsy, assert_in_exception_handler, composite_condition, unittest_assertion, assert_falsy, assert_in_exception_handler, composite_condition, unittest_assertion,
}; };
pub use fail::fail_call; pub use fail::fail_call;
pub use fixture::fixture; pub use fixture::{
fixture, DeprecatedYieldFixture, ErroneousUseFixturesOnFixture, ExtraneousScopeFunction,
FixtureFinalizerCallback, FixtureParamWithoutValue, FixturePositionalArgs,
IncorrectFixtureNameUnderscore, IncorrectFixtureParenthesesStyle, MissingFixtureNameUnderscore,
UnnecessaryAsyncioMarkOnFixture, UselessYieldFixture,
};
pub use imports::{import, import_from}; pub use imports::{import, import_from};
pub use marks::marks; pub use marks::marks;
pub use parametrize::parametrize; pub use parametrize::parametrize;

View file

@ -10,6 +10,140 @@ expression: diagnostics
end_location: end_location:
row: 14 row: 14
column: 32 column: 32
fix: ~ fix:
content:
- ""
location:
row: 14
column: 16
end_location:
row: 14
column: 32
parent: ~
- kind:
ExtraneousScopeFunction: ~
location:
row: 19
column: 16
end_location:
row: 19
column: 32
fix:
content:
- ""
location:
row: 19
column: 16
end_location:
row: 19
column: 34
parent: ~
- kind:
ExtraneousScopeFunction: ~
location:
row: 24
column: 35
end_location:
row: 24
column: 51
fix:
content:
- ""
location:
row: 24
column: 33
end_location:
row: 24
column: 51
parent: ~
- kind:
ExtraneousScopeFunction: ~
location:
row: 29
column: 35
end_location:
row: 29
column: 51
fix:
content:
- ""
location:
row: 29
column: 35
end_location:
row: 29
column: 53
parent: ~
- kind:
ExtraneousScopeFunction: ~
location:
row: 37
column: 30
end_location:
row: 37
column: 46
fix:
content:
- ""
location:
row: 37
column: 28
end_location:
row: 37
column: 46
parent: ~
- kind:
ExtraneousScopeFunction: ~
location:
row: 43
column: 4
end_location:
row: 43
column: 20
fix:
content:
- ""
location:
row: 43
column: 4
end_location:
row: 44
column: 4
parent: ~
- kind:
ExtraneousScopeFunction: ~
location:
row: 52
column: 4
end_location:
row: 52
column: 20
fix:
content:
- ""
location:
row: 51
column: 21
end_location:
row: 52
column: 20
parent: ~
- kind:
ExtraneousScopeFunction: ~
location:
row: 66
column: 4
end_location:
row: 67
column: 18
fix:
content:
- ""
location:
row: 66
column: 4
end_location:
row: 70
column: 4
parent: ~ parent: ~

View file

@ -2,10 +2,9 @@ use libcst_native::{
Codegen, CodegenState, Expression, ParenthesizableWhitespace, SmallStatement, Statement, Codegen, CodegenState, Expression, ParenthesizableWhitespace, SmallStatement, Statement,
}; };
use rustpython_ast::{Expr, Keyword, Location}; use rustpython_ast::{Expr, Keyword, Location};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use crate::ast::types::Range; use crate::ast::types::Range;
use crate::autofix::helpers::remove_argument;
use crate::fix::Fix; use crate::fix::Fix;
use crate::source_code::{Locator, Stylist}; use crate::source_code::{Locator, Stylist};
@ -14,93 +13,14 @@ pub fn remove_class_def_base(
locator: &Locator, locator: &Locator,
stmt_at: Location, stmt_at: Location,
expr_at: Location, expr_at: Location,
expr_end: Location,
bases: &[Expr], bases: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
) -> Option<Fix> { ) -> Option<Fix> {
let contents = locator.slice_source_code_at(stmt_at); if let Ok(fix) = remove_argument(locator, stmt_at, expr_at, expr_end, bases, keywords, true) {
Some(fix)
// Case 1: `object` is the only base.
if bases.len() == 1 && keywords.is_empty() {
let mut fix_start = None;
let mut fix_end = None;
let mut count: usize = 0;
for (start, tok, end) in lexer::make_tokenizer_located(contents, stmt_at).flatten() {
if matches!(tok, Tok::Lpar) {
if count == 0 {
fix_start = Some(start);
}
count += 1;
}
if matches!(tok, Tok::Rpar) {
count -= 1;
if count == 0 {
fix_end = Some(end);
break;
}
}
}
return match (fix_start, fix_end) {
(Some(start), Some(end)) => Some(Fix::deletion(start, end)),
_ => None,
};
}
if bases
.iter()
.map(|node| node.location)
.chain(keywords.iter().map(|node| node.location))
.any(|location| location > expr_at)
{
// Case 2: `object` is _not_ the last node.
let mut fix_start: Option<Location> = None;
let mut fix_end: Option<Location> = None;
let mut seen_comma = false;
for (start, tok, end) in lexer::make_tokenizer_located(contents, stmt_at).flatten() {
if seen_comma {
if matches!(tok, Tok::NonLogicalNewline) {
// Also delete any non-logical newlines after the comma.
continue;
}
if matches!(tok, Tok::Newline) {
fix_end = Some(end);
} else {
fix_end = Some(start);
}
break;
}
if start == expr_at {
fix_start = Some(start);
}
if fix_start.is_some() && matches!(tok, Tok::Comma) {
seen_comma = true;
}
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Some(Fix::deletion(start, end)),
_ => None,
}
} else { } else {
// Case 3: `object` is the last node, so we have to find the last token that None
// isn't a comma.
let mut fix_start: Option<Location> = None;
let mut fix_end: Option<Location> = None;
for (start, tok, end) in lexer::make_tokenizer_located(contents, stmt_at).flatten() {
if start == expr_at {
fix_end = Some(end);
break;
}
if matches!(tok, Tok::Comma) {
fix_start = Some(start);
}
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Some(Fix::deletion(start, end)),
_ => None,
}
} }
} }

View file

@ -53,6 +53,7 @@ pub fn useless_object_inheritance(
checker.locator, checker.locator,
stmt.location, stmt.location,
diagnostic.location, diagnostic.location,
diagnostic.end_location,
bases, bases,
keywords, keywords,
) { ) {

View file

@ -4684,76 +4684,6 @@ impl Violation for DotFormatInException {
// flake8-pytest-style // flake8-pytest-style
define_violation!(
pub struct IncorrectFixtureParenthesesStyle {
pub expected_parens: String,
pub actual_parens: String,
}
);
impl AlwaysAutofixableViolation for IncorrectFixtureParenthesesStyle {
#[derive_message_formats]
fn message(&self) -> String {
let IncorrectFixtureParenthesesStyle {
expected_parens,
actual_parens,
} = self;
format!("Use `@pytest.fixture{expected_parens}` over `@pytest.fixture{actual_parens}`")
}
fn autofix_title(&self) -> String {
"Add/remove parentheses".to_string()
}
}
define_violation!(
pub struct FixturePositionalArgs {
pub function: String,
}
);
impl Violation for FixturePositionalArgs {
#[derive_message_formats]
fn message(&self) -> String {
let FixturePositionalArgs { function } = self;
format!("Configuration for fixture `{function}` specified via positional args, use kwargs")
}
}
define_violation!(
pub struct ExtraneousScopeFunction;
);
impl Violation for ExtraneousScopeFunction {
#[derive_message_formats]
fn message(&self) -> String {
format!("`scope='function'` is implied in `@pytest.fixture()`")
}
}
define_violation!(
pub struct MissingFixtureNameUnderscore {
pub function: String,
}
);
impl Violation for MissingFixtureNameUnderscore {
#[derive_message_formats]
fn message(&self) -> String {
let MissingFixtureNameUnderscore { function } = self;
format!("Fixture `{function}` does not return anything, add leading underscore")
}
}
define_violation!(
pub struct IncorrectFixtureNameUnderscore {
pub function: String,
}
);
impl Violation for IncorrectFixtureNameUnderscore {
#[derive_message_formats]
fn message(&self) -> String {
let IncorrectFixtureNameUnderscore { function } = self;
format!("Fixture `{function}` returns a value, remove leading underscore")
}
}
define_violation!( define_violation!(
pub struct ParametrizeNamesWrongType { pub struct ParametrizeNamesWrongType {
pub expected: ParametrizeNameType, pub expected: ParametrizeNameType,
@ -4905,59 +4835,6 @@ impl Violation for CompositeAssertion {
} }
} }
define_violation!(
pub struct FixtureParamWithoutValue {
pub name: String,
}
);
impl Violation for FixtureParamWithoutValue {
#[derive_message_formats]
fn message(&self) -> String {
let FixtureParamWithoutValue { name } = self;
format!(
"Fixture `{name}` without value is injected as parameter, use \
`@pytest.mark.usefixtures` instead"
)
}
}
define_violation!(
pub struct DeprecatedYieldFixture;
);
impl Violation for DeprecatedYieldFixture {
#[derive_message_formats]
fn message(&self) -> String {
format!("`@pytest.yield_fixture` is deprecated, use `@pytest.fixture`")
}
}
define_violation!(
pub struct FixtureFinalizerCallback;
);
impl Violation for FixtureFinalizerCallback {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `yield` instead of `request.addfinalizer`")
}
}
define_violation!(
pub struct UselessYieldFixture {
pub name: String,
}
);
impl AlwaysAutofixableViolation for UselessYieldFixture {
#[derive_message_formats]
fn message(&self) -> String {
let UselessYieldFixture { name } = self;
format!("No teardown in fixture `{name}`, use `return` instead of `yield`")
}
fn autofix_title(&self) -> String {
"Replace `yield` with `return`".to_string()
}
}
define_violation!( define_violation!(
pub struct IncorrectMarkParenthesesStyle { pub struct IncorrectMarkParenthesesStyle {
pub mark_name: String, pub mark_name: String,
@ -4984,34 +4861,6 @@ impl AlwaysAutofixableViolation for IncorrectMarkParenthesesStyle {
} }
} }
define_violation!(
pub struct UnnecessaryAsyncioMarkOnFixture;
);
impl AlwaysAutofixableViolation for UnnecessaryAsyncioMarkOnFixture {
#[derive_message_formats]
fn message(&self) -> String {
format!("`pytest.mark.asyncio` is unnecessary for fixtures")
}
fn autofix_title(&self) -> String {
"Remove `pytest.mark.asyncio`".to_string()
}
}
define_violation!(
pub struct ErroneousUseFixturesOnFixture;
);
impl AlwaysAutofixableViolation for ErroneousUseFixturesOnFixture {
#[derive_message_formats]
fn message(&self) -> String {
format!("`pytest.mark.usefixtures` has no effect on fixtures")
}
fn autofix_title(&self) -> String {
"Remove `pytest.mark.usefixtures`".to_string()
}
}
define_violation!( define_violation!(
pub struct UseFixturesWithoutParameters; pub struct UseFixturesWithoutParameters;
); );