blob: e4b51da3cadcfed25bdf5d244dc0f077a1a40ac3 [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::MacroCall;
use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma};
use clippy_utils::sym;
use rustc_ast::{FormatArgs, FormatArgsPiece};
use rustc_errors::Applicability;
use rustc_lint::{LateContext, LintContext};
use rustc_span::BytePos;
use super::{PRINT_WITH_NEWLINE, WRITE_WITH_NEWLINE};
pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) {
let Some(&FormatArgsPiece::Literal(last)) = format_args.template.last() else {
return;
};
let count_vertical_whitespace = || {
format_args
.template
.iter()
.filter_map(|piece| match piece {
FormatArgsPiece::Literal(literal) => Some(literal),
FormatArgsPiece::Placeholder(_) => None,
})
.flat_map(|literal| literal.as_str().chars())
.filter(|ch| matches!(ch, '\r' | '\n'))
.count()
};
if last.as_str().ends_with('\n')
// ignore format strings with other internal vertical whitespace
&& count_vertical_whitespace() == 1
{
let mut format_string_span = format_args.span;
let lint = if name == "write" {
format_string_span = expand_past_previous_comma(cx, format_string_span);
WRITE_WITH_NEWLINE
} else {
PRINT_WITH_NEWLINE
};
span_lint_and_then(
cx,
lint,
macro_call.span,
format!("using `{name}!()` with a format string that ends in a single newline"),
|diag| {
let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!');
let Some(format_snippet) = format_string_span.get_source_text(cx) else {
return;
};
if format_args.template.len() == 1 && last == sym::LF {
// print!("\n"), write!(f, "\n")
diag.multipart_suggestion(
format!("use `{name}ln!` instead"),
vec![(name_span, format!("{name}ln")), (format_string_span, String::new())],
Applicability::MachineApplicable,
);
} else if format_snippet.ends_with("\\n\"") {
// print!("...\n"), write!(f, "...\n")
let hi = format_string_span.hi();
let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1));
diag.multipart_suggestion(
format!("use `{name}ln!` instead"),
vec![(name_span, format!("{name}ln")), (newline_span, String::new())],
Applicability::MachineApplicable,
);
}
},
);
}
}