Compare commits

..

10 Commits

Author SHA1 Message Date
Charlie Marsh
2774194b03 Bump version to 0.0.65 2022-10-09 22:14:04 -04:00
Charlie Marsh
71ebd39f35 Extend assertEquals check to all deprecated unittest aliases (#380) 2022-10-09 22:13:51 -04:00
Charlie Marsh
a2f78ba2c7 Fix auto-fix for assertEquals rename 2022-10-09 22:11:32 -04:00
Charlie Marsh
b51a080a44 Rename SPR001 to U008 (#379) 2022-10-09 21:58:22 -04:00
Charlie Marsh
6a1d7d8a1c Defer string annotations even when futures annotations are enabled (#378) 2022-10-09 18:28:29 -04:00
Charlie Marsh
10b250ee57 Bump version to 0.0.64 2022-10-09 17:38:09 -04:00
Charlie Marsh
30b1b1e15a Treat TypeAlias values as annotations (#377) 2022-10-09 17:37:19 -04:00
Charlie Marsh
aafe7c0c39 Mark aliased submodule imports as used (#374) 2022-10-09 17:01:14 -04:00
Harutaka Kawamura
f060248656 Fix collapsed message (#372) 2022-10-09 13:01:36 -04:00
Harutaka Kawamura
bbe0220c72 Implement C415 (#371) 2022-10-09 10:12:58 -04:00
29 changed files with 742 additions and 141 deletions

3
Cargo.lock generated
View File

@@ -1907,7 +1907,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.63"
version = "0.0.65"
dependencies = [
"anyhow",
"bincode",
@@ -1926,6 +1926,7 @@ dependencies = [
"libcst",
"log",
"notify",
"num-bigint",
"once_cell",
"path-absolutize",
"rayon",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.63"
version = "0.0.65"
edition = "2021"
[lib]
@@ -37,6 +37,7 @@ update-informer = { version = "0.5.0", default_features = false, features = ["py
walkdir = { version = "2.3.2" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = "0.24.3"
num-bigint = "0.4.3"
[dev-dependencies]
insta = { version = "1.19.1", features = ["yaml"] }

View File

@@ -286,7 +286,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| C405 | UnnecessaryLiteralSet | Unnecessary <list/tuple> literal - rewrite as a set literal | | |
| C406 | UnnecessaryLiteralDict | Unnecessary <list/tuple> literal - rewrite as a dict literal | | |
| C408 | UnnecessaryCollectionCall | Unnecessary <dict/list/tuple> call - rewrite as a literal | | |
| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within <reversed/set/sorted>() | | |
| T201 | PrintFound | `print` found | | 🛠 |
| T203 | PPrintFound | `pprint` found | | 🛠 |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | | 🛠 |
@@ -295,7 +295,8 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| U004 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
| U005 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | | 🛠 |
| U007 | UsePEP604Annotation | Use `X | Y` for type annotations | | 🛠 |
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | | 🛠 |
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
## Integrations

View File

@@ -18,7 +18,7 @@ fn main() {
"| {} | {} | {} | {} | {} |",
check_kind.code().as_ref(),
check_kind.as_ref(),
check_kind.body(),
check_kind.body().replace("|", r"\|"),
default_token,
fix_token
);

9
resources/test/fixtures/C415.py vendored Normal file
View File

@@ -0,0 +1,9 @@
lst = [2, 1, 3]
a = set(lst[::-1])
b = reversed(lst[::-1])
c = sorted(lst[::-1])
d = sorted(lst[::-1], reverse=True)
e = set(lst[2:-1])
f = set(lst[:1:-1])
g = set(lst[::1])
h = set(lst[::-2])

View File

@@ -65,3 +65,24 @@ b = Union["Nut", None]
c = cast("Vegetable", b)
Field = lambda default=MISSING: field(default=default)
# Test: access a sub-importation via an alias.
import pyarrow as pa
import pyarrow.csv
print(pa.csv.read_csv("test.csv"))
# Test: referencing an import via TypeAlias.
import sys
import numpy as np
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
CustomInt: TypeAlias = "np.int8 | np.int16"

5
resources/test/fixtures/F401_1.py vendored Normal file
View File

@@ -0,0 +1,5 @@
"""Access a sub-importation via an alias."""
import pyarrow as pa
import pyarrow.csv
print(pa.csv.read_csv("test.csv"))

12
resources/test/fixtures/F401_2.py vendored Normal file
View File

@@ -0,0 +1,12 @@
"""Test: referencing an import via TypeAlias."""
import sys
import numpy as np
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
CustomInt: TypeAlias = "np.int8 | np.int16"

14
resources/test/fixtures/F401_3.py vendored Normal file
View File

@@ -0,0 +1,14 @@
"""Test: referencing an import via TypeAlias (with future annotations)."""
from __future__ import annotations
import sys
import numpy as np
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
CustomInt: TypeAlias = np.int8 | np.int16

14
resources/test/fixtures/F401_4.py vendored Normal file
View File

@@ -0,0 +1,14 @@
"""Test: referencing an import via TypeAlias (with future annotations and quotes)."""
from __future__ import annotations
import sys
import numpy as np
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
CustomInt: TypeAlias = "np.int8 | np.int16"

View File

@@ -1,3 +1,10 @@
self.assertEquals (1, 2)
self.assertEquals(1, 2)
self.assertEqual(3, 4)
import unittest
class Suite(unittest.TestCase):
def test(self) -> None:
self.assertEquals (1, 2)
self.assertEquals(1, 2)
self.assertEqual(3, 4)
self.failUnlessAlmostEqual(1, 1.1)
self.assertNotRegexpMatches("a", "b")

View File

@@ -1,6 +1,7 @@
use std::collections::BTreeSet;
use itertools::izip;
use num_bigint::BigInt;
use regex::Regex;
use rustpython_parser::ast::{
Arg, ArgData, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind,
@@ -360,23 +361,6 @@ pub fn check_duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
checks
}
/// Check AssertEquals compliance.
pub fn check_assert_equals(expr: &Expr) -> Option<Check> {
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
if attr == "assertEquals" {
if let ExprKind::Name { id, .. } = &value.node {
if id == "self" {
return Some(Check::new(
CheckKind::NoAssertEquals,
Range::from_located(expr),
));
}
}
}
}
None
}
#[derive(Debug, PartialEq)]
enum DictionaryKey<'a> {
Constant(&'a Constant),
@@ -985,6 +969,44 @@ pub fn unnecessary_collection_call(
None
}
pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
if let Some(first_arg) = args.first() {
if let ExprKind::Name { id, .. } = &func.node {
if id == "set" || id == "sorted" || id == "reversed" {
if let ExprKind::Subscript { slice, .. } = &first_arg.node {
if let ExprKind::Slice { lower, upper, step } = &slice.node {
if lower.is_none() && upper.is_none() {
if let Some(step) = step {
if let ExprKind::UnaryOp {
op: Unaryop::USub,
operand,
} = &step.node
{
if let ExprKind::Constant {
value: Constant::Int(val),
..
} = &operand.node
{
if *val == BigInt::from(1) {
return Some(Check::new(
CheckKind::UnnecessarySubscriptReversal(
id.to_string(),
),
Range::from_located(expr),
));
}
}
}
}
}
}
}
}
}
}
None
}
// flake8-super
/// Check that `super()` has no args
pub fn check_super_args(

View File

@@ -74,9 +74,9 @@ pub enum BindingKind {
Export(Vec<String>),
FutureImportation,
StarImportation,
Importation(String, BindingContext),
FromImportation(String, BindingContext),
SubmoduleImportation(String, BindingContext),
Importation(String, String, BindingContext),
FromImportation(String, String, BindingContext),
SubmoduleImportation(String, String, BindingContext),
}
#[derive(Clone, Debug)]

View File

@@ -1,3 +1,4 @@
use crate::ast::helpers::match_name_or_attr;
use rustpython_parser::ast::{
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler,
ExcepthandlerKind, Expr, ExprContext, ExprKind, Keyword, MatchCase, Operator, Pattern,
@@ -148,7 +149,11 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
} => {
visitor.visit_annotation(annotation);
if let Some(expr) = value {
visitor.visit_expr(expr);
if match_name_or_attr(annotation, "TypeAlias") {
visitor.visit_annotation(expr);
} else {
visitor.visit_expr(expr);
}
}
visitor.visit_expr(target);
}

View File

@@ -364,13 +364,16 @@ where
for alias in names {
if alias.node.name.contains('.') && alias.node.asname.is_none() {
// TODO(charlie): Multiple submodule imports with the same parent module
// will be merged into a single binding.
// Given `import foo.bar`, `name` would be "foo", and `full_name` would be
// "foo.bar".
let name = alias.node.name.split('.').next().unwrap();
let full_name = &alias.node.name;
self.add_binding(
alias.node.name.split('.').next().unwrap().to_string(),
name.to_string(),
Binding {
kind: BindingKind::SubmoduleImportation(
alias.node.name.to_string(),
name.to_string(),
full_name.to_string(),
self.binding_context(),
),
used: None,
@@ -382,19 +385,17 @@ where
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
}
// Given `import foo`, `name` and `full_name` would both be `foo`.
// Given `import foo as bar`, `name` would be `bar` and `full_name` would
// be `foo`.
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
let full_name = &alias.node.name;
self.add_binding(
alias
.node
.asname
.clone()
.unwrap_or_else(|| alias.node.name.clone()),
name.to_string(),
Binding {
kind: BindingKind::Importation(
alias
.node
.asname
.clone()
.unwrap_or_else(|| alias.node.name.clone()),
name.to_string(),
full_name.to_string(),
self.binding_context(),
),
used: None,
@@ -423,14 +424,10 @@ where
}
for alias in names {
let name = alias
.node
.asname
.clone()
.unwrap_or_else(|| alias.node.name.clone());
if let Some("__future__") = module.as_deref() {
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
self.add_binding(
name,
name.to_string(),
Binding {
kind: BindingKind::FutureImportation,
used: Some((
@@ -509,18 +506,26 @@ where
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
}
let binding = Binding {
kind: BindingKind::FromImportation(
match module {
None => name.clone(),
Some(parent) => format!("{}.{}", parent, name),
},
self.binding_context(),
),
used: None,
range: Range::from_located(stmt),
// Given `from foo import bar`, `name` would be "bar" and `full_name` would
// be "foo.bar". Given `from foo import bar as baz`, `name` would be "baz"
// and `full_name` would be "foo.bar".
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
let full_name = match module {
None => alias.node.name.to_string(),
Some(parent) => format!("{}.{}", parent, alias.node.name),
};
self.add_binding(name, binding)
self.add_binding(
name.to_string(),
Binding {
kind: BindingKind::FromImportation(
name.to_string(),
full_name,
self.binding_context(),
),
used: None,
range: Range::from_located(stmt),
},
)
}
}
}
@@ -628,12 +633,20 @@ where
let prev_in_annotation = self.in_annotation;
if self.in_annotation && self.annotations_future_enabled {
self.deferred_annotations.push((
expr,
self.scope_stack.clone(),
self.parent_stack.clone(),
));
visitor::walk_expr(self, expr);
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &expr.node
{
self.deferred_string_annotations
.push((Range::from_located(expr), value));
} else {
self.deferred_annotations.push((
expr,
self.scope_stack.clone(),
self.parent_stack.clone(),
));
}
return;
}
@@ -713,11 +726,11 @@ where
..
} => {
if self.settings.enabled.contains(&CheckCode::U005) {
plugins::assert_equals(self, func);
plugins::deprecated_unittest_alias(self, func);
}
// flake8-super
if self.settings.enabled.contains(&CheckCode::SPR001) {
if self.settings.enabled.contains(&CheckCode::U008) {
plugins::super_call_with_parameters(self, expr, func, args);
}
@@ -783,6 +796,12 @@ where
};
}
if self.settings.enabled.contains(&CheckCode::C415) {
if let Some(check) = checks::unnecessary_subscript_reversal(expr, func, args) {
self.checks.push(check);
};
}
// pyupgrade
if self.settings.enabled.contains(&CheckCode::U002)
&& self.settings.target_version >= PythonVersion::Py310
@@ -1249,6 +1268,47 @@ impl CheckLocator for Checker<'_> {
}
}
fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> bool {
let alias = if let Some(binding) = scope.values.get_mut(id) {
// Mark the binding as used.
binding.used = Some((scope_id, Range::from_located(expr)));
// If the name of the sub-importation is the same as an alias of another importation and the
// alias is used, that sub-importation should be marked as used too.
//
// This handles code like:
// import pyarrow as pa
// import pyarrow.csv
// print(pa.csv.read_csv("test.csv"))
if let BindingKind::Importation(name, full_name, _)
| BindingKind::FromImportation(name, full_name, _)
| BindingKind::SubmoduleImportation(name, full_name, _) = &binding.kind
{
let has_alias = full_name
.split('.')
.last()
.map(|segment| segment != name)
.unwrap_or_default();
if has_alias {
// Clone the alias. (We'll mutate it below.)
full_name.to_string()
} else {
return true;
}
} else {
return true;
}
} else {
return false;
};
// Mark the sub-importation as used.
if let Some(binding) = scope.values.get_mut(&alias) {
binding.used = Some((scope_id, Range::from_located(expr)));
}
true
}
impl<'a> Checker<'a> {
pub fn add_check(&mut self, check: Check) {
self.checks.push(check);
@@ -1332,7 +1392,11 @@ impl<'a> Checker<'a> {
&& matches!(binding.kind, BindingKind::LoopVar)
&& matches!(
existing.kind,
BindingKind::Importation(_, _) | BindingKind::FromImportation(_, _)
BindingKind::Importation(_, _, _)
| BindingKind::FromImportation(_, _, _)
| BindingKind::SubmoduleImportation(_, _, _)
| BindingKind::StarImportation
| BindingKind::FutureImportation
)
{
self.checks.push(Check::new(
@@ -1371,8 +1435,8 @@ impl<'a> Checker<'a> {
continue;
}
}
if let Some(binding) = scope.values.get_mut(id) {
binding.used = Some((scope_id, Range::from_located(expr)));
if try_mark_used(scope, scope_id, id, expr) {
return;
}
@@ -1707,7 +1771,7 @@ impl<'a> Checker<'a> {
if !used {
match &binding.kind {
BindingKind::FromImportation(full_name, context) => {
BindingKind::FromImportation(_, full_name, context) => {
let full_names = unused
.entry((
ImportKind::ImportFrom,
@@ -1717,8 +1781,8 @@ impl<'a> Checker<'a> {
.or_default();
full_names.push(full_name);
}
BindingKind::Importation(full_name, context)
| BindingKind::SubmoduleImportation(full_name, context) => {
BindingKind::Importation(_, full_name, context)
| BindingKind::SubmoduleImportation(_, full_name, context) => {
let full_names = unused
.entry((
ImportKind::Import,

View File

@@ -129,8 +129,7 @@ pub enum CheckCode {
C405,
C406,
C408,
// flake8-super
SPR001,
C415,
// flake8-print
T201,
T203,
@@ -142,6 +141,7 @@ pub enum CheckCode {
U005,
U006,
U007,
U008,
// Meta
M001,
}
@@ -219,8 +219,7 @@ pub enum CheckKind {
UnnecessaryLiteralSet(String),
UnnecessaryLiteralDict(String),
UnnecessaryCollectionCall(String),
// flake8-super
SuperCallWithParameters,
UnnecessarySubscriptReversal(String),
// flake8-print
PrintFound,
PPrintFound,
@@ -228,10 +227,11 @@ pub enum CheckKind {
TypeOfPrimitive(Primitive),
UnnecessaryAbspath,
UselessMetaclassType,
NoAssertEquals,
DeprecatedUnittestAlias(String, String),
UselessObjectInheritance(String),
UsePEP585Annotation(String),
UsePEP604Annotation,
SuperCallWithParameters,
// Meta
UnusedNOQA(Option<String>),
}
@@ -312,8 +312,9 @@ impl CheckCode {
CheckCode::C408 => {
CheckKind::UnnecessaryCollectionCall("<dict/list/tuple>".to_string())
}
// flake8-super
CheckCode::SPR001 => CheckKind::SuperCallWithParameters,
CheckCode::C415 => {
CheckKind::UnnecessarySubscriptReversal("<reversed/set/sorted>".to_string())
}
// flake8-print
CheckCode::T201 => CheckKind::PrintFound,
CheckCode::T203 => CheckKind::PPrintFound,
@@ -322,9 +323,13 @@ impl CheckCode {
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
CheckCode::U003 => CheckKind::TypeOfPrimitive(Primitive::Str),
CheckCode::U004 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::U005 => CheckKind::NoAssertEquals,
CheckCode::U005 => CheckKind::DeprecatedUnittestAlias(
"assertEquals".to_string(),
"assertEqual".to_string(),
),
CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()),
CheckCode::U007 => CheckKind::UsePEP604Annotation,
CheckCode::U008 => CheckKind::SuperCallWithParameters,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
@@ -393,8 +398,7 @@ impl CheckKind {
CheckKind::UnnecessaryLiteralSet(_) => &CheckCode::C405,
CheckKind::UnnecessaryLiteralDict(_) => &CheckCode::C406,
CheckKind::UnnecessaryCollectionCall(_) => &CheckCode::C408,
// flake8-super
CheckKind::SuperCallWithParameters => &CheckCode::SPR001,
CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415,
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
@@ -402,10 +406,11 @@ impl CheckKind {
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
CheckKind::UselessMetaclassType => &CheckCode::U001,
CheckKind::NoAssertEquals => &CheckCode::U005,
CheckKind::DeprecatedUnittestAlias(_, _) => &CheckCode::U005,
CheckKind::UsePEP585Annotation(_) => &CheckCode::U006,
CheckKind::UsePEP604Annotation => &CheckCode::U007,
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
CheckKind::SuperCallWithParameters => &CheckCode::U008,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
@@ -579,9 +584,8 @@ impl CheckKind {
CheckKind::UnnecessaryCollectionCall(obj_type) => {
format!("Unnecessary {obj_type} call - rewrite as a literal")
}
// flake8-super
CheckKind::SuperCallWithParameters => {
"Use `super()` instead of `super(__class__, self)`".to_string()
CheckKind::UnnecessarySubscriptReversal(func) => {
format!("Unnecessary subscript reversal of iterable within {func}()")
}
// flake8-print
CheckKind::PrintFound => "`print` found".to_string(),
@@ -594,8 +598,8 @@ impl CheckKind {
"`abspath(__file__)` is unnecessary in Python 3.9 and later".to_string()
}
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
CheckKind::DeprecatedUnittestAlias(alias, target) => {
format!("`{}` is deprecated, use `{}` instead", alias, target)
}
CheckKind::UselessObjectInheritance(name) => {
format!("Class `{name}` inherits from object")
@@ -608,6 +612,9 @@ impl CheckKind {
)
}
CheckKind::UsePEP604Annotation => "Use `X | Y` for type annotations".to_string(),
CheckKind::SuperCallWithParameters => {
"Use `super()` instead of `super(__class__, self)`".to_string()
}
// Meta
CheckKind::UnusedNOQA(code) => match code {
None => "Unused `noqa` directive".to_string(),
@@ -620,7 +627,7 @@ impl CheckKind {
pub fn fixable(&self) -> bool {
matches!(
self,
CheckKind::NoAssertEquals
CheckKind::DeprecatedUnittestAlias(_, _)
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::SuperCallWithParameters

View File

@@ -376,9 +376,57 @@ mod tests {
}
#[test]
fn f401() -> Result<()> {
fn f401_0() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401.py"),
Path::new("./resources/test/fixtures/F401_0.py"),
&settings::Settings::for_rule(CheckCode::F401),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn f401_1() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401_1.py"),
&settings::Settings::for_rule(CheckCode::F401),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn f401_2() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401_2.py"),
&settings::Settings::for_rule(CheckCode::F401),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn f401_3() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401_3.py"),
&settings::Settings::for_rule(CheckCode::F401),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn f401_4() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401_4.py"),
&settings::Settings::for_rule(CheckCode::F401),
&fixer::Mode::Generate,
)?;
@@ -895,10 +943,22 @@ mod tests {
}
#[test]
fn spr001() -> Result<()> {
fn c415() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/SPR001.py"),
&settings::Settings::for_rule(CheckCode::SPR001),
Path::new("./resources/test/fixtures/C415.py"),
&settings::Settings::for_rule(CheckCode::C415),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u008() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U008.py"),
&settings::Settings::for_rule(CheckCode::U008),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);

View File

@@ -1,5 +1,5 @@
pub use assert_equals::assert_equals;
pub use assert_tuple::assert_tuple;
pub use deprecated_unittest_alias::deprecated_unittest_alias;
pub use if_tuple::if_tuple;
pub use invalid_print_syntax::invalid_print_syntax;
pub use print_call::print_call;
@@ -11,8 +11,8 @@ pub use use_pep604_annotation::use_pep604_annotation;
pub use useless_metaclass_type::useless_metaclass_type;
pub use useless_object_inheritance::useless_object_inheritance;
mod assert_equals;
mod assert_tuple;
mod deprecated_unittest_alias;
mod if_tuple;
mod invalid_print_syntax;
mod print_call;

View File

@@ -1,23 +0,0 @@
use rustpython_ast::{Expr, Location};
use crate::ast::checks;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::Fix;
pub fn assert_equals(checker: &mut Checker, expr: &Expr) {
if let Some(mut check) = checks::check_assert_equals(expr) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "assertEqual".to_string(),
location: Location::new(expr.location.row(), expr.location.column() + 1),
end_location: Location::new(
expr.location.row(),
expr.location.column() + 1 + "assertEquals".len(),
),
applied: false,
});
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,56 @@
use std::collections::BTreeMap;
use once_cell::sync::Lazy;
use rustpython_ast::{Expr, ExprKind, Location};
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind, Fix};
static DEPRECATED_ALIASES: Lazy<BTreeMap<&'static str, &'static str>> = Lazy::new(|| {
BTreeMap::from([
("failUnlessEqual", "assertEqual"),
("assertEquals", "assertEqual"),
("failIfEqual", "assertNotEqual"),
("assertNotEquals", "assertNotEqual"),
("failUnless", "assertTrue"),
("assert_", "assertTrue"),
("failIf", "assertFalse"),
("failUnlessRaises", "assertRaises"),
("failUnlessAlmostEqual", "assertAlmostEqual"),
("assertAlmostEquals", "assertAlmostEqual"),
("failIfAlmostEqual", "assertNotAlmostEqual"),
("assertNotAlmostEquals", "assertNotAlmostEqual"),
("assertRegexpMatches", "assertRegex"),
("assertNotRegexpMatches", "assertNotRegex"),
("assertRaisesRegexp", "assertRaisesRegex"),
])
});
pub fn deprecated_unittest_alias(checker: &mut Checker, expr: &Expr) {
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
if let Some(target) = DEPRECATED_ALIASES.get(attr.as_str()) {
if let ExprKind::Name { id, .. } = &value.node {
if id == "self" {
let mut check = Check::new(
CheckKind::DeprecatedUnittestAlias(attr.to_string(), target.to_string()),
Range::from_located(expr),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: format!("self.{}", target),
location: Location::new(expr.location.row(), expr.location.column()),
end_location: Location::new(
expr.end_location.row(),
expr.end_location.column(),
),
applied: false,
});
}
checker.add_check(check);
}
}
}
}
}

View File

@@ -0,0 +1,41 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnnecessarySubscriptReversal: set
location:
row: 2
column: 5
end_location:
row: 2
column: 19
fix: ~
- kind:
UnnecessarySubscriptReversal: reversed
location:
row: 3
column: 5
end_location:
row: 3
column: 24
fix: ~
- kind:
UnnecessarySubscriptReversal: sorted
location:
row: 4
column: 5
end_location:
row: 4
column: 22
fix: ~
- kind:
UnnecessarySubscriptReversal: sorted
location:
row: 5
column: 5
end_location:
row: 5
column: 36
fix: ~

View File

@@ -0,0 +1,131 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnusedImport:
- functools
location:
row: 2
column: 1
end_location:
row: 2
column: 21
fix:
content: import os
location:
row: 2
column: 1
end_location:
row: 2
column: 21
applied: false
- kind:
UnusedImport:
- collections.OrderedDict
location:
row: 4
column: 1
end_location:
row: 8
column: 2
fix:
content: "from collections import (\n Counter,\n namedtuple,\n)"
location:
row: 4
column: 1
end_location:
row: 8
column: 2
applied: false
- kind:
UnusedImport:
- logging.handlers
location:
row: 12
column: 1
end_location:
row: 12
column: 24
fix:
content: import logging.handlers
location:
row: 12
column: 1
end_location:
row: 12
column: 24
applied: false
- kind:
UnusedImport:
- shelve
location:
row: 33
column: 5
end_location:
row: 33
column: 18
fix:
content: ""
location:
row: 33
column: 1
end_location:
row: 34
column: 1
applied: false
- kind:
UnusedImport:
- importlib
location:
row: 34
column: 5
end_location:
row: 34
column: 21
fix:
content: ""
location:
row: 34
column: 1
end_location:
row: 35
column: 1
applied: false
- kind:
UnusedImport:
- pathlib
location:
row: 38
column: 5
end_location:
row: 38
column: 19
fix:
content: ""
location:
row: 38
column: 1
end_location:
row: 39
column: 1
applied: false
- kind:
UnusedImport:
- pickle
location:
row: 53
column: 9
end_location:
row: 53
column: 22
fix:
content: pass
location:
row: 53
column: 9
end_location:
row: 53
column: 22
applied: false

View File

@@ -0,0 +1,6 @@
---
source: src/linter.rs
expression: checks
---
[]

View File

@@ -0,0 +1,6 @@
---
source: src/linter.rs
expression: checks
---
[]

View File

@@ -0,0 +1,6 @@
---
source: src/linter.rs
expression: checks
---
[]

View File

@@ -0,0 +1,6 @@
---
source: src/linter.rs
expression: checks
---
[]

View File

@@ -2,36 +2,80 @@
source: src/linter.rs
expression: checks
---
- kind: NoAssertEquals
- kind:
DeprecatedUnittestAlias:
- assertEquals
- assertEqual
location:
row: 1
column: 1
row: 6
column: 9
end_location:
row: 1
column: 18
row: 6
column: 26
fix:
content: assertEqual
content: self.assertEqual
location:
row: 1
column: 2
row: 6
column: 9
end_location:
row: 1
column: 14
row: 6
column: 26
applied: false
- kind: NoAssertEquals
- kind:
DeprecatedUnittestAlias:
- assertEquals
- assertEqual
location:
row: 2
column: 1
row: 7
column: 9
end_location:
row: 2
column: 18
row: 7
column: 26
fix:
content: assertEqual
content: self.assertEqual
location:
row: 2
column: 2
row: 7
column: 9
end_location:
row: 2
column: 14
row: 7
column: 26
applied: false
- kind:
DeprecatedUnittestAlias:
- failUnlessAlmostEqual
- assertAlmostEqual
location:
row: 9
column: 9
end_location:
row: 9
column: 35
fix:
content: self.assertAlmostEqual
location:
row: 9
column: 9
end_location:
row: 9
column: 35
applied: false
- kind:
DeprecatedUnittestAlias:
- assertNotRegexpMatches
- assertNotRegex
location:
row: 10
column: 9
end_location:
row: 10
column: 36
fix:
content: self.assertNotRegex
location:
row: 10
column: 9
end_location:
row: 10
column: 36
applied: false

View File

@@ -0,0 +1,85 @@
---
source: src/linter.rs
expression: checks
---
- kind: SuperCallWithParameters
location:
row: 17
column: 18
end_location:
row: 17
column: 36
fix:
content: super()
location:
row: 17
column: 18
end_location:
row: 17
column: 36
applied: false
- kind: SuperCallWithParameters
location:
row: 18
column: 9
end_location:
row: 18
column: 27
fix:
content: super()
location:
row: 18
column: 9
end_location:
row: 18
column: 27
applied: false
- kind: SuperCallWithParameters
location:
row: 19
column: 9
end_location:
row: 22
column: 10
fix:
content: super()
location:
row: 19
column: 9
end_location:
row: 22
column: 10
applied: false
- kind: SuperCallWithParameters
location:
row: 36
column: 9
end_location:
row: 36
column: 29
fix:
content: super()
location:
row: 36
column: 9
end_location:
row: 36
column: 29
applied: false
- kind: SuperCallWithParameters
location:
row: 50
column: 13
end_location:
row: 50
column: 33
fix:
content: super()
location:
row: 50
column: 13
end_location:
row: 50
column: 33
applied: false