mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:39:12 +00:00
Replace deletion-tracking with enforced isolation levels (#4766)
This commit is contained in:
parent
fcbf5c3fae
commit
621718784a
42 changed files with 471 additions and 620 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1908,6 +1908,7 @@ name = "ruff_diagnostics"
|
|||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"is-macro",
|
||||
"log",
|
||||
"ruff_text_size",
|
||||
"serde",
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! Interface for generating autofix edits from higher-level actions (e.g., "remove an argument").
|
||||
use anyhow::{bail, Result};
|
||||
use itertools::Itertools;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustpython_parser::ast::{self, Excepthandler, Expr, Keyword, Ranged, Stmt};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
@ -28,21 +27,19 @@ use crate::autofix::codemods;
|
|||
pub(crate) fn delete_stmt(
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Edit> {
|
||||
) -> Edit {
|
||||
if parent
|
||||
.map(|parent| is_lone_child(stmt, parent, deleted))
|
||||
.map_or(Ok(None), |v| v.map(Some))?
|
||||
.map(|parent| is_lone_child(stmt, parent))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// If removing this node would lead to an invalid syntax tree, replace
|
||||
// it with a `pass`.
|
||||
Ok(Edit::range_replacement("pass".to_string(), stmt.range()))
|
||||
Edit::range_replacement("pass".to_string(), stmt.range())
|
||||
} else {
|
||||
Ok(if let Some(semicolon) = trailing_semicolon(stmt, locator) {
|
||||
if let Some(semicolon) = trailing_semicolon(stmt, locator) {
|
||||
let next = next_stmt_break(semicolon, locator);
|
||||
Edit::deletion(stmt.start(), next)
|
||||
} else if helpers::has_leading_content(stmt, locator) {
|
||||
|
@ -57,7 +54,7 @@ pub(crate) fn delete_stmt(
|
|||
} else {
|
||||
let range = locator.full_lines_range(stmt.range());
|
||||
Edit::range_deletion(range)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,13 +63,12 @@ pub(crate) fn remove_unused_imports<'a>(
|
|||
unused_imports: impl Iterator<Item = &'a str>,
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Edit> {
|
||||
match codemods::remove_imports(unused_imports, stmt, locator, stylist)? {
|
||||
None => delete_stmt(stmt, parent, deleted, locator, indexer, stylist),
|
||||
None => Ok(delete_stmt(stmt, parent, locator, indexer, stylist)),
|
||||
Some(content) => Ok(Edit::range_replacement(content, stmt.range())),
|
||||
}
|
||||
}
|
||||
|
@ -179,36 +175,29 @@ pub(crate) fn remove_argument(
|
|||
}
|
||||
}
|
||||
|
||||
/// Determine if a body contains only a single statement, taking into account
|
||||
/// deleted.
|
||||
fn has_single_child(body: &[Stmt], deleted: &[&Stmt]) -> bool {
|
||||
body.iter().filter(|child| !deleted.contains(child)).count() == 1
|
||||
/// Determine if a vector contains only one, specific element.
|
||||
fn is_only<T: PartialEq>(vec: &[T], value: &T) -> bool {
|
||||
vec.len() == 1 && vec[0] == *value
|
||||
}
|
||||
|
||||
/// Determine if a child is the only statement in its body.
|
||||
fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool> {
|
||||
fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
|
||||
match parent {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. })
|
||||
| Stmt::ClassDef(ast::StmtClassDef { body, .. })
|
||||
| Stmt::With(ast::StmtWith { body, .. })
|
||||
| Stmt::AsyncWith(ast::StmtAsyncWith { body, .. }) => {
|
||||
if body.iter().contains(child) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else {
|
||||
bail!("Unable to find child in parent body")
|
||||
if is_only(body, child) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::For(ast::StmtFor { body, orelse, .. })
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
||||
| Stmt::While(ast::StmtWhile { body, orelse, .. })
|
||||
| Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
||||
if body.iter().contains(child) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else if orelse.iter().contains(child) {
|
||||
Ok(has_single_child(orelse, deleted))
|
||||
} else {
|
||||
bail!("Unable to find child in parent body")
|
||||
if is_only(body, child) || is_only(orelse, child) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::Try(ast::StmtTry {
|
||||
|
@ -225,41 +214,26 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
|
|||
finalbody,
|
||||
range: _,
|
||||
}) => {
|
||||
if body.iter().contains(child) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else if orelse.iter().contains(child) {
|
||||
Ok(has_single_child(orelse, deleted))
|
||||
} else if finalbody.iter().contains(child) {
|
||||
Ok(has_single_child(finalbody, deleted))
|
||||
} else if let Some(body) = handlers.iter().find_map(|handler| match handler {
|
||||
Excepthandler::ExceptHandler(ast::ExcepthandlerExceptHandler { body, .. }) => {
|
||||
if body.iter().contains(child) {
|
||||
Some(body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else {
|
||||
bail!("Unable to find child in parent body")
|
||||
if is_only(body, child)
|
||||
|| is_only(orelse, child)
|
||||
|| is_only(finalbody, child)
|
||||
|| handlers.iter().any(|handler| match handler {
|
||||
Excepthandler::ExceptHandler(ast::ExcepthandlerExceptHandler {
|
||||
body, ..
|
||||
}) => is_only(body, child),
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
||||
if let Some(body) = cases.iter().find_map(|case| {
|
||||
if case.body.iter().contains(child) {
|
||||
Some(&case.body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else {
|
||||
bail!("Unable to find child in parent body")
|
||||
if cases.iter().any(|case| is_only(&case.body, child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_ => bail!("Unable to find child in parent body"),
|
||||
_ => {}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Return the location of a trailing semicolon following a `Stmt`, if it's part
|
||||
|
|
|
@ -37,6 +37,7 @@ fn apply_fixes<'a>(
|
|||
) -> (String, FixTable) {
|
||||
let mut output = String::with_capacity(locator.len());
|
||||
let mut last_pos: Option<TextSize> = None;
|
||||
let mut isolation = false;
|
||||
let mut applied: BTreeSet<&Edit> = BTreeSet::default();
|
||||
let mut fixed = FxHashMap::default();
|
||||
|
||||
|
@ -65,6 +66,15 @@ fn apply_fixes<'a>(
|
|||
continue;
|
||||
}
|
||||
|
||||
// If this fix requires isolation, and we've already applied another fix that
|
||||
// requires isolation, skip it. We apply at most one isolated fix per run.
|
||||
if fix.isolation().is_isolated() {
|
||||
if isolation {
|
||||
continue;
|
||||
}
|
||||
isolation = true;
|
||||
}
|
||||
|
||||
for edit in fix
|
||||
.edits()
|
||||
.iter()
|
||||
|
|
|
@ -3,19 +3,19 @@ use std::path::Path;
|
|||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_format::cformat::{CFormatError, CFormatErrorType};
|
||||
use rustpython_parser::ast::{
|
||||
self, Arg, Arguments, Comprehension, Constant, Excepthandler, Expr, ExprContext, Keyword,
|
||||
Operator, Pattern, Ranged, Stmt, Suite, Unaryop,
|
||||
};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Fix, IsolationLevel};
|
||||
use ruff_python_ast::all::{extract_all_names, AllNamesFlags};
|
||||
use ruff_python_ast::helpers::{extract_handled_exceptions, to_module_path};
|
||||
use ruff_python_ast::source_code::{Generator, Indexer, Locator, Quote, Stylist};
|
||||
use ruff_python_ast::str::trailing_quote;
|
||||
use ruff_python_ast::types::{Node, RefEquality};
|
||||
use ruff_python_ast::types::Node;
|
||||
use ruff_python_ast::typing::{parse_type_annotation, AnnotationKind};
|
||||
use ruff_python_ast::visitor::{walk_excepthandler, walk_pattern, Visitor};
|
||||
use ruff_python_ast::{cast, helpers, str, visitor};
|
||||
|
@ -75,9 +75,8 @@ pub(crate) struct Checker<'a> {
|
|||
pub(crate) importer: Importer<'a>,
|
||||
// Stateful fields.
|
||||
semantic_model: SemanticModel<'a>,
|
||||
pub(crate) diagnostics: Vec<Diagnostic>,
|
||||
pub(crate) deletions: FxHashSet<RefEquality<'a, Stmt>>,
|
||||
deferred: Deferred<'a>,
|
||||
pub(crate) diagnostics: Vec<Diagnostic>,
|
||||
// Check-specific state.
|
||||
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>,
|
||||
}
|
||||
|
@ -111,7 +110,6 @@ impl<'a> Checker<'a> {
|
|||
semantic_model: SemanticModel::new(&settings.typing_modules, path, module),
|
||||
deferred: Deferred::default(),
|
||||
diagnostics: Vec::default(),
|
||||
deletions: FxHashSet::default(),
|
||||
flake8_bugbear_seen: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
@ -2124,7 +2122,7 @@ where
|
|||
}
|
||||
|
||||
if self.enabled(Rule::EmptyTypeCheckingBlock) {
|
||||
flake8_type_checking::rules::empty_type_checking_block(self, stmt, body);
|
||||
flake8_type_checking::rules::empty_type_checking_block(self, stmt_if);
|
||||
}
|
||||
|
||||
self.visit_type_checking_block(body);
|
||||
|
@ -5245,14 +5243,14 @@ impl<'a> Checker<'a> {
|
|||
_ => continue,
|
||||
};
|
||||
|
||||
let child_id = binding.source.unwrap();
|
||||
let parent_id = self.semantic_model.stmts.parent_id(child_id);
|
||||
let stmt_id = binding.source.unwrap();
|
||||
let parent_id = self.semantic_model.stmts.parent_id(stmt_id);
|
||||
|
||||
let exceptions = binding.exceptions;
|
||||
let diagnostic_offset = binding.range.start();
|
||||
let child = &self.semantic_model.stmts[child_id];
|
||||
let parent_offset = if matches!(child, Stmt::ImportFrom(_)) {
|
||||
Some(child.start())
|
||||
let stmt = &self.semantic_model.stmts[stmt_id];
|
||||
let parent_offset = if matches!(stmt, Stmt::ImportFrom(_)) {
|
||||
Some(stmt.start())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -5263,12 +5261,12 @@ impl<'a> Checker<'a> {
|
|||
})
|
||||
{
|
||||
ignored
|
||||
.entry((child_id, parent_id, exceptions))
|
||||
.entry((stmt_id, parent_id, exceptions))
|
||||
.or_default()
|
||||
.push((full_name, &binding.range));
|
||||
} else {
|
||||
unused
|
||||
.entry((child_id, parent_id, exceptions))
|
||||
.entry((stmt_id, parent_id, exceptions))
|
||||
.or_default()
|
||||
.push((full_name, &binding.range));
|
||||
}
|
||||
|
@ -5276,38 +5274,26 @@ impl<'a> Checker<'a> {
|
|||
|
||||
let in_init =
|
||||
self.settings.ignore_init_module_imports && self.path.ends_with("__init__.py");
|
||||
for ((defined_by, defined_in, exceptions), unused_imports) in unused
|
||||
for ((stmt_id, parent_id, exceptions), unused_imports) in unused
|
||||
.into_iter()
|
||||
.sorted_by_key(|((defined_by, ..), ..)| *defined_by)
|
||||
{
|
||||
let child = self.semantic_model.stmts[defined_by];
|
||||
let parent = defined_in.map(|defined_in| self.semantic_model.stmts[defined_in]);
|
||||
let stmt = self.semantic_model.stmts[stmt_id];
|
||||
let parent = parent_id.map(|parent_id| self.semantic_model.stmts[parent_id]);
|
||||
let multiple = unused_imports.len() > 1;
|
||||
let in_except_handler = exceptions
|
||||
.intersects(Exceptions::MODULE_NOT_FOUND_ERROR | Exceptions::IMPORT_ERROR);
|
||||
|
||||
let fix = if !in_init && !in_except_handler && self.patch(Rule::UnusedImport) {
|
||||
let deleted: Vec<&Stmt> = self.deletions.iter().map(Into::into).collect();
|
||||
match autofix::edits::remove_unused_imports(
|
||||
autofix::edits::remove_unused_imports(
|
||||
unused_imports.iter().map(|(full_name, _)| *full_name),
|
||||
child,
|
||||
stmt,
|
||||
parent,
|
||||
&deleted,
|
||||
self.locator,
|
||||
self.indexer,
|
||||
self.stylist,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix.is_deletion() || fix.content() == Some("pass") {
|
||||
self.deletions.insert(RefEquality(child));
|
||||
}
|
||||
Some(fix)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to remove unused imports: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -5327,22 +5313,26 @@ impl<'a> Checker<'a> {
|
|||
},
|
||||
*range,
|
||||
);
|
||||
if matches!(child, Stmt::ImportFrom(_)) {
|
||||
diagnostic.set_parent(child.start());
|
||||
if matches!(stmt, Stmt::ImportFrom(_)) {
|
||||
diagnostic.set_parent(stmt.start());
|
||||
}
|
||||
|
||||
if let Some(edit) = &fix {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(edit.clone()));
|
||||
if let Some(edit) = fix.as_ref() {
|
||||
diagnostic.set_fix(Fix::automatic(edit.clone()).isolate(
|
||||
if parent.is_some() {
|
||||
IsolationLevel::Isolated
|
||||
} else {
|
||||
IsolationLevel::NonOverlapping
|
||||
},
|
||||
));
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
for ((child, .., exceptions), unused_imports) in ignored
|
||||
for ((stmt_id, .., exceptions), unused_imports) in ignored
|
||||
.into_iter()
|
||||
.sorted_by_key(|((defined_by, ..), ..)| *defined_by)
|
||||
.sorted_by_key(|((stmt_id, ..), ..)| *stmt_id)
|
||||
{
|
||||
let child = self.semantic_model.stmts[child];
|
||||
let stmt = self.semantic_model.stmts[stmt_id];
|
||||
let multiple = unused_imports.len() > 1;
|
||||
let in_except_handler = exceptions
|
||||
.intersects(Exceptions::MODULE_NOT_FOUND_ERROR | Exceptions::IMPORT_ERROR);
|
||||
|
@ -5361,8 +5351,8 @@ impl<'a> Checker<'a> {
|
|||
},
|
||||
*range,
|
||||
);
|
||||
if matches!(child, Stmt::ImportFrom(_)) {
|
||||
diagnostic.set_parent(child.start());
|
||||
if matches!(stmt, Stmt::ImportFrom(_)) {
|
||||
diagnostic.set_parent(stmt.start());
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
use log::error;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_parser::ast::{self, Expr, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::AlwaysAutofixableViolation;
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Fix, IsolationLevel};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
@ -35,17 +31,19 @@ use crate::registry::AsRule;
|
|||
/// ...
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct DuplicateClassFieldDefinition(pub String);
|
||||
pub struct DuplicateClassFieldDefinition {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl AlwaysAutofixableViolation for DuplicateClassFieldDefinition {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let DuplicateClassFieldDefinition(name) = self;
|
||||
let DuplicateClassFieldDefinition { name } = self;
|
||||
format!("Class field `{name}` is defined multiple times")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
let DuplicateClassFieldDefinition(name) = self;
|
||||
let DuplicateClassFieldDefinition { name } = self;
|
||||
format!("Remove duplicate field definition for `{name}`")
|
||||
}
|
||||
}
|
||||
|
@ -84,29 +82,20 @@ pub(crate) fn duplicate_class_field_definition<'a, 'b>(
|
|||
|
||||
if !seen_targets.insert(target) {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
DuplicateClassFieldDefinition(target.to_string()),
|
||||
DuplicateClassFieldDefinition {
|
||||
name: target.to_string(),
|
||||
},
|
||||
stmt.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
let locator = checker.locator;
|
||||
match delete_stmt(
|
||||
let edit = autofix::edits::delete_stmt(
|
||||
stmt,
|
||||
Some(parent),
|
||||
&deleted,
|
||||
locator,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
checker.deletions.insert(RefEquality(stmt));
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix_from_edit(fix);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to remove duplicate class definition: {}", err);
|
||||
}
|
||||
}
|
||||
);
|
||||
diagnostic.set_fix(Fix::suggested(edit).isolate(IsolationLevel::Isolated));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use rustpython_parser::ast::{self, Constant, Expr, Ranged, Stmt};
|
||||
use rustpython_parser::ast::{Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::AlwaysAutofixableViolation;
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{is_docstring_stmt, trailing_comment_start_offset};
|
||||
|
||||
use ruff_python_ast::helpers::trailing_comment_start_offset;
|
||||
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
@ -53,41 +52,31 @@ pub(crate) fn no_unnecessary_pass(checker: &mut Checker, body: &[Stmt]) {
|
|||
if body.len() > 1 {
|
||||
// This only catches the case in which a docstring makes a `pass` statement
|
||||
// redundant. Consider removing all `pass` statements instead.
|
||||
let docstring_stmt = &body[0];
|
||||
let pass_stmt = &body[1];
|
||||
let Stmt::Expr(ast::StmtExpr { value, range: _ } )= docstring_stmt else {
|
||||
if !is_docstring_stmt(&body[0]) {
|
||||
return;
|
||||
};
|
||||
if matches!(
|
||||
value.as_ref(),
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..),
|
||||
..
|
||||
})
|
||||
) {
|
||||
if pass_stmt.is_pass_stmt() {
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryPass, pass_stmt.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(index) = trailing_comment_start_offset(pass_stmt, checker.locator) {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_deletion(
|
||||
pass_stmt.range().add_end(index),
|
||||
)));
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.try_set_fix_from_edit(|| {
|
||||
delete_stmt(
|
||||
pass_stmt,
|
||||
None,
|
||||
&[],
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
// The second statement must be a `pass` statement.
|
||||
let stmt = &body[1];
|
||||
if !stmt.is_pass_stmt() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryPass, stmt.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let edit = if let Some(index) = trailing_comment_start_offset(stmt, checker.locator) {
|
||||
Edit::range_deletion(stmt.range().add_end(index))
|
||||
} else {
|
||||
autofix::edits::delete_stmt(
|
||||
stmt,
|
||||
None,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
)
|
||||
};
|
||||
diagnostic.set_fix(Fix::automatic(edit));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ PIE790.py:4:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | class Foo:
|
||||
2 2 | """buzz"""
|
||||
3 3 |
|
||||
|
@ -28,7 +28,7 @@ PIE790.py:9:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
6 6 |
|
||||
7 7 | if foo:
|
||||
8 8 | """foo"""
|
||||
|
@ -46,7 +46,7 @@ PIE790.py:14:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
11 11 |
|
||||
12 12 | def multi_statement() -> None:
|
||||
13 13 | """This is a function."""
|
||||
|
@ -65,7 +65,7 @@ PIE790.py:21:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
18 18 | pass
|
||||
19 19 | else:
|
||||
20 20 | """bar"""
|
||||
|
@ -83,7 +83,7 @@ PIE790.py:28:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
25 25 | pass
|
||||
26 26 | else:
|
||||
27 27 | """bar"""
|
||||
|
@ -101,7 +101,7 @@ PIE790.py:35:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
32 32 | pass
|
||||
33 33 | else:
|
||||
34 34 | """bar"""
|
||||
|
@ -119,7 +119,7 @@ PIE790.py:42:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
39 39 | pass
|
||||
40 40 | else:
|
||||
41 41 | """bar"""
|
||||
|
@ -137,7 +137,7 @@ PIE790.py:50:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
47 47 | buzz
|
||||
48 48 | """
|
||||
49 49 |
|
||||
|
@ -155,7 +155,7 @@ PIE790.py:58:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
55 55 | buzz
|
||||
56 56 | """
|
||||
57 57 |
|
||||
|
@ -175,7 +175,7 @@ PIE790.py:65:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
62 62 | """
|
||||
63 63 | buzz
|
||||
64 64 | """
|
||||
|
@ -193,7 +193,7 @@ PIE790.py:74:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
71 71 | bar()
|
||||
72 72 | except ValueError:
|
||||
73 73 | """bar"""
|
||||
|
@ -213,7 +213,7 @@ PIE790.py:79:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
76 76 |
|
||||
77 77 | for _ in range(10):
|
||||
78 78 | """buzz"""
|
||||
|
@ -233,7 +233,7 @@ PIE790.py:83:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
80 80 |
|
||||
81 81 | async for _ in range(10):
|
||||
82 82 | """buzz"""
|
||||
|
@ -251,7 +251,7 @@ PIE790.py:87:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
84 84 |
|
||||
85 85 | while cond:
|
||||
86 86 | """buzz"""
|
||||
|
@ -271,7 +271,7 @@ PIE790.py:92:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
89 89 |
|
||||
90 90 | with bar:
|
||||
91 91 | """buzz"""
|
||||
|
@ -289,7 +289,7 @@ PIE790.py:96:5: PIE790 [*] Unnecessary `pass` statement
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
93 93 |
|
||||
94 94 | async with bar:
|
||||
95 95 | """buzz"""
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use rustpython_parser::ast::{Expr, ExprConstant, Ranged, Stmt, StmtExpr};
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, IsolationLevel, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
@ -56,38 +55,29 @@ pub(crate) fn ellipsis_in_non_empty_class_body<'a>(
|
|||
}
|
||||
|
||||
for stmt in body {
|
||||
if let Stmt::Expr(StmtExpr { value, .. }) = &stmt {
|
||||
if let Expr::Constant(ExprConstant { value, .. }) = value.as_ref() {
|
||||
if value.is_ellipsis() {
|
||||
let mut diagnostic = Diagnostic::new(EllipsisInNonEmptyClassBody, stmt.range());
|
||||
let Stmt::Expr(StmtExpr { value, .. }) = &stmt else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
let deleted: Vec<&Stmt> =
|
||||
checker.deletions.iter().map(Into::into).collect();
|
||||
let edit = delete_stmt(
|
||||
stmt,
|
||||
Some(parent),
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
)?;
|
||||
let Expr::Constant(ExprConstant { value, .. }) = value.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// In the unlikely event the class body consists solely of several
|
||||
// consecutive ellipses, `delete_stmt` can actually result in a
|
||||
// `pass`.
|
||||
if edit.is_deletion() || edit.content() == Some("pass") {
|
||||
checker.deletions.insert(RefEquality(stmt));
|
||||
}
|
||||
|
||||
Ok(Fix::automatic(edit))
|
||||
});
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if !value.is_ellipsis() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(EllipsisInNonEmptyClassBody, stmt.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let edit = autofix::edits::delete_stmt(
|
||||
stmt,
|
||||
Some(parent),
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
);
|
||||
diagnostic.set_fix(Fix::automatic(edit).isolate(IsolationLevel::Isolated));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use log::error;
|
||||
use rustpython_parser::ast::{Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, IsolationLevel};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
@ -35,33 +33,21 @@ pub(crate) fn pass_in_class_body<'a>(
|
|||
}
|
||||
|
||||
for stmt in body {
|
||||
if stmt.is_pass_stmt() {
|
||||
let mut diagnostic = Diagnostic::new(PassInClassBody, stmt.range());
|
||||
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
match delete_stmt(
|
||||
stmt,
|
||||
Some(parent),
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix.is_deletion() || fix.content() == Some("pass") {
|
||||
checker.deletions.insert(RefEquality(stmt));
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix_from_edit(fix);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to delete `pass` statement: {}", e);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
if !stmt.is_pass_stmt() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(PassInClassBody, stmt.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let edit = autofix::edits::delete_stmt(
|
||||
stmt,
|
||||
Some(parent),
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
);
|
||||
diagnostic.set_fix(Fix::automatic(edit).isolate(IsolationLevel::Isolated));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ PYI012.pyi:5:5: PYI012 [*] Class body must not contain `pass`
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
2 2 |
|
||||
3 3 | class OneAttributeClass:
|
||||
4 4 | value: int
|
||||
|
@ -30,7 +30,7 @@ PYI012.pyi:8:5: PYI012 [*] Class body must not contain `pass`
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
5 5 | pass # PYI012 Class body must not contain `pass`
|
||||
6 6 |
|
||||
7 7 | class OneAttributeClassRev:
|
||||
|
@ -50,7 +50,7 @@ PYI012.pyi:16:5: PYI012 [*] Class body must not contain `pass`
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
13 13 | My body only contains pass.
|
||||
14 14 | """
|
||||
15 15 |
|
||||
|
@ -70,7 +70,7 @@ PYI012.pyi:20:5: PYI012 [*] Class body must not contain `pass`
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
17 17 |
|
||||
18 18 | class NonEmptyChild(Exception):
|
||||
19 19 | value: int
|
||||
|
@ -88,7 +88,7 @@ PYI012.pyi:23:5: PYI012 [*] Class body must not contain `pass`
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
20 20 | pass # PYI012 Class body must not contain `pass`
|
||||
21 21 |
|
||||
22 22 | class NonEmptyChild2(Exception):
|
||||
|
@ -108,7 +108,7 @@ PYI012.pyi:28:5: PYI012 [*] Class body must not contain `pass`
|
|||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
25 25 |
|
||||
26 26 | class NonEmptyWithInit:
|
||||
27 27 | value: int
|
||||
|
|
|
@ -91,10 +91,9 @@ PYI013.pyi:17:5: PYI013 [*] Non-empty class body must not contain `...`
|
|||
15 15 | class TwoEllipsesClass:
|
||||
16 16 | ...
|
||||
17 |- ... # Error
|
||||
17 |+ pass # Error
|
||||
18 18 |
|
||||
19 19 | class DocstringClass:
|
||||
20 20 | """
|
||||
18 17 |
|
||||
19 18 | class DocstringClass:
|
||||
20 19 | """
|
||||
|
||||
PYI013.pyi:24:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use log::error;
|
||||
use rustpython_parser::ast::{Ranged, Stmt};
|
||||
use rustpython_parser::ast;
|
||||
use rustpython_parser::ast::Ranged;
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, IsolationLevel};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
@ -48,39 +47,33 @@ impl AlwaysAutofixableViolation for EmptyTypeCheckingBlock {
|
|||
}
|
||||
|
||||
/// TCH005
|
||||
pub(crate) fn empty_type_checking_block<'a, 'b>(
|
||||
checker: &mut Checker<'a>,
|
||||
stmt: &'a Stmt,
|
||||
body: &'a [Stmt],
|
||||
) where
|
||||
'b: 'a,
|
||||
{
|
||||
if body.len() == 1 && body[0].is_pass_stmt() {
|
||||
let mut diagnostic = Diagnostic::new(EmptyTypeCheckingBlock, body[0].range());
|
||||
|
||||
// Delete the entire type-checking block.
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let parent = checker.semantic_model().stmts.parent(stmt);
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
match delete_stmt(
|
||||
stmt,
|
||||
parent,
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
) {
|
||||
Ok(edit) => {
|
||||
if edit.is_deletion() || edit.content() == Some("pass") {
|
||||
checker.deletions.insert(RefEquality(stmt));
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(edit));
|
||||
}
|
||||
Err(e) => error!("Failed to remove empty type-checking block: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
pub(crate) fn empty_type_checking_block(checker: &mut Checker, stmt: &ast::StmtIf) {
|
||||
if stmt.body.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let stmt = &stmt.body[0];
|
||||
if !stmt.is_pass_stmt() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(EmptyTypeCheckingBlock, stmt.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
// Delete the entire type-checking block.
|
||||
let stmt = checker.semantic_model().stmt();
|
||||
let parent = checker.semantic_model().stmt_parent();
|
||||
let edit = autofix::edits::delete_stmt(
|
||||
stmt,
|
||||
parent,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
);
|
||||
diagnostic.set_fix(Fix::automatic(edit).isolate(if parent.is_some() {
|
||||
IsolationLevel::Isolated
|
||||
} else {
|
||||
IsolationLevel::NonOverlapping
|
||||
}));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use rustpython_parser::ast::Stmt;
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, IsolationLevel, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::binding::{
|
||||
Binding, BindingKind, FromImportation, Importation, SubmoduleImportation,
|
||||
|
@ -98,21 +96,15 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
|||
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
// Step 1) Remove the import from the type-checking block.
|
||||
// Step 1) Remove the import.
|
||||
// SAFETY: All non-builtin bindings have a source.
|
||||
let source = binding.source.unwrap();
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
let stmt = checker.semantic_model().stmts[source];
|
||||
let parent = checker
|
||||
.semantic_model()
|
||||
.stmts
|
||||
.parent_id(source)
|
||||
.map(|id| checker.semantic_model().stmts[id]);
|
||||
let parent = checker.semantic_model().stmts.parent(stmt);
|
||||
let remove_import_edit = autofix::edits::remove_unused_imports(
|
||||
std::iter::once(full_name),
|
||||
stmt,
|
||||
parent,
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
|
@ -125,10 +117,15 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
|||
reference.range().start(),
|
||||
)?;
|
||||
|
||||
Ok(Fix::suggested_edits(
|
||||
remove_import_edit,
|
||||
add_import_edit.into_edits(),
|
||||
))
|
||||
Ok(
|
||||
Fix::suggested_edits(remove_import_edit, add_import_edit.into_edits()).isolate(
|
||||
if parent.is_some() {
|
||||
IsolationLevel::Isolated
|
||||
} else {
|
||||
IsolationLevel::NonOverlapping
|
||||
},
|
||||
),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use rustpython_parser::ast::Stmt;
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, IsolationLevel, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::binding::{
|
||||
Binding, BindingKind, FromImportation, Importation, SubmoduleImportation,
|
||||
|
@ -373,18 +371,12 @@ pub(crate) fn typing_only_runtime_import(
|
|||
// Step 1) Remove the import.
|
||||
// SAFETY: All non-builtin bindings have a source.
|
||||
let source = binding.source.unwrap();
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
let stmt = checker.semantic_model().stmts[source];
|
||||
let parent = checker
|
||||
.semantic_model()
|
||||
.stmts
|
||||
.parent_id(source)
|
||||
.map(|id| checker.semantic_model().stmts[id]);
|
||||
let parent = checker.semantic_model().stmts.parent(stmt);
|
||||
let remove_import_edit = autofix::edits::remove_unused_imports(
|
||||
std::iter::once(full_name),
|
||||
stmt,
|
||||
parent,
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
|
@ -398,10 +390,15 @@ pub(crate) fn typing_only_runtime_import(
|
|||
checker.semantic_model(),
|
||||
)?;
|
||||
|
||||
Ok(Fix::suggested_edits(
|
||||
remove_import_edit,
|
||||
add_import_edit.into_edits(),
|
||||
))
|
||||
Ok(
|
||||
Fix::suggested_edits(remove_import_edit, add_import_edit.into_edits()).isolate(
|
||||
if parent.is_some() {
|
||||
IsolationLevel::Isolated
|
||||
} else {
|
||||
IsolationLevel::NonOverlapping
|
||||
},
|
||||
),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ TCH005.py:4:5: TCH005 [*] Found empty type-checking block
|
|||
|
|
||||
= help: Delete empty type-checking block
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | from typing import TYPE_CHECKING, List
|
||||
2 2 |
|
||||
3 |-if TYPE_CHECKING:
|
||||
|
@ -28,7 +28,7 @@ TCH005.py:8:5: TCH005 [*] Found empty type-checking block
|
|||
|
|
||||
= help: Delete empty type-checking block
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
4 4 | pass # TCH005
|
||||
5 5 |
|
||||
6 6 |
|
||||
|
@ -46,7 +46,7 @@ TCH005.py:11:5: TCH005 [*] Found empty type-checking block
|
|||
|
|
||||
= help: Delete empty type-checking block
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
7 7 | if False:
|
||||
8 8 | pass # TCH005
|
||||
9 9 |
|
||||
|
@ -66,7 +66,7 @@ TCH005.py:16:9: TCH005 [*] Found empty type-checking block
|
|||
|
|
||||
= help: Delete empty type-checking block
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
12 12 |
|
||||
13 13 |
|
||||
14 14 | def example():
|
||||
|
@ -86,7 +86,7 @@ TCH005.py:22:9: TCH005 [*] Found empty type-checking block
|
|||
|
|
||||
= help: Delete empty type-checking block
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
18 18 |
|
||||
19 19 |
|
||||
20 20 | class Test:
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{self, Ranged, Stmt};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, IsolationLevel, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::contains_effect;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
use ruff_python_semantic::scope::{ScopeId, ScopeKind};
|
||||
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
|
@ -186,19 +184,14 @@ where
|
|||
unreachable!("No token after matched");
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum DeletionKind {
|
||||
Whole,
|
||||
Partial,
|
||||
}
|
||||
|
||||
/// Generate a [`Edit`] to remove an unused variable assignment, given the
|
||||
/// enclosing [`Stmt`] and the [`TextRange`] of the variable binding.
|
||||
fn remove_unused_variable(
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
range: TextRange,
|
||||
checker: &Checker,
|
||||
) -> Option<(DeletionKind, Fix)> {
|
||||
) -> Option<Fix> {
|
||||
// First case: simple assignment (`x = 1`)
|
||||
if let Stmt::Assign(ast::StmtAssign { targets, value, .. }) = stmt {
|
||||
if let Some(target) = targets.iter().find(|target| range == target.range()) {
|
||||
|
@ -208,34 +201,25 @@ fn remove_unused_variable(
|
|||
{
|
||||
// If the expression is complex (`x = foo()`), remove the assignment,
|
||||
// but preserve the right-hand side.
|
||||
#[allow(deprecated)]
|
||||
Some((
|
||||
DeletionKind::Partial,
|
||||
Fix::unspecified(Edit::deletion(
|
||||
target.start(),
|
||||
match_token_after(target, checker.locator, |tok| tok == Tok::Equal)
|
||||
.start(),
|
||||
)),
|
||||
))
|
||||
let edit = Edit::deletion(
|
||||
target.start(),
|
||||
match_token_after(target, checker.locator, |tok| tok == Tok::Equal).start(),
|
||||
);
|
||||
Some(Fix::suggested(edit))
|
||||
} else {
|
||||
// If (e.g.) assigning to a constant (`x = 1`), delete the entire statement.
|
||||
let parent = checker.semantic_model().stmts.parent(stmt);
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
match delete_stmt(
|
||||
let edit = delete_stmt(
|
||||
stmt,
|
||||
parent,
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
) {
|
||||
#[allow(deprecated)]
|
||||
Ok(fix) => Some((DeletionKind::Whole, Fix::unspecified(fix))),
|
||||
Err(err) => {
|
||||
error!("Failed to delete unused variable: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
);
|
||||
Some(Fix::suggested(edit).isolate(if parent.is_some() {
|
||||
IsolationLevel::Isolated
|
||||
} else {
|
||||
IsolationLevel::NonOverlapping
|
||||
}))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -252,33 +236,25 @@ fn remove_unused_variable(
|
|||
return if contains_effect(value, |id| checker.semantic_model().is_builtin(id)) {
|
||||
// If the expression is complex (`x = foo()`), remove the assignment,
|
||||
// but preserve the right-hand side.
|
||||
#[allow(deprecated)]
|
||||
Some((
|
||||
DeletionKind::Partial,
|
||||
Fix::unspecified(Edit::deletion(
|
||||
stmt.start(),
|
||||
match_token_after(stmt, checker.locator, |tok| tok == Tok::Equal).start(),
|
||||
)),
|
||||
))
|
||||
let edit = Edit::deletion(
|
||||
stmt.start(),
|
||||
match_token_after(stmt, checker.locator, |tok| tok == Tok::Equal).start(),
|
||||
);
|
||||
Some(Fix::suggested(edit))
|
||||
} else {
|
||||
// If assigning to a constant (`x = 1`), delete the entire statement.
|
||||
let parent = checker.semantic_model().stmts.parent(stmt);
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
match delete_stmt(
|
||||
// If (e.g.) assigning to a constant (`x = 1`), delete the entire statement.
|
||||
let edit = delete_stmt(
|
||||
stmt,
|
||||
parent,
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
) {
|
||||
#[allow(deprecated)]
|
||||
Ok(edit) => Some((DeletionKind::Whole, Fix::unspecified(edit))),
|
||||
Err(err) => {
|
||||
error!("Failed to delete unused variable: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
);
|
||||
Some(Fix::suggested(edit).isolate(if parent.is_some() {
|
||||
IsolationLevel::Isolated
|
||||
} else {
|
||||
IsolationLevel::NonOverlapping
|
||||
}))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -290,19 +266,16 @@ fn remove_unused_variable(
|
|||
for item in items {
|
||||
if let Some(optional_vars) = &item.optional_vars {
|
||||
if optional_vars.range() == range {
|
||||
#[allow(deprecated)]
|
||||
return Some((
|
||||
DeletionKind::Partial,
|
||||
Fix::unspecified(Edit::deletion(
|
||||
item.context_expr.end(),
|
||||
// The end of the `Withitem` is the colon, comma, or closing
|
||||
// parenthesis following the `optional_vars`.
|
||||
match_token(&item.context_expr, checker.locator, |tok| {
|
||||
tok == Tok::Colon || tok == Tok::Comma || tok == Tok::Rpar
|
||||
})
|
||||
.start(),
|
||||
)),
|
||||
));
|
||||
let edit = Edit::deletion(
|
||||
item.context_expr.end(),
|
||||
// The end of the `Withitem` is the colon, comma, or closing
|
||||
// parenthesis following the `optional_vars`.
|
||||
match_token(&item.context_expr, checker.locator, |tok| {
|
||||
tok == Tok::Colon || tok == Tok::Comma || tok == Tok::Rpar
|
||||
})
|
||||
.start(),
|
||||
);
|
||||
return Some(Fix::suggested(edit));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -339,19 +312,15 @@ pub(crate) fn unused_variable(checker: &mut Checker, scope: ScopeId) {
|
|||
|
||||
for (name, range, source) in bindings {
|
||||
let mut diagnostic = Diagnostic::new(UnusedVariable { name }, range);
|
||||
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(source) = source {
|
||||
let stmt = checker.semantic_model().stmts[source];
|
||||
if let Some((kind, fix)) = remove_unused_variable(stmt, range, checker) {
|
||||
if matches!(kind, DeletionKind::Whole) {
|
||||
checker.deletions.insert(RefEquality(stmt));
|
||||
}
|
||||
let parent = checker.semantic_model().stmts.parent(stmt);
|
||||
if let Some(fix) = remove_unused_variable(stmt, parent, range, checker) {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ F401_0.py:2:8: F401 [*] `functools` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `functools`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | from __future__ import all_feature_names
|
||||
2 |-import functools, os
|
||||
2 |+import os
|
||||
|
@ -30,7 +30,7 @@ F401_0.py:6:5: F401 [*] `collections.OrderedDict` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `collections.OrderedDict`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
3 3 | from datetime import datetime
|
||||
4 4 | from collections import (
|
||||
5 5 | Counter,
|
||||
|
@ -50,7 +50,7 @@ F401_0.py:12:8: F401 [*] `logging.handlers` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `logging.handlers`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
9 9 | import multiprocessing.pool
|
||||
10 10 | import multiprocessing.process
|
||||
11 11 | import logging.config
|
||||
|
@ -68,7 +68,7 @@ F401_0.py:32:12: F401 [*] `shelve` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `shelve`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
29 29 | from models import Fruit, Nut, Vegetable
|
||||
30 30 |
|
||||
31 31 | if TYPE_CHECKING:
|
||||
|
@ -88,15 +88,14 @@ F401_0.py:33:12: F401 [*] `importlib` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `importlib`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
30 30 |
|
||||
31 31 | if TYPE_CHECKING:
|
||||
32 32 | import shelve
|
||||
33 |- import importlib
|
||||
33 |+ pass
|
||||
34 34 |
|
||||
35 35 | if TYPE_CHECKING:
|
||||
36 36 | """Hello, world!"""
|
||||
34 33 |
|
||||
35 34 | if TYPE_CHECKING:
|
||||
36 35 | """Hello, world!"""
|
||||
|
||||
F401_0.py:37:12: F401 [*] `pathlib` imported but unused
|
||||
|
|
||||
|
@ -109,7 +108,7 @@ F401_0.py:37:12: F401 [*] `pathlib` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `pathlib`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
34 34 |
|
||||
35 35 | if TYPE_CHECKING:
|
||||
36 36 | """Hello, world!"""
|
||||
|
@ -126,7 +125,7 @@ F401_0.py:52:16: F401 [*] `pickle` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `pickle`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
49 49 | z = multiprocessing.pool.ThreadPool()
|
||||
50 50 |
|
||||
51 51 | def b(self) -> None:
|
||||
|
@ -146,7 +145,7 @@ F401_0.py:93:16: F401 [*] `x` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `x`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
90 90 | # Test: match statements.
|
||||
91 91 | match *0, 1, *2:
|
||||
92 92 | case 0,:
|
||||
|
@ -162,11 +161,10 @@ F401_0.py:94:16: F401 [*] `y` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `y`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
91 91 | match *0, 1, *2:
|
||||
92 92 | case 0,:
|
||||
93 93 | import x
|
||||
94 |- import y
|
||||
94 |+ pass
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ F401_11.py:4:27: F401 [*] `pathlib.PurePath` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `pathlib.PurePath`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | """Test: parsing of nested string annotations."""
|
||||
2 2 |
|
||||
3 3 | from typing import List
|
||||
|
|
|
@ -9,7 +9,7 @@ F401_15.py:5:25: F401 [*] `pathlib.Path` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `pathlib.Path`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
2 2 | from django.db.models import ForeignKey
|
||||
3 3 |
|
||||
4 4 | if TYPE_CHECKING:
|
||||
|
|
|
@ -11,7 +11,7 @@ F401_5.py:2:17: F401 [*] `a.b.c` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `a.b.c`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | """Test: removal of multi-segment and aliases imports."""
|
||||
2 |-from a.b import c
|
||||
3 2 | from d.e import f as g
|
||||
|
@ -29,7 +29,7 @@ F401_5.py:3:17: F401 [*] `d.e.f` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `d.e.f`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | """Test: removal of multi-segment and aliases imports."""
|
||||
2 2 | from a.b import c
|
||||
3 |-from d.e import f as g
|
||||
|
@ -46,7 +46,7 @@ F401_5.py:4:8: F401 [*] `h.i` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `h.i`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | """Test: removal of multi-segment and aliases imports."""
|
||||
2 2 | from a.b import c
|
||||
3 3 | from d.e import f as g
|
||||
|
@ -62,7 +62,7 @@ F401_5.py:5:8: F401 [*] `j.k` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `j.k`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
2 2 | from a.b import c
|
||||
3 3 | from d.e import f as g
|
||||
4 4 | import h.i
|
||||
|
|
|
@ -11,7 +11,7 @@ F401_6.py:7:25: F401 [*] `.background.BackgroundTasks` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `.background.BackgroundTasks`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
4 4 | from .applications import FastAPI as FastAPI
|
||||
5 5 |
|
||||
6 6 | # F401 `background.BackgroundTasks` imported but unused
|
||||
|
@ -30,7 +30,7 @@ F401_6.py:10:29: F401 [*] `.datastructures.UploadFile` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `.datastructures.UploadFile`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
7 7 | from .background import BackgroundTasks
|
||||
8 8 |
|
||||
9 9 | # F401 `datastructures.UploadFile` imported but unused
|
||||
|
@ -49,7 +49,7 @@ F401_6.py:16:8: F401 [*] `background` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `background`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
13 13 | import applications as applications
|
||||
14 14 |
|
||||
15 15 | # F401 `background` imported but unused
|
||||
|
@ -66,7 +66,7 @@ F401_6.py:19:8: F401 [*] `datastructures` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `datastructures`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
16 16 | import background
|
||||
17 17 |
|
||||
18 18 | # F401 `datastructures` imported but unused
|
||||
|
|
|
@ -11,7 +11,7 @@ F401_7.py:30:5: F401 [*] `typing.Union` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `typing.Union`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
27 27 | # This should ignore the first error.
|
||||
28 28 | from typing import (
|
||||
29 29 | Mapping, # noqa: F401
|
||||
|
@ -30,7 +30,7 @@ F401_7.py:66:20: F401 [*] `typing.Awaitable` imported but unused
|
|||
|
|
||||
= help: Remove unused import
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
63 63 | from typing import AsyncIterable, AsyncGenerator # noqa
|
||||
64 64 |
|
||||
65 65 | # This should mark F501 as unused.
|
||||
|
@ -44,7 +44,7 @@ F401_7.py:66:31: F401 [*] `typing.AwaitableGenerator` imported but unused
|
|||
|
|
||||
= help: Remove unused import
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
63 63 | from typing import AsyncIterable, AsyncGenerator # noqa
|
||||
64 64 |
|
||||
65 65 | # This should mark F501 as unused.
|
||||
|
|
|
@ -9,7 +9,7 @@ F401_9.py:4:22: F401 [*] `foo.baz` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo.baz`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | """Test: late-binding of `__all__`."""
|
||||
2 2 |
|
||||
3 3 | __all__ = ("bar",)
|
||||
|
|
|
@ -315,10 +315,9 @@ F841_3.py:61:5: F841 [*] Local variable `x` is assigned to but never used
|
|||
63 |- if a is not None
|
||||
64 |- else b
|
||||
65 |- )
|
||||
61 |+ pass
|
||||
66 62 |
|
||||
67 63 | y = \
|
||||
68 64 | a if a is not None else b
|
||||
66 61 |
|
||||
67 62 | y = \
|
||||
68 63 | a if a is not None else b
|
||||
|
||||
F841_3.py:67:5: F841 [*] Local variable `y` is assigned to but never used
|
||||
|
|
||||
|
|
|
@ -131,10 +131,9 @@ F841_0.py:37:5: F841 [*] Local variable `_discarded` is assigned to but never us
|
|||
35 35 | _ = 1
|
||||
36 36 | __ = 1
|
||||
37 |- _discarded = 1
|
||||
37 |+ pass
|
||||
38 38 |
|
||||
39 39 |
|
||||
40 40 | a = 1
|
||||
38 37 |
|
||||
39 38 |
|
||||
40 39 | a = 1
|
||||
|
||||
F841_0.py:51:9: F841 [*] Local variable `b` is assigned to but never used
|
||||
|
|
||||
|
|
|
@ -11,7 +11,7 @@ future_annotations.py:8:5: F401 [*] `models.Nut` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `models.Nut`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
5 5 |
|
||||
6 6 | from models import (
|
||||
7 7 | Fruit,
|
||||
|
|
|
@ -10,7 +10,7 @@ multi_statement_lines.py:3:12: F401 [*] `foo1` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo1`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 |
|
||||
2 2 | if True:
|
||||
3 |- import foo1; x = 1
|
||||
|
@ -30,7 +30,7 @@ multi_statement_lines.py:4:12: F401 [*] `foo2` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo2`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 |
|
||||
2 2 | if True:
|
||||
3 3 | import foo1; x = 1
|
||||
|
@ -49,7 +49,7 @@ multi_statement_lines.py:7:12: F401 [*] `foo3` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo3`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
4 4 | import foo2; x = 1
|
||||
5 5 |
|
||||
6 6 | if True:
|
||||
|
@ -69,7 +69,7 @@ multi_statement_lines.py:11:12: F401 [*] `foo4` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo4`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
8 8 | x = 1
|
||||
9 9 |
|
||||
10 10 | if True:
|
||||
|
@ -88,7 +88,7 @@ multi_statement_lines.py:16:19: F401 [*] `foo5` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo5`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
13 13 |
|
||||
14 14 |
|
||||
15 15 | if True:
|
||||
|
@ -107,7 +107,7 @@ multi_statement_lines.py:21:17: F401 [*] `foo6` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo6`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
18 18 |
|
||||
19 19 | if True:
|
||||
20 20 | x = 1; \
|
||||
|
@ -126,7 +126,7 @@ multi_statement_lines.py:26:18: F401 [*] `foo7` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo7`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
23 23 |
|
||||
24 24 | if True:
|
||||
25 25 | x = 1 \
|
||||
|
@ -145,7 +145,7 @@ multi_statement_lines.py:30:19: F401 [*] `foo8` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo8`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
27 27 |
|
||||
28 28 |
|
||||
29 29 | if True:
|
||||
|
@ -166,7 +166,7 @@ multi_statement_lines.py:31:23: F401 [*] `foo9` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo9`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
28 28 |
|
||||
29 29 | if True:
|
||||
30 30 | x = 1; import foo8; x = 1
|
||||
|
@ -186,7 +186,7 @@ multi_statement_lines.py:35:16: F401 [*] `foo10` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo10`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
32 32 |
|
||||
33 33 | if True:
|
||||
34 34 | x = 1; \
|
||||
|
@ -207,7 +207,7 @@ multi_statement_lines.py:40:17: F401 [*] `foo11` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo11`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
37 37 |
|
||||
38 38 | if True:
|
||||
39 39 | x = 1 \
|
||||
|
@ -227,7 +227,7 @@ multi_statement_lines.py:46:8: F401 [*] `foo12` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo12`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
43 43 |
|
||||
44 44 | # Continuation, but not as the last content in the file.
|
||||
45 45 | x = 1; \
|
||||
|
@ -247,7 +247,7 @@ multi_statement_lines.py:51:8: F401 [*] `foo13` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `foo13`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
48 48 | # Continuation, followed by end-of-file. (Removing `import foo` would cause a syntax
|
||||
49 49 | # error.)
|
||||
50 50 | x = 1; \
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
use log::error;
|
||||
use rustpython_parser::ast::{self, Constant, Expr, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, IsolationLevel};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{is_const_none, ReturnStatementVisitor};
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
@ -57,14 +55,12 @@ pub(crate) fn useless_return<'a>(
|
|||
return;
|
||||
}
|
||||
|
||||
// Skip empty functions.
|
||||
if body.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the last statement in the function.
|
||||
let last_stmt = body.last().unwrap();
|
||||
if !matches!(last_stmt, Stmt::Return(_)) {
|
||||
let Some(last_stmt) = body.last() else {
|
||||
// Skip empty functions.
|
||||
return;
|
||||
};
|
||||
if !last_stmt.is_return_stmt() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -107,26 +103,14 @@ pub(crate) fn useless_return<'a>(
|
|||
|
||||
let mut diagnostic = Diagnostic::new(UselessReturn, last_stmt.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
match delete_stmt(
|
||||
let edit = autofix::edits::delete_stmt(
|
||||
last_stmt,
|
||||
Some(stmt),
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
) {
|
||||
Ok(edit) => {
|
||||
if edit.is_deletion() || edit.content() == Some("pass") {
|
||||
checker.deletions.insert(RefEquality(last_stmt));
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(edit));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to delete `return` statement: {}", e);
|
||||
}
|
||||
};
|
||||
);
|
||||
diagnostic.set_fix(Fix::automatic(edit).isolate(IsolationLevel::Isolated));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ useless_return.py:6:5: PLR1711 [*] Useless `return` statement at end of function
|
|||
|
|
||||
= help: Remove useless `return` statement
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
3 3 |
|
||||
4 4 | def print_python_version():
|
||||
5 5 | print(sys.version)
|
||||
|
@ -28,7 +28,7 @@ useless_return.py:11:5: PLR1711 [*] Useless `return` statement at end of functio
|
|||
|
|
||||
= help: Remove useless `return` statement
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
8 8 |
|
||||
9 9 | def print_python_version():
|
||||
10 10 | print(sys.version)
|
||||
|
@ -46,7 +46,7 @@ useless_return.py:16:5: PLR1711 [*] Useless `return` statement at end of functio
|
|||
|
|
||||
= help: Remove useless `return` statement
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
13 13 |
|
||||
14 14 | def print_python_version():
|
||||
15 15 | print(sys.version)
|
||||
|
@ -64,7 +64,7 @@ useless_return.py:22:9: PLR1711 [*] Useless `return` statement at end of functio
|
|||
|
|
||||
= help: Remove useless `return` statement
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
19 19 | class SomeClass:
|
||||
20 20 | def print_python_version(self):
|
||||
21 21 | print(sys.version)
|
||||
|
@ -82,7 +82,7 @@ useless_return.py:50:5: PLR1711 [*] Useless `return` statement at end of functio
|
|||
|
|
||||
= help: Remove useless `return` statement
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
47 47 | def print_python_version():
|
||||
48 48 | """This function returns None."""
|
||||
49 49 | print(sys.version)
|
||||
|
@ -100,7 +100,7 @@ useless_return.py:60:9: PLR1711 [*] Useless `return` statement at end of functio
|
|||
|
|
||||
= help: Remove useless `return` statement
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
57 57 |
|
||||
58 58 | def get(self, key: str) -> None:
|
||||
59 59 | print(f"{key} not found")
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use log::error;
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use rustpython_parser::ast::{self, Cmpop, Constant, Expr, Ranged, Stmt};
|
||||
|
@ -9,7 +8,6 @@ use rustpython_parser::{lexer, Mode, Tok};
|
|||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
use ruff_python_ast::whitespace::indentation;
|
||||
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
|
@ -180,7 +178,7 @@ fn compare_version(if_version: &[u32], py_version: PythonVersion, or_equal: bool
|
|||
|
||||
/// Convert a [`Stmt::If`], retaining the `else`.
|
||||
fn fix_py2_block(
|
||||
checker: &mut Checker,
|
||||
checker: &Checker,
|
||||
stmt: &Stmt,
|
||||
orelse: &[Stmt],
|
||||
block: &BlockMetadata,
|
||||
|
@ -190,31 +188,16 @@ fn fix_py2_block(
|
|||
// Delete the entire statement. If this is an `elif`, know it's the only child
|
||||
// of its parent, so avoid passing in the parent at all. Otherwise,
|
||||
// `delete_stmt` will erroneously include a `pass`.
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
let defined_by = checker.semantic_model().stmt();
|
||||
let defined_in = checker.semantic_model().stmt_parent();
|
||||
return match delete_stmt(
|
||||
defined_by,
|
||||
if matches!(block.leading_token.tok, StartTok::If) {
|
||||
defined_in
|
||||
} else {
|
||||
None
|
||||
},
|
||||
&deleted,
|
||||
let stmt = checker.semantic_model().stmt();
|
||||
let parent = checker.semantic_model().stmt_parent();
|
||||
let edit = delete_stmt(
|
||||
stmt,
|
||||
if matches!(block.leading_token.tok, StartTok::If) { parent } else { None },
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
) {
|
||||
Ok(edit) => {
|
||||
checker.deletions.insert(RefEquality(defined_by));
|
||||
#[allow(deprecated)]
|
||||
Some(Fix::unspecified(edit))
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to remove block: {}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
);
|
||||
return Some(Fix::suggested(edit));
|
||||
};
|
||||
|
||||
match (&leading_token.tok, &trailing_token.tok) {
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use rustpython_parser::ast::{Alias, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, IsolationLevel};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
@ -80,6 +78,7 @@ pub(crate) fn unnecessary_builtin_import(
|
|||
_ => return,
|
||||
};
|
||||
|
||||
// Do this with a filter?
|
||||
let mut unused_imports: Vec<&Alias> = vec![];
|
||||
for alias in names {
|
||||
if alias.asname.is_some() {
|
||||
|
@ -93,6 +92,7 @@ pub(crate) fn unnecessary_builtin_import(
|
|||
if unused_imports.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryBuiltinImport {
|
||||
names: unused_imports
|
||||
|
@ -103,33 +103,28 @@ pub(crate) fn unnecessary_builtin_import(
|
|||
},
|
||||
stmt.range(),
|
||||
);
|
||||
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
let defined_by = checker.semantic_model().stmt();
|
||||
let defined_in = checker.semantic_model().stmt_parent();
|
||||
let unused_imports: Vec<String> = unused_imports
|
||||
.iter()
|
||||
.map(|alias| format!("{module}.{}", alias.name))
|
||||
.collect();
|
||||
match autofix::edits::remove_unused_imports(
|
||||
unused_imports.iter().map(String::as_str),
|
||||
defined_by,
|
||||
defined_in,
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
) {
|
||||
Ok(edit) => {
|
||||
if edit.is_deletion() || edit.content() == Some("pass") {
|
||||
checker.deletions.insert(RefEquality(defined_by));
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(edit));
|
||||
}
|
||||
Err(e) => error!("Failed to remove builtin import: {e}"),
|
||||
}
|
||||
diagnostic.try_set_fix(|| {
|
||||
let stmt = checker.semantic_model().stmt();
|
||||
let parent = checker.semantic_model().stmt_parent();
|
||||
let unused_imports: Vec<String> = unused_imports
|
||||
.iter()
|
||||
.map(|alias| format!("{module}.{}", alias.name))
|
||||
.collect();
|
||||
let edit = autofix::edits::remove_unused_imports(
|
||||
unused_imports.iter().map(String::as_str),
|
||||
stmt,
|
||||
parent,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
)?;
|
||||
Ok(Fix::suggested(edit).isolate(if parent.is_some() {
|
||||
IsolationLevel::Isolated
|
||||
} else {
|
||||
IsolationLevel::NonOverlapping
|
||||
}))
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use rustpython_parser::ast::{Alias, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, IsolationLevel};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
@ -85,31 +83,27 @@ pub(crate) fn unnecessary_future_import(checker: &mut Checker, stmt: &Stmt, name
|
|||
);
|
||||
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
let defined_by = checker.semantic_model().stmt();
|
||||
let defined_in = checker.semantic_model().stmt_parent();
|
||||
let unused_imports: Vec<String> = unused_imports
|
||||
.iter()
|
||||
.map(|alias| format!("__future__.{}", alias.name))
|
||||
.collect();
|
||||
match autofix::edits::remove_unused_imports(
|
||||
unused_imports.iter().map(String::as_str),
|
||||
defined_by,
|
||||
defined_in,
|
||||
&deleted,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix.is_deletion() || fix.content() == Some("pass") {
|
||||
checker.deletions.insert(RefEquality(defined_by));
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(fix));
|
||||
}
|
||||
Err(e) => error!("Failed to remove `__future__` import: {e}"),
|
||||
}
|
||||
diagnostic.try_set_fix(|| {
|
||||
let unused_imports: Vec<String> = unused_imports
|
||||
.iter()
|
||||
.map(|alias| format!("__future__.{}", alias.name))
|
||||
.collect();
|
||||
let stmt = checker.semantic_model().stmt();
|
||||
let parent = checker.semantic_model().stmt_parent();
|
||||
let edit = autofix::edits::remove_unused_imports(
|
||||
unused_imports.iter().map(String::as_str),
|
||||
stmt,
|
||||
parent,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
)?;
|
||||
Ok(Fix::suggested(edit).isolate(if parent.is_some() {
|
||||
IsolationLevel::Isolated
|
||||
} else {
|
||||
IsolationLevel::NonOverlapping
|
||||
}))
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use log::error;
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{self, Expr, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, IsolationLevel};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::edits;
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
@ -24,25 +21,6 @@ impl AlwaysAutofixableViolation for UselessMetaclassType {
|
|||
}
|
||||
}
|
||||
|
||||
fn rule(targets: &[Expr], value: &Expr, location: TextRange) -> Option<Diagnostic> {
|
||||
if targets.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
let Expr::Name(ast::ExprName { id, .. }) = targets.first().unwrap() else {
|
||||
return None;
|
||||
};
|
||||
if id != "__metaclass__" {
|
||||
return None;
|
||||
}
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value else {
|
||||
return None;
|
||||
};
|
||||
if id != "type" {
|
||||
return None;
|
||||
}
|
||||
Some(Diagnostic::new(UselessMetaclassType, location))
|
||||
}
|
||||
|
||||
/// UP001
|
||||
pub(crate) fn useless_metaclass_type(
|
||||
checker: &mut Checker,
|
||||
|
@ -50,31 +28,34 @@ pub(crate) fn useless_metaclass_type(
|
|||
value: &Expr,
|
||||
targets: &[Expr],
|
||||
) {
|
||||
let Some(mut diagnostic) =
|
||||
rule(targets, value, stmt.range()) else {
|
||||
return;
|
||||
};
|
||||
if targets.len() != 1 {
|
||||
return;
|
||||
}
|
||||
let Expr::Name(ast::ExprName { id, .. }) = targets.first().unwrap() else {
|
||||
return ;
|
||||
};
|
||||
if id != "__metaclass__" {
|
||||
return;
|
||||
}
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value else {
|
||||
return ;
|
||||
};
|
||||
if id != "type" {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(UselessMetaclassType, stmt.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(Into::into).collect();
|
||||
let defined_by = checker.semantic_model().stmt();
|
||||
let defined_in = checker.semantic_model().stmt_parent();
|
||||
match edits::delete_stmt(
|
||||
defined_by,
|
||||
defined_in,
|
||||
&deleted,
|
||||
let stmt = checker.semantic_model().stmt();
|
||||
let parent = checker.semantic_model().stmt_parent();
|
||||
let edit = autofix::edits::delete_stmt(
|
||||
stmt,
|
||||
parent,
|
||||
checker.locator,
|
||||
checker.indexer,
|
||||
checker.stylist,
|
||||
) {
|
||||
Ok(edit) => {
|
||||
if edit.is_deletion() || edit.content() == Some("pass") {
|
||||
checker.deletions.insert(RefEquality(defined_by));
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(edit));
|
||||
}
|
||||
Err(e) => error!("Failed to fix remove metaclass type: {e}"),
|
||||
}
|
||||
);
|
||||
diagnostic.set_fix(Fix::automatic(edit).isolate(IsolationLevel::Isolated));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ UP001.py:2:5: UP001 [*] `__metaclass__ = type` is implied
|
|||
|
|
||||
= help: Remove `metaclass = type`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | class A:
|
||||
2 |- __metaclass__ = type
|
||||
2 |+ pass
|
||||
|
@ -27,7 +27,7 @@ UP001.py:6:5: UP001 [*] `__metaclass__ = type` is implied
|
|||
|
|
||||
= help: Remove `metaclass = type`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
3 3 |
|
||||
4 4 |
|
||||
5 5 | class B:
|
||||
|
|
|
@ -146,10 +146,9 @@ UP010.py:10:5: UP010 [*] Unnecessary `__future__` import `generators` for target
|
|||
8 8 | if True:
|
||||
9 9 | from __future__ import generator_stop
|
||||
10 |- from __future__ import generators
|
||||
10 |+ pass
|
||||
11 11 |
|
||||
12 12 | if True:
|
||||
13 13 | from __future__ import generator_stop
|
||||
11 10 |
|
||||
12 11 | if True:
|
||||
13 12 | from __future__ import generator_stop
|
||||
|
||||
UP010.py:13:5: UP010 [*] Unnecessary `__future__` import `generator_stop` for target Python version
|
||||
|
|
||||
|
|
|
@ -228,7 +228,7 @@ RUF100_0.py:85:8: F401 [*] `shelve` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `shelve`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
82 82 |
|
||||
83 83 | import collections # noqa
|
||||
84 84 | import os # noqa: F401, RUF100
|
||||
|
|
|
@ -11,7 +11,7 @@ RUF100_1.py:37:9: F401 [*] `typing.Union` imported but unused
|
|||
|
|
||||
= help: Remove unused import: `typing.Union`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
34 34 | # This should ignore the first error.
|
||||
35 35 | from typing import (
|
||||
36 36 | Mapping, # noqa: F401
|
||||
|
@ -112,7 +112,7 @@ RUF100_1.py:89:24: F401 [*] `typing.Awaitable` imported but unused
|
|||
|
|
||||
= help: Remove unused import
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
86 86 |
|
||||
87 87 | def f():
|
||||
88 88 | # This should mark F501 as unused.
|
||||
|
@ -128,7 +128,7 @@ RUF100_1.py:89:35: F401 [*] `typing.AwaitableGenerator` imported but unused
|
|||
|
|
||||
= help: Remove unused import
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
86 86 |
|
||||
87 87 | def f():
|
||||
88 88 | # This should mark F501 as unused.
|
||||
|
|
|
@ -40,9 +40,9 @@ pub(crate) fn test_snippet(contents: &str, settings: &Settings) -> Vec<Message>
|
|||
}
|
||||
|
||||
/// A convenient wrapper around [`check_path`], that additionally
|
||||
/// asserts that autofixes converge after 10 iterations.
|
||||
/// asserts that autofixes converge after a fixed number of iterations.
|
||||
fn test_contents(contents: &str, path: &Path, settings: &Settings) -> Vec<Message> {
|
||||
static MAX_ITERATIONS: usize = 10;
|
||||
static MAX_ITERATIONS: usize = 20;
|
||||
|
||||
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
|
||||
let locator = Locator::new(contents);
|
||||
|
|
|
@ -93,7 +93,7 @@ fn stdin_json() -> Result<()> {
|
|||
"code": "F401",
|
||||
"message": "`os` imported but unused",
|
||||
"fix": {{
|
||||
"applicability": "Unspecified",
|
||||
"applicability": "Automatic",
|
||||
"message": "Remove unused import: `os`",
|
||||
"edits": [
|
||||
{{
|
||||
|
|
|
@ -8,7 +8,9 @@ rust-version = { workspace = true }
|
|||
[lib]
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
log = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde = { workspace = true, optional = true, features = [] }
|
||||
|
|
|
@ -27,12 +27,24 @@ pub enum Applicability {
|
|||
Unspecified,
|
||||
}
|
||||
|
||||
/// Indicates the level of isolation required to apply a fix.
|
||||
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum IsolationLevel {
|
||||
/// The fix should be applied in isolation.
|
||||
Isolated,
|
||||
/// The fix should be applied as long as it does not overlap with any other fixes.
|
||||
#[default]
|
||||
NonOverlapping,
|
||||
}
|
||||
|
||||
/// A collection of [`Edit`] elements to be applied to a source file.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Fix {
|
||||
edits: Vec<Edit>,
|
||||
applicability: Applicability,
|
||||
isolation_level: IsolationLevel,
|
||||
}
|
||||
|
||||
impl Fix {
|
||||
|
@ -44,6 +56,7 @@ impl Fix {
|
|||
Self {
|
||||
edits: vec![edit],
|
||||
applicability: Applicability::Unspecified,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +68,7 @@ impl Fix {
|
|||
Self {
|
||||
edits: std::iter::once(edit).chain(rest.into_iter()).collect(),
|
||||
applicability: Applicability::Unspecified,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,6 +77,7 @@ impl Fix {
|
|||
Self {
|
||||
edits: vec![edit],
|
||||
applicability: Applicability::Automatic,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,6 +86,7 @@ impl Fix {
|
|||
Self {
|
||||
edits: std::iter::once(edit).chain(rest.into_iter()).collect(),
|
||||
applicability: Applicability::Automatic,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,6 +95,7 @@ impl Fix {
|
|||
Self {
|
||||
edits: vec![edit],
|
||||
applicability: Applicability::Suggested,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,6 +104,7 @@ impl Fix {
|
|||
Self {
|
||||
edits: std::iter::once(edit).chain(rest.into_iter()).collect(),
|
||||
applicability: Applicability::Suggested,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,6 +113,7 @@ impl Fix {
|
|||
Self {
|
||||
edits: vec![edit],
|
||||
applicability: Applicability::Manual,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,6 +122,7 @@ impl Fix {
|
|||
Self {
|
||||
edits: std::iter::once(edit).chain(rest.into_iter()).collect(),
|
||||
applicability: Applicability::Manual,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +140,20 @@ impl Fix {
|
|||
self.edits
|
||||
}
|
||||
|
||||
/// Return the [`Applicability`] of the [`Fix`].
|
||||
pub fn applicability(&self) -> Applicability {
|
||||
self.applicability
|
||||
}
|
||||
|
||||
/// Return the [`IsolationLevel`] of the [`Fix`].
|
||||
pub fn isolation(&self) -> IsolationLevel {
|
||||
self.isolation_level
|
||||
}
|
||||
|
||||
/// Create a new [`Fix`] with the given [`IsolationLevel`].
|
||||
#[must_use]
|
||||
pub fn isolate(mut self, isolation: IsolationLevel) -> Self {
|
||||
self.isolation_level = isolation;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pub use diagnostic::{Diagnostic, DiagnosticKind};
|
||||
pub use edit::Edit;
|
||||
pub use fix::{Applicability, Fix};
|
||||
pub use fix::{Applicability, Fix, IsolationLevel};
|
||||
pub use violation::{AlwaysAutofixableViolation, AutofixKind, Violation};
|
||||
|
||||
mod diagnostic;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue