Implement R002 (NoAssertEquals)

This commit is contained in:
Charlie Marsh 2022-09-03 22:53:59 -04:00
parent f902d25dc7
commit ca45952e33
9 changed files with 126 additions and 17 deletions

View File

@ -128,7 +128,8 @@ OPTIONS:
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | | F831 | DuplicateArgumentName | Duplicate argument name in function definition |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | | F841 | UnusedVariable | Local variable `...` is assigned to but never used |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | | F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` |
| R0205 | UselessObjectInheritance | Class `...` inherits from object | | R001 | UselessObjectInheritance | Class `...` inherits from object |
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead |
## Development ## Development

View File

@ -11,6 +11,7 @@ fn main() {
CheckKind::ImportStarUsage, CheckKind::ImportStarUsage,
CheckKind::LineTooLong, CheckKind::LineTooLong,
CheckKind::ModuleImportNotAtTopOfFile, CheckKind::ModuleImportNotAtTopOfFile,
CheckKind::NoAssertEquals,
CheckKind::RaiseNotImplemented, CheckKind::RaiseNotImplemented,
CheckKind::ReturnOutsideFunction, CheckKind::ReturnOutsideFunction,
CheckKind::UndefinedExport("...".to_string()), CheckKind::UndefinedExport("...".to_string()),

3
resources/test/fixtures/R002.py vendored Normal file
View File

@ -0,0 +1,3 @@
self.assertEquals (1, 2)
self.assertEquals(1, 2)
self.assertEqual(3, 4)

View File

@ -18,5 +18,6 @@ select = [
"F831", "F831",
"F841", "F841",
"F901", "F901",
"R0205", "R001",
"R002",
] ]

View File

@ -2,8 +2,8 @@ use std::collections::BTreeSet;
use std::path::Path; use std::path::Path;
use rustpython_parser::ast::{ use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Stmt, Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
StmtKind, Suite, Location, Stmt, StmtKind, Suite,
}; };
use rustpython_parser::parser; use rustpython_parser::parser;
@ -11,7 +11,7 @@ use crate::ast_ops::{
extract_all_names, Binding, BindingKind, Scope, ScopeKind, SourceCodeLocator, extract_all_names, Binding, BindingKind, Scope, ScopeKind, SourceCodeLocator,
}; };
use crate::builtins::{BUILTINS, MAGIC_GLOBALS}; use crate::builtins::{BUILTINS, MAGIC_GLOBALS};
use crate::checks::{Check, CheckCode, CheckKind}; use crate::checks::{Check, CheckCode, CheckKind, Fix};
use crate::settings::Settings; use crate::settings::Settings;
use crate::visitor::{walk_excepthandler, Visitor}; use crate::visitor::{walk_excepthandler, Visitor};
use crate::{autofix, fixer, visitor}; use crate::{autofix, fixer, visitor};
@ -132,7 +132,7 @@ impl Visitor for Checker<'_> {
decorator_list, decorator_list,
.. ..
} => { } => {
if self.settings.select.contains(&CheckCode::R0205) { if self.settings.select.contains(&CheckCode::R001) {
for expr in bases { for expr in bases {
if let ExprKind::Name { id, .. } = &expr.node { if let ExprKind::Name { id, .. } = &expr.node {
if id == "object" { if id == "object" {
@ -159,6 +159,7 @@ impl Visitor for Checker<'_> {
) { ) {
check.amend(fix); check.amend(fix);
} }
} else {
} }
self.checks.push(check); self.checks.push(check);
} }
@ -442,6 +443,37 @@ impl Visitor for Checker<'_> {
ExprContext::Store => self.handle_node_store(expr, parent), ExprContext::Store => self.handle_node_store(expr, parent),
ExprContext::Del => self.handle_node_delete(expr), ExprContext::Del => self.handle_node_delete(expr),
}, },
ExprKind::Call { func, .. } => {
if self.settings.select.contains(&CheckCode::R002) {
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if attr == "assertEquals" {
if let ExprKind::Name { id, .. } = &value.node {
if id == "self" {
let mut check =
Check::new(CheckKind::NoAssertEquals, expr.location);
if matches!(self.autofix, autofix::Mode::Generate)
|| matches!(self.autofix, autofix::Mode::Apply)
{
check.amend(Fix {
content: "assertEqual".to_string(),
start: Location::new(
func.location.row(),
func.location.column() + 1,
),
end: Location::new(
func.location.row(),
func.location.column() + 1 + "assertEquals".len(),
),
applied: false,
});
}
self.checks.push(check);
}
}
}
}
}
}
ExprKind::GeneratorExp { .. } ExprKind::GeneratorExp { .. }
| ExprKind::ListComp { .. } | ExprKind::ListComp { .. }
| ExprKind::DictComp { .. } | ExprKind::DictComp { .. }

View File

@ -24,7 +24,8 @@ pub enum CheckCode {
F831, F831,
F841, F841,
F901, F901,
R0205, R001,
R002,
} }
impl FromStr for CheckCode { impl FromStr for CheckCode {
@ -48,7 +49,8 @@ impl FromStr for CheckCode {
"F831" => Ok(CheckCode::F831), "F831" => Ok(CheckCode::F831),
"F841" => Ok(CheckCode::F841), "F841" => Ok(CheckCode::F841),
"F901" => Ok(CheckCode::F901), "F901" => Ok(CheckCode::F901),
"R0205" => Ok(CheckCode::R0205), "R001" => Ok(CheckCode::R001),
"R002" => Ok(CheckCode::R002),
_ => Err(anyhow::anyhow!("Unknown check code: {s}")), _ => Err(anyhow::anyhow!("Unknown check code: {s}")),
} }
} }
@ -68,12 +70,13 @@ impl CheckCode {
CheckCode::F706 => "F706", CheckCode::F706 => "F706",
CheckCode::F707 => "F707", CheckCode::F707 => "F707",
CheckCode::F821 => "F821", CheckCode::F821 => "F821",
CheckCode::F823 => "F823",
CheckCode::F822 => "F822", CheckCode::F822 => "F822",
CheckCode::F823 => "F823",
CheckCode::F831 => "F831", CheckCode::F831 => "F831",
CheckCode::F841 => "F841", CheckCode::F841 => "F841",
CheckCode::F901 => "F901", CheckCode::F901 => "F901",
CheckCode::R0205 => "R0205", CheckCode::R001 => "R001",
CheckCode::R002 => "R002",
} }
} }
@ -96,7 +99,8 @@ impl CheckCode {
CheckCode::F831 => &LintSource::AST, CheckCode::F831 => &LintSource::AST,
CheckCode::F841 => &LintSource::AST, CheckCode::F841 => &LintSource::AST,
CheckCode::F901 => &LintSource::AST, CheckCode::F901 => &LintSource::AST,
CheckCode::R0205 => &LintSource::AST, CheckCode::R001 => &LintSource::AST,
CheckCode::R002 => &LintSource::AST,
} }
} }
} }
@ -117,6 +121,7 @@ pub enum CheckKind {
ImportStarUsage, ImportStarUsage,
LineTooLong, LineTooLong,
ModuleImportNotAtTopOfFile, ModuleImportNotAtTopOfFile,
NoAssertEquals,
RaiseNotImplemented, RaiseNotImplemented,
ReturnOutsideFunction, ReturnOutsideFunction,
UndefinedExport(String), UndefinedExport(String),
@ -140,6 +145,7 @@ impl CheckKind {
CheckKind::ImportStarUsage => "ImportStarUsage", CheckKind::ImportStarUsage => "ImportStarUsage",
CheckKind::LineTooLong => "LineTooLong", CheckKind::LineTooLong => "LineTooLong",
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile", CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
CheckKind::NoAssertEquals => "NoAssertEquals",
CheckKind::RaiseNotImplemented => "RaiseNotImplemented", CheckKind::RaiseNotImplemented => "RaiseNotImplemented",
CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction", CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction",
CheckKind::UndefinedExport(_) => "UndefinedExport", CheckKind::UndefinedExport(_) => "UndefinedExport",
@ -163,6 +169,7 @@ impl CheckKind {
CheckKind::ImportStarUsage => &CheckCode::F403, CheckKind::ImportStarUsage => &CheckCode::F403,
CheckKind::LineTooLong => &CheckCode::E501, CheckKind::LineTooLong => &CheckCode::E501,
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402, CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
CheckKind::NoAssertEquals => &CheckCode::R002,
CheckKind::RaiseNotImplemented => &CheckCode::F901, CheckKind::RaiseNotImplemented => &CheckCode::F901,
CheckKind::ReturnOutsideFunction => &CheckCode::F706, CheckKind::ReturnOutsideFunction => &CheckCode::F706,
CheckKind::UndefinedExport(_) => &CheckCode::F822, CheckKind::UndefinedExport(_) => &CheckCode::F822,
@ -170,7 +177,7 @@ impl CheckKind {
CheckKind::UndefinedName(_) => &CheckCode::F821, CheckKind::UndefinedName(_) => &CheckCode::F821,
CheckKind::UnusedImport(_) => &CheckCode::F401, CheckKind::UnusedImport(_) => &CheckCode::F401,
CheckKind::UnusedVariable(_) => &CheckCode::F841, CheckKind::UnusedVariable(_) => &CheckCode::F841,
CheckKind::UselessObjectInheritance(_) => &CheckCode::R0205, CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
CheckKind::YieldOutsideFunction => &CheckCode::F704, CheckKind::YieldOutsideFunction => &CheckCode::F704,
} }
} }
@ -196,6 +203,9 @@ impl CheckKind {
CheckKind::ModuleImportNotAtTopOfFile => { CheckKind::ModuleImportNotAtTopOfFile => {
"Module level import not at top of file".to_string() "Module level import not at top of file".to_string()
} }
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
}
CheckKind::RaiseNotImplemented => { CheckKind::RaiseNotImplemented => {
"`raise NotImplemented` should be `raise NotImplementedError`".to_string() "`raise NotImplemented` should be `raise NotImplementedError`".to_string()
} }
@ -226,7 +236,26 @@ impl CheckKind {
/// Whether the check kind is (potentially) fixable. /// Whether the check kind is (potentially) fixable.
pub fn fixable(&self) -> bool { pub fn fixable(&self) -> bool {
matches!(self, CheckKind::UselessObjectInheritance(_)) match self {
CheckKind::AssertTuple => false,
CheckKind::DefaultExceptNotLast => false,
CheckKind::DuplicateArgumentName => false,
CheckKind::FStringMissingPlaceholders => false,
CheckKind::IfTuple => false,
CheckKind::ImportStarUsage => false,
CheckKind::LineTooLong => false,
CheckKind::ModuleImportNotAtTopOfFile => false,
CheckKind::NoAssertEquals => true,
CheckKind::RaiseNotImplemented => false,
CheckKind::ReturnOutsideFunction => false,
CheckKind::UndefinedExport(_) => false,
CheckKind::UndefinedLocal(_) => false,
CheckKind::UndefinedName(_) => false,
CheckKind::UnusedImport(_) => false,
CheckKind::UnusedVariable(_) => false,
CheckKind::UselessObjectInheritance(_) => true,
CheckKind::YieldOutsideFunction => false,
}
} }
} }

View File

@ -591,13 +591,13 @@ mod tests {
} }
#[test] #[test]
fn r0205() -> Result<()> { fn r001() -> Result<()> {
let actual = check_path( let actual = check_path(
Path::new("./resources/test/fixtures/R0205.py"), Path::new("./resources/test/fixtures/R001.py"),
&settings::Settings { &settings::Settings {
line_length: 88, line_length: 88,
exclude: vec![], exclude: vec![],
select: BTreeSet::from([CheckCode::R0205]), select: BTreeSet::from([CheckCode::R001]),
}, },
&autofix::Mode::Generate, &autofix::Mode::Generate,
)?; )?;
@ -810,4 +810,45 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn r002() -> Result<()> {
let actual = check_path(
Path::new("./resources/test/fixtures/R002.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::R002]),
},
&autofix::Mode::Generate,
)?;
let expected = vec![
Check {
kind: CheckKind::NoAssertEquals,
location: Location::new(1, 19),
fix: Some(Fix {
content: "assertEqual".to_string(),
start: Location::new(1, 6),
end: Location::new(1, 18),
applied: false,
}),
},
Check {
kind: CheckKind::NoAssertEquals,
location: Location::new(2, 18),
fix: Some(Fix {
content: "assertEqual".to_string(),
start: Location::new(2, 6),
end: Location::new(2, 18),
applied: false,
}),
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
} }

View File

@ -253,7 +253,8 @@ other-attribute = 1
CheckCode::F831, CheckCode::F831,
CheckCode::F841, CheckCode::F841,
CheckCode::F901, CheckCode::F901,
CheckCode::R0205, CheckCode::R001,
CheckCode::R002,
])), ])),
} }
); );