Fix index of the remaining positional arguments
This commit is contained in:
parent
124f1b0659
commit
b413bf6c4e
9 changed files with 164 additions and 205 deletions
|
|
@ -3,12 +3,15 @@ use clippy_utils::macros::{find_format_args, format_arg_removal_span, root_macro
|
|||
use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
|
||||
use clippy_utils::{is_in_cfg_test, is_in_test_function};
|
||||
use rustc_ast::token::LitKind;
|
||||
use rustc_ast::{FormatArgPosition, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder, FormatTrait};
|
||||
use rustc_ast::{
|
||||
FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder,
|
||||
FormatTrait,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Impl, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{sym, BytePos};
|
||||
use rustc_span::{sym, BytePos, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -450,6 +453,12 @@ fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call
|
|||
fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
|
||||
let arg_index = |argument: &FormatArgPosition| argument.index.unwrap_or_else(|pos| pos);
|
||||
|
||||
let lint_name = if name.starts_with("write") {
|
||||
WRITE_LITERAL
|
||||
} else {
|
||||
PRINT_LITERAL
|
||||
};
|
||||
|
||||
let mut counts = vec![0u32; format_args.arguments.all_args().len()];
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece {
|
||||
|
|
@ -457,6 +466,12 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
let mut suggestion: Vec<(Span, String)> = vec![];
|
||||
// holds index of replaced positional arguments; used to decrement the index of the remaining
|
||||
// positional arguments.
|
||||
let mut replaced_position: Vec<usize> = vec![];
|
||||
let mut sug_span: Option<Span> = None;
|
||||
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||
argument,
|
||||
|
|
@ -493,12 +508,6 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
|
|||
_ => continue,
|
||||
};
|
||||
|
||||
let lint = if name.starts_with("write") {
|
||||
WRITE_LITERAL
|
||||
} else {
|
||||
PRINT_LITERAL
|
||||
};
|
||||
|
||||
let Some(format_string_snippet) = snippet_opt(cx, format_args.span) else { continue };
|
||||
let format_string_is_raw = format_string_snippet.starts_with('r');
|
||||
|
||||
|
|
@ -519,29 +528,58 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
|
|||
},
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
lint,
|
||||
arg.expr.span,
|
||||
"literal with an empty format string",
|
||||
|diag| {
|
||||
if let Some(replacement) = replacement
|
||||
// `format!("{}", "a")`, `format!("{named}", named = "b")
|
||||
// ~~~~~ ~~~~~~~~~~~~~
|
||||
&& let Some(removal_span) = format_arg_removal_span(format_args, index)
|
||||
{
|
||||
let replacement = replacement.replace('{', "{{").replace('}', "}}");
|
||||
diag.multipart_suggestion(
|
||||
"try",
|
||||
vec![(*placeholder_span, replacement), (removal_span, String::new())],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span));
|
||||
|
||||
if let Some((_, index)) = positional_arg_piece_span(piece) {
|
||||
replaced_position.push(index);
|
||||
}
|
||||
|
||||
if let Some(replacement) = replacement
|
||||
// `format!("{}", "a")`, `format!("{named}", named = "b")
|
||||
// ~~~~~ ~~~~~~~~~~~~~
|
||||
&& let Some(removal_span) = format_arg_removal_span(format_args, index) {
|
||||
let replacement = replacement.replace('{', "{{").replace('}', "}}");
|
||||
suggestion.push((*placeholder_span, replacement));
|
||||
suggestion.push((removal_span, String::new()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement the index of the remaining by the number of replaced positional arguments
|
||||
if !suggestion.is_empty() {
|
||||
for piece in &format_args.template {
|
||||
if let Some((span, index)) = positional_arg_piece_span(piece)
|
||||
&& suggestion.iter().all(|(s, _)| *s != span) {
|
||||
let decrement = replaced_position.iter().filter(|i| **i < index).count();
|
||||
suggestion.push((span, format!("{{{}}}", index.saturating_sub(decrement))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(span) = sug_span {
|
||||
span_lint_and_then(cx, lint_name, span, "literal with an empty format string", |diag| {
|
||||
if !suggestion.is_empty() {
|
||||
diag.multipart_suggestion("try", suggestion, Applicability::MachineApplicable);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract Span and its index from the given `piece`, iff it's positional argument.
|
||||
fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> {
|
||||
match piece {
|
||||
FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||
argument:
|
||||
FormatArgPosition {
|
||||
index: Ok(index),
|
||||
kind: FormatArgPositionKind::Number,
|
||||
..
|
||||
},
|
||||
span: Some(span),
|
||||
..
|
||||
}) => Some((*span, *index)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue