mirror of https://github.com/astral-sh/ruff
Extract lower-level edit utility from autofix module (#4737)
This commit is contained in:
parent
399eb84d5e
commit
0b471197dc
|
|
@ -0,0 +1,125 @@
|
|||
//! Interface for editing code snippets. These functions take statements or expressions as input,
|
||||
//! and return the modified code snippet as output.
|
||||
use anyhow::{bail, Result};
|
||||
use libcst_native::{
|
||||
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
|
||||
};
|
||||
use rustpython_parser::ast::{Ranged, Stmt};
|
||||
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_statement;
|
||||
|
||||
/// Given an import statement, remove any imports that are specified in the `imports` iterator.
|
||||
///
|
||||
/// Returns `Ok(None)` if the statement is empty after removing the imports.
|
||||
pub(crate) fn remove_imports<'a>(
|
||||
imports: impl Iterator<Item = &'a str>,
|
||||
stmt: &Stmt,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Option<String>> {
|
||||
let module_text = locator.slice(stmt.range());
|
||||
let mut tree = match_statement(module_text)?;
|
||||
|
||||
let Statement::Simple(body) = &mut tree else {
|
||||
bail!("Expected Statement::Simple");
|
||||
};
|
||||
|
||||
let (aliases, import_module) = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
|
||||
Some(SmallStatement::ImportFrom(import_body)) => {
|
||||
if let ImportNames::Aliases(names) = &mut import_body.names {
|
||||
(
|
||||
names,
|
||||
Some((&import_body.relative, import_body.module.as_ref())),
|
||||
)
|
||||
} else if let ImportNames::Star(..) = &import_body.names {
|
||||
// Special-case: if the import is a `from ... import *`, then we delete the
|
||||
// entire statement.
|
||||
let mut found_star = false;
|
||||
for import in imports {
|
||||
let full_name = match import_body.module.as_ref() {
|
||||
Some(module_name) => format!("{}.*", compose_module_path(module_name)),
|
||||
None => "*".to_string(),
|
||||
};
|
||||
if import == full_name {
|
||||
found_star = true;
|
||||
} else {
|
||||
bail!("Expected \"*\" for unused import (got: \"{}\")", import);
|
||||
}
|
||||
}
|
||||
if !found_star {
|
||||
bail!("Expected \'*\' for unused import");
|
||||
}
|
||||
return Ok(None);
|
||||
} else {
|
||||
bail!("Expected: ImportNames::Aliases | ImportNames::Star");
|
||||
}
|
||||
}
|
||||
_ => bail!("Expected: SmallStatement::ImportFrom | SmallStatement::Import"),
|
||||
};
|
||||
|
||||
// Preserve the trailing comma (or not) from the last entry.
|
||||
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
|
||||
|
||||
for import in imports {
|
||||
let alias_index = aliases.iter().position(|alias| {
|
||||
let full_name = match import_module {
|
||||
Some((relative, module)) => {
|
||||
let module = module.map(compose_module_path);
|
||||
let member = compose_module_path(&alias.name);
|
||||
let mut full_name = String::with_capacity(
|
||||
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
|
||||
);
|
||||
for _ in 0..relative.len() {
|
||||
full_name.push('.');
|
||||
}
|
||||
if let Some(module) = module {
|
||||
full_name.push_str(&module);
|
||||
full_name.push('.');
|
||||
}
|
||||
full_name.push_str(&member);
|
||||
full_name
|
||||
}
|
||||
None => compose_module_path(&alias.name),
|
||||
};
|
||||
full_name == import
|
||||
});
|
||||
|
||||
if let Some(index) = alias_index {
|
||||
aliases.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
// But avoid destroying any trailing comments.
|
||||
if let Some(alias) = aliases.last_mut() {
|
||||
let has_comment = if let Some(comma) = &alias.comma {
|
||||
match &comma.whitespace_after {
|
||||
ParenthesizableWhitespace::SimpleWhitespace(_) => false,
|
||||
ParenthesizableWhitespace::ParenthesizedWhitespace(whitespace) => {
|
||||
whitespace.first_line.comment.is_some()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !has_comment {
|
||||
alias.comma = trailing_comma;
|
||||
}
|
||||
}
|
||||
|
||||
if aliases.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut state = CodegenState {
|
||||
default_newline: &stylist.line_ending(),
|
||||
default_indent: stylist.indentation(),
|
||||
..CodegenState::default()
|
||||
};
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Some(state.to_string()))
|
||||
}
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
//! Interface for generating autofix edits from higher-level actions (e.g., "remove an argument").
|
||||
use anyhow::{bail, Result};
|
||||
use itertools::Itertools;
|
||||
use libcst_native::{
|
||||
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
|
||||
};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustpython_parser::ast::{self, Excepthandler, Expr, Keyword, Ranged, Stmt};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
|
@ -13,8 +10,174 @@ use ruff_newlines::NewlineWithTrailingNewline;
|
|||
use ruff_python_ast::helpers;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_statement;
|
||||
use crate::autofix::codemods;
|
||||
|
||||
/// Return the `Fix` to use when deleting a `Stmt`.
|
||||
///
|
||||
/// In some cases, this is as simple as deleting the `Range` of the `Stmt`
|
||||
/// itself. However, there are a few exceptions:
|
||||
/// - If the `Stmt` is _not_ the terminal statement in a multi-statement line,
|
||||
/// we need to delete up to the start of the next statement (and avoid
|
||||
/// deleting any content that precedes the statement).
|
||||
/// - If the `Stmt` is the terminal statement in a multi-statement line, we need
|
||||
/// to avoid deleting any content that precedes the statement.
|
||||
/// - If the `Stmt` has no trailing and leading content, then it's convenient to
|
||||
/// remove the entire start and end lines.
|
||||
/// - If the `Stmt` is the last statement in its parent body, replace it with a
|
||||
/// `pass` instead.
|
||||
pub(crate) fn delete_stmt(
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Edit> {
|
||||
if parent
|
||||
.map(|parent| is_lone_child(stmt, parent, deleted))
|
||||
.map_or(Ok(None), |v| v.map(Some))?
|
||||
.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()))
|
||||
} else {
|
||||
Ok(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) {
|
||||
Edit::range_deletion(stmt.range())
|
||||
} else if helpers::preceded_by_continuation(stmt, indexer, locator) {
|
||||
if is_end_of_file(stmt, locator) && locator.is_at_start_of_line(stmt.start()) {
|
||||
// Special-case: a file can't end in a continuation.
|
||||
Edit::range_replacement(stylist.line_ending().to_string(), stmt.range())
|
||||
} else {
|
||||
Edit::range_deletion(stmt.range())
|
||||
}
|
||||
} else {
|
||||
let range = locator.full_lines_range(stmt.range());
|
||||
Edit::range_deletion(range)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a `Fix` to remove any unused imports from an `import` statement.
|
||||
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),
|
||||
Some(content) => Ok(Edit::range_replacement(content, stmt.range())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic function to remove arguments or keyword arguments in function
|
||||
/// calls and class definitions. (For classes `args` should be considered
|
||||
/// `bases`)
|
||||
///
|
||||
/// Supports the removal of parentheses when this is the only (kw)arg left.
|
||||
/// For this behavior, set `remove_parentheses` to `true`.
|
||||
pub(crate) fn remove_argument(
|
||||
locator: &Locator,
|
||||
call_at: TextSize,
|
||||
expr_range: TextRange,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
remove_parentheses: bool,
|
||||
) -> Result<Edit> {
|
||||
// TODO(sbrugman): Preserve trailing comments.
|
||||
let contents = locator.after(call_at);
|
||||
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
|
||||
let n_arguments = keywords.len() + args.len();
|
||||
if n_arguments == 0 {
|
||||
bail!("No arguments or keywords to remove");
|
||||
}
|
||||
|
||||
if n_arguments == 1 {
|
||||
// Case 1: there is only one argument.
|
||||
let mut count: usize = 0;
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
fix_start = Some(if remove_parentheses {
|
||||
range.start()
|
||||
} else {
|
||||
range.start() + TextSize::from(1)
|
||||
});
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Rpar) {
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
fix_end = Some(if remove_parentheses {
|
||||
range.end()
|
||||
} else {
|
||||
range.end() - TextSize::from(1)
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if args
|
||||
.iter()
|
||||
.map(Expr::start)
|
||||
.chain(keywords.iter().map(Keyword::start))
|
||||
.any(|location| location > expr_range.start())
|
||||
{
|
||||
// Case 2: argument or keyword is _not_ the last node.
|
||||
let mut seen_comma = false;
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if seen_comma {
|
||||
if matches!(tok, Tok::NonLogicalNewline) {
|
||||
// Also delete any non-logical newlines after the comma.
|
||||
continue;
|
||||
}
|
||||
fix_end = Some(if matches!(tok, Tok::Newline) {
|
||||
range.end()
|
||||
} else {
|
||||
range.start()
|
||||
});
|
||||
break;
|
||||
}
|
||||
if range.start() == expr_range.start() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
if fix_start.is_some() && matches!(tok, Tok::Comma) {
|
||||
seen_comma = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case 3: argument or keyword is the last node, so we have to find the last
|
||||
// comma in the stmt.
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if range.start() == expr_range.start() {
|
||||
fix_end = Some(expr_range.end());
|
||||
break;
|
||||
}
|
||||
if matches!(tok, Tok::Comma) {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
|
||||
_ => {
|
||||
bail!("No fix could be constructed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if a body contains only a single statement, taking into account
|
||||
/// deleted.
|
||||
|
|
@ -152,274 +315,6 @@ fn is_end_of_file(stmt: &Stmt, locator: &Locator) -> bool {
|
|||
stmt.end() == locator.contents().text_len()
|
||||
}
|
||||
|
||||
/// Return the `Fix` to use when deleting a `Stmt`.
|
||||
///
|
||||
/// In some cases, this is as simple as deleting the `Range` of the `Stmt`
|
||||
/// itself. However, there are a few exceptions:
|
||||
/// - If the `Stmt` is _not_ the terminal statement in a multi-statement line,
|
||||
/// we need to delete up to the start of the next statement (and avoid
|
||||
/// deleting any content that precedes the statement).
|
||||
/// - If the `Stmt` is the terminal statement in a multi-statement line, we need
|
||||
/// to avoid deleting any content that precedes the statement.
|
||||
/// - If the `Stmt` has no trailing and leading content, then it's convenient to
|
||||
/// remove the entire start and end lines.
|
||||
/// - If the `Stmt` is the last statement in its parent body, replace it with a
|
||||
/// `pass` instead.
|
||||
pub(crate) fn delete_stmt(
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Edit> {
|
||||
if parent
|
||||
.map(|parent| is_lone_child(stmt, parent, deleted))
|
||||
.map_or(Ok(None), |v| v.map(Some))?
|
||||
.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()))
|
||||
} else {
|
||||
Ok(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) {
|
||||
Edit::range_deletion(stmt.range())
|
||||
} else if helpers::preceded_by_continuation(stmt, indexer, locator) {
|
||||
if is_end_of_file(stmt, locator) && locator.is_at_start_of_line(stmt.start()) {
|
||||
// Special-case: a file can't end in a continuation.
|
||||
Edit::range_replacement(stylist.line_ending().to_string(), stmt.range())
|
||||
} else {
|
||||
Edit::range_deletion(stmt.range())
|
||||
}
|
||||
} else {
|
||||
let range = locator.full_lines_range(stmt.range());
|
||||
Edit::range_deletion(range)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a `Fix` to remove any unused imports from an `import` statement.
|
||||
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> {
|
||||
let module_text = locator.slice(stmt.range());
|
||||
let mut tree = match_statement(module_text)?;
|
||||
|
||||
let Statement::Simple(body) = &mut tree else {
|
||||
bail!("Expected Statement::Simple");
|
||||
};
|
||||
|
||||
let (aliases, import_module) = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
|
||||
Some(SmallStatement::ImportFrom(import_body)) => {
|
||||
if let ImportNames::Aliases(names) = &mut import_body.names {
|
||||
(
|
||||
names,
|
||||
Some((&import_body.relative, import_body.module.as_ref())),
|
||||
)
|
||||
} else if let ImportNames::Star(..) = &import_body.names {
|
||||
// Special-case: if the import is a `from ... import *`, then we delete the
|
||||
// entire statement.
|
||||
let mut found_star = false;
|
||||
for unused_import in unused_imports {
|
||||
let full_name = match import_body.module.as_ref() {
|
||||
Some(module_name) => format!("{}.*", compose_module_path(module_name)),
|
||||
None => "*".to_string(),
|
||||
};
|
||||
if unused_import == full_name {
|
||||
found_star = true;
|
||||
} else {
|
||||
bail!(
|
||||
"Expected \"*\" for unused import (got: \"{}\")",
|
||||
unused_import
|
||||
);
|
||||
}
|
||||
}
|
||||
if !found_star {
|
||||
bail!("Expected \'*\' for unused import");
|
||||
}
|
||||
return delete_stmt(stmt, parent, deleted, locator, indexer, stylist);
|
||||
} else {
|
||||
bail!("Expected: ImportNames::Aliases | ImportNames::Star");
|
||||
}
|
||||
}
|
||||
_ => bail!("Expected: SmallStatement::ImportFrom | SmallStatement::Import"),
|
||||
};
|
||||
|
||||
// Preserve the trailing comma (or not) from the last entry.
|
||||
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
|
||||
|
||||
for unused_import in unused_imports {
|
||||
let alias_index = aliases.iter().position(|alias| {
|
||||
let full_name = match import_module {
|
||||
Some((relative, module)) => {
|
||||
let module = module.map(compose_module_path);
|
||||
let member = compose_module_path(&alias.name);
|
||||
let mut full_name = String::with_capacity(
|
||||
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
|
||||
);
|
||||
for _ in 0..relative.len() {
|
||||
full_name.push('.');
|
||||
}
|
||||
if let Some(module) = module {
|
||||
full_name.push_str(&module);
|
||||
full_name.push('.');
|
||||
}
|
||||
full_name.push_str(&member);
|
||||
full_name
|
||||
}
|
||||
None => compose_module_path(&alias.name),
|
||||
};
|
||||
full_name == unused_import
|
||||
});
|
||||
|
||||
if let Some(index) = alias_index {
|
||||
aliases.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
// But avoid destroying any trailing comments.
|
||||
if let Some(alias) = aliases.last_mut() {
|
||||
let has_comment = if let Some(comma) = &alias.comma {
|
||||
match &comma.whitespace_after {
|
||||
ParenthesizableWhitespace::SimpleWhitespace(_) => false,
|
||||
ParenthesizableWhitespace::ParenthesizedWhitespace(whitespace) => {
|
||||
whitespace.first_line.comment.is_some()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !has_comment {
|
||||
alias.comma = trailing_comma;
|
||||
}
|
||||
}
|
||||
|
||||
if aliases.is_empty() {
|
||||
delete_stmt(stmt, parent, deleted, locator, indexer, stylist)
|
||||
} else {
|
||||
let mut state = CodegenState {
|
||||
default_newline: &stylist.line_ending(),
|
||||
default_indent: stylist.indentation(),
|
||||
..CodegenState::default()
|
||||
};
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Edit::range_replacement(state.to_string(), stmt.range()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic function to remove arguments or keyword arguments in function
|
||||
/// calls and class definitions. (For classes `args` should be considered
|
||||
/// `bases`)
|
||||
///
|
||||
/// Supports the removal of parentheses when this is the only (kw)arg left.
|
||||
/// For this behavior, set `remove_parentheses` to `true`.
|
||||
pub(crate) fn remove_argument(
|
||||
locator: &Locator,
|
||||
call_at: TextSize,
|
||||
expr_range: TextRange,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
remove_parentheses: bool,
|
||||
) -> Result<Edit> {
|
||||
// TODO(sbrugman): Preserve trailing comments.
|
||||
let contents = locator.after(call_at);
|
||||
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
|
||||
let n_arguments = keywords.len() + args.len();
|
||||
if n_arguments == 0 {
|
||||
bail!("No arguments or keywords to remove");
|
||||
}
|
||||
|
||||
if n_arguments == 1 {
|
||||
// Case 1: there is only one argument.
|
||||
let mut count: usize = 0;
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
fix_start = Some(if remove_parentheses {
|
||||
range.start()
|
||||
} else {
|
||||
range.start() + TextSize::from(1)
|
||||
});
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Rpar) {
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
fix_end = Some(if remove_parentheses {
|
||||
range.end()
|
||||
} else {
|
||||
range.end() - TextSize::from(1)
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if args
|
||||
.iter()
|
||||
.map(Expr::start)
|
||||
.chain(keywords.iter().map(Keyword::start))
|
||||
.any(|location| location > expr_range.start())
|
||||
{
|
||||
// Case 2: argument or keyword is _not_ the last node.
|
||||
let mut seen_comma = false;
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if seen_comma {
|
||||
if matches!(tok, Tok::NonLogicalNewline) {
|
||||
// Also delete any non-logical newlines after the comma.
|
||||
continue;
|
||||
}
|
||||
fix_end = Some(if matches!(tok, Tok::Newline) {
|
||||
range.end()
|
||||
} else {
|
||||
range.start()
|
||||
});
|
||||
break;
|
||||
}
|
||||
if range.start() == expr_range.start() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
if fix_start.is_some() && matches!(tok, Tok::Comma) {
|
||||
seen_comma = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case 3: argument or keyword is the last node, so we have to find the last
|
||||
// comma in the stmt.
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if range.start() == expr_range.start() {
|
||||
fix_end = Some(expr_range.end());
|
||||
break;
|
||||
}
|
||||
if matches!(tok, Tok::Comma) {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
|
||||
_ => {
|
||||
bail!("No fix could be constructed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
|
@ -429,7 +324,7 @@ mod tests {
|
|||
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::autofix::actions::{next_stmt_break, trailing_semicolon};
|
||||
use crate::autofix::edits::{next_stmt_break, trailing_semicolon};
|
||||
|
||||
#[test]
|
||||
fn find_semicolon() -> Result<()> {
|
||||
|
|
@ -10,7 +10,8 @@ use ruff_python_ast::source_code::Locator;
|
|||
use crate::linter::FixTable;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
pub(crate) mod actions;
|
||||
pub(crate) mod codemods;
|
||||
pub(crate) mod edits;
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub(crate) fn fix_file(
|
||||
|
|
|
|||
|
|
@ -5223,7 +5223,7 @@ impl<'a> Checker<'a> {
|
|||
|
||||
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::actions::remove_unused_imports(
|
||||
match autofix::edits::remove_unused_imports(
|
||||
unused_imports.iter().map(|(full_name, _)| *full_name),
|
||||
child,
|
||||
parent,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use ruff_macros::{derive_message_formats, violation};
|
|||
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::actions::delete_stmt;
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use ruff_macros::{derive_message_formats, violation};
|
|||
|
||||
use ruff_python_ast::helpers::trailing_comment_start_offset;
|
||||
|
||||
use crate::autofix::actions::delete_stmt;
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
|||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::actions::delete_stmt;
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
|||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::actions::delete_stmt;
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use ruff_python_ast::{helpers, visitor};
|
|||
use ruff_python_semantic::analyze::visibility::is_abstract;
|
||||
use ruff_python_semantic::model::SemanticModel;
|
||||
|
||||
use crate::autofix::actions::remove_argument;
|
||||
use crate::autofix::edits::remove_argument;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
|||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::actions::delete_stmt;
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use rustpython_parser::ast::{self, Expr, Keyword, Ranged};
|
|||
use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::autofix::actions::remove_argument;
|
||||
use crate::autofix::edits::remove_argument;
|
||||
|
||||
fn match_name(expr: &Expr) -> Option<&str> {
|
||||
if let Expr::Call(ast::ExprCall { func, .. }) = expr {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use ruff_python_ast::source_code::Locator;
|
|||
use ruff_python_ast::types::RefEquality;
|
||||
use ruff_python_semantic::scope::{ScopeId, ScopeKind};
|
||||
|
||||
use crate::autofix::actions::delete_stmt;
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ 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::actions::delete_stmt;
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use ruff_python_ast::source_code::Locator;
|
|||
use ruff_python_ast::types::RefEquality;
|
||||
use ruff_python_ast::whitespace::indentation;
|
||||
|
||||
use crate::autofix::actions::delete_stmt;
|
||||
use crate::autofix::edits::delete_stmt;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::rules::pyupgrade::fixes::adjust_indentation;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use ruff_macros::{derive_message_formats, violation};
|
|||
use ruff_python_ast::helpers::find_keyword;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::autofix::actions::remove_argument;
|
||||
use crate::autofix::edits::remove_argument;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ pub(crate) fn unnecessary_builtin_import(
|
|||
.iter()
|
||||
.map(|alias| format!("{module}.{}", alias.name))
|
||||
.collect();
|
||||
match autofix::actions::remove_unused_imports(
|
||||
match autofix::edits::remove_unused_imports(
|
||||
unused_imports.iter().map(String::as_str),
|
||||
defined_by,
|
||||
defined_in,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
|||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::autofix::actions::remove_argument;
|
||||
use crate::autofix::edits::remove_argument;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Rule;
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ pub(crate) fn unnecessary_future_import(checker: &mut Checker, stmt: &Stmt, name
|
|||
.iter()
|
||||
.map(|alias| format!("__future__.{}", alias.name))
|
||||
.collect();
|
||||
match autofix::actions::remove_unused_imports(
|
||||
match autofix::edits::remove_unused_imports(
|
||||
unused_imports.iter().map(String::as_str),
|
||||
defined_by,
|
||||
defined_in,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
|||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
|
||||
use crate::autofix::actions;
|
||||
use crate::autofix::edits;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ pub(crate) fn useless_metaclass_type(
|
|||
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 actions::delete_stmt(
|
||||
match edits::delete_stmt(
|
||||
defined_by,
|
||||
defined_in,
|
||||
&deleted,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
|
|||
use ruff_python_semantic::binding::{Binding, BindingKind, Bindings};
|
||||
use ruff_python_semantic::scope::Scope;
|
||||
|
||||
use crate::autofix::actions::remove_argument;
|
||||
use crate::autofix::edits::remove_argument;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue