refactor(write): split each lint into its own module (#15857)
changelog: none
This commit is contained in:
commit
c23eed6ddc
13 changed files with 1064 additions and 971 deletions
|
|
@ -1,734 +0,0 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::macros::{FormatArgsStorage, MacroCall, format_arg_removal_span, root_macro_call_first_node};
|
||||
use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma};
|
||||
use clippy_utils::{is_in_test, sym};
|
||||
use rustc_ast::token::LitKind;
|
||||
use rustc_ast::{
|
||||
FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions,
|
||||
FormatPlaceholder, FormatTrait,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns when you use `println!("")` to
|
||||
/// print a newline.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// You should use `println!()`, which is simpler.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// println!("");
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// println!();
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub PRINTLN_EMPTY_STRING,
|
||||
style,
|
||||
"using `println!(\"\")` with an empty string"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns when you use `print!()` with a format
|
||||
/// string that ends in a newline.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// You should use `println!()` instead, which appends the
|
||||
/// newline.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # let name = "World";
|
||||
/// print!("Hello {}!\n", name);
|
||||
/// ```
|
||||
/// use println!() instead
|
||||
/// ```no_run
|
||||
/// # let name = "World";
|
||||
/// println!("Hello {}!", name);
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub PRINT_WITH_NEWLINE,
|
||||
style,
|
||||
"using `print!()` with a format string that ends in a single newline"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for printing on *stdout*. The purpose of this lint
|
||||
/// is to catch debugging remnants.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// People often print on *stdout* while debugging an
|
||||
/// application and might forget to remove those prints afterward.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Only catches `print!` and `println!` calls.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// println!("Hello world!");
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub PRINT_STDOUT,
|
||||
restriction,
|
||||
"printing on stdout"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for printing on *stderr*. The purpose of this lint
|
||||
/// is to catch debugging remnants.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// People often print on *stderr* while debugging an
|
||||
/// application and might forget to remove those prints afterward.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Only catches `eprint!` and `eprintln!` calls.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// eprintln!("Hello world!");
|
||||
/// ```
|
||||
#[clippy::version = "1.50.0"]
|
||||
pub PRINT_STDERR,
|
||||
restriction,
|
||||
"printing on stderr"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `Debug` formatting. The purpose of this
|
||||
/// lint is to catch debugging remnants.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// The purpose of the `Debug` trait is to facilitate debugging Rust code,
|
||||
/// and [no guarantees are made about its output][stability].
|
||||
/// It should not be used in user-facing output.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # let foo = "bar";
|
||||
/// println!("{:?}", foo);
|
||||
/// ```
|
||||
///
|
||||
/// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub USE_DEBUG,
|
||||
restriction,
|
||||
"use of `Debug`-based formatting"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns about the use of literals as `print!`/`println!` args.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using literals as `println!` args is inefficient
|
||||
/// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
|
||||
/// (i.e., just put the literal in the format string)
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// println!("{}", "foo");
|
||||
/// ```
|
||||
/// use the literal without formatting:
|
||||
/// ```no_run
|
||||
/// println!("foo");
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub PRINT_LITERAL,
|
||||
style,
|
||||
"printing a literal with a format string"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns when you use `writeln!(buf, "")` to
|
||||
/// print a newline.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// You should use `writeln!(buf)`, which is simpler.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// writeln!(buf, "");
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// writeln!(buf);
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub WRITELN_EMPTY_STRING,
|
||||
style,
|
||||
"using `writeln!(buf, \"\")` with an empty string"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns when you use `write!()` with a format
|
||||
/// string that
|
||||
/// ends in a newline.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// You should use `writeln!()` instead, which appends the
|
||||
/// newline.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// # let name = "World";
|
||||
/// write!(buf, "Hello {}!\n", name);
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// # let name = "World";
|
||||
/// writeln!(buf, "Hello {}!", name);
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub WRITE_WITH_NEWLINE,
|
||||
style,
|
||||
"using `write!()` with a format string that ends in a single newline"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns about the use of literals as `write!`/`writeln!` args.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using literals as `writeln!` args is inefficient
|
||||
/// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
|
||||
/// (i.e., just put the literal in the format string)
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// writeln!(buf, "{}", "foo");
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// writeln!(buf, "foo");
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub WRITE_LITERAL,
|
||||
style,
|
||||
"writing a literal with a format string"
|
||||
}
|
||||
|
||||
pub struct Write {
|
||||
format_args: FormatArgsStorage,
|
||||
// The outermost `impl Debug` we're currently in. While we're in one, `USE_DEBUG` is deactivated
|
||||
outermost_debug_impl: Option<OwnerId>,
|
||||
allow_print_in_tests: bool,
|
||||
}
|
||||
|
||||
impl Write {
|
||||
pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self {
|
||||
Self {
|
||||
format_args,
|
||||
outermost_debug_impl: None,
|
||||
allow_print_in_tests: conf.allow_print_in_tests,
|
||||
}
|
||||
}
|
||||
|
||||
fn in_debug_impl(&self) -> bool {
|
||||
self.outermost_debug_impl.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(Write => [
|
||||
PRINT_WITH_NEWLINE,
|
||||
PRINTLN_EMPTY_STRING,
|
||||
PRINT_STDOUT,
|
||||
PRINT_STDERR,
|
||||
USE_DEBUG,
|
||||
PRINT_LITERAL,
|
||||
WRITE_WITH_NEWLINE,
|
||||
WRITELN_EMPTY_STRING,
|
||||
WRITE_LITERAL,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Write {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
// Only check for `impl Debug`s if we're not already in one
|
||||
if self.outermost_debug_impl.is_none() && is_debug_impl(cx, item) {
|
||||
self.outermost_debug_impl = Some(item.owner_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
// Only clear `self.outermost_debug_impl` if we're escaping the _outermost_ debug impl
|
||||
if self.outermost_debug_impl == Some(item.owner_id) {
|
||||
self.outermost_debug_impl = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
|
||||
return;
|
||||
};
|
||||
let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) else {
|
||||
return;
|
||||
};
|
||||
let Some(name) = diag_name.as_str().strip_suffix("_macro") else {
|
||||
return;
|
||||
};
|
||||
|
||||
let is_build_script = cx
|
||||
.sess()
|
||||
.opts
|
||||
.crate_name
|
||||
.as_ref()
|
||||
.is_some_and(|crate_name| crate_name == "build_script_build");
|
||||
|
||||
let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id);
|
||||
match diag_name {
|
||||
sym::print_macro | sym::println_macro if !allowed_in_tests => {
|
||||
if !is_build_script {
|
||||
span_lint(cx, PRINT_STDOUT, macro_call.span, format!("use of `{name}!`"));
|
||||
}
|
||||
},
|
||||
sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => {
|
||||
span_lint(cx, PRINT_STDERR, macro_call.span, format!("use of `{name}!`"));
|
||||
},
|
||||
sym::write_macro | sym::writeln_macro => {},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) {
|
||||
// ignore `writeln!(w)` and `write!(v, some_macro!())`
|
||||
if format_args.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
match diag_name {
|
||||
sym::print_macro | sym::eprint_macro | sym::write_macro => {
|
||||
check_newline(cx, format_args, ¯o_call, name);
|
||||
},
|
||||
sym::println_macro | sym::eprintln_macro | sym::writeln_macro => {
|
||||
check_empty_string(cx, format_args, ¯o_call, name);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
check_literal(cx, format_args, name);
|
||||
|
||||
if !self.in_debug_impl() {
|
||||
for piece in &format_args.template {
|
||||
if let &FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||
span: Some(span),
|
||||
format_trait: FormatTrait::Debug,
|
||||
..
|
||||
}) = piece
|
||||
{
|
||||
span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(of_trait),
|
||||
..
|
||||
}) = &item.kind
|
||||
&& let Some(trait_id) = of_trait.trait_ref.trait_def_id()
|
||||
{
|
||||
cx.tcx.is_diagnostic_item(sym::Debug, trait_id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn check_newline(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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) {
|
||||
if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] {
|
||||
let mut span = format_args.span;
|
||||
|
||||
let lint = if name == "writeln" {
|
||||
span = expand_past_previous_comma(cx, span);
|
||||
|
||||
WRITELN_EMPTY_STRING
|
||||
} else {
|
||||
PRINTLN_EMPTY_STRING
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
lint,
|
||||
macro_call.span,
|
||||
format!("empty string literal in `{name}!`"),
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
"remove the empty string",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
counts[arg_index(&placeholder.argument)] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
span: Some(placeholder_span),
|
||||
format_trait: FormatTrait::Display,
|
||||
format_options,
|
||||
}) = piece
|
||||
&& *format_options == FormatOptions::default()
|
||||
&& let index = arg_index(argument)
|
||||
&& counts[index] == 1
|
||||
&& let Some(arg) = format_args.arguments.by_index(index)
|
||||
&& let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind
|
||||
&& !arg.expr.span.from_expansion()
|
||||
&& let Some(value_string) = arg.expr.span.get_source_text(cx)
|
||||
{
|
||||
let (replacement, replace_raw) = match lit.kind {
|
||||
LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
|
||||
Some(extracted) => extracted,
|
||||
None => return,
|
||||
},
|
||||
LitKind::Char => (
|
||||
match lit.symbol {
|
||||
sym::DOUBLE_QUOTE => "\\\"",
|
||||
sym::BACKSLASH_SINGLE_QUOTE => "'",
|
||||
_ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
|
||||
Some(stripped) => stripped,
|
||||
None => return,
|
||||
},
|
||||
}
|
||||
.to_string(),
|
||||
false,
|
||||
),
|
||||
LitKind::Bool => (lit.symbol.to_string(), false),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let Some(format_string_snippet) = format_args.span.get_source_text(cx) else {
|
||||
continue;
|
||||
};
|
||||
let format_string_is_raw = format_string_snippet.starts_with('r');
|
||||
|
||||
let replacement = match (format_string_is_raw, replace_raw) {
|
||||
(false, false) => Some(replacement),
|
||||
(false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")),
|
||||
(true, false) => match conservative_unescape(&replacement) {
|
||||
Ok(unescaped) => Some(unescaped),
|
||||
Err(UnescapeErr::Lint) => None,
|
||||
Err(UnescapeErr::Ignore) => continue,
|
||||
},
|
||||
(true, true) => {
|
||||
if replacement.contains(['#', '"']) {
|
||||
None
|
||||
} else {
|
||||
Some(replacement)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span));
|
||||
|
||||
if let Some((_, index)) = format_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 = escape_braces(&replacement, !format_string_is_raw && !replace_raw);
|
||||
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 {
|
||||
relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position);
|
||||
}
|
||||
}
|
||||
|
||||
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`
|
||||
fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> {
|
||||
match piece {
|
||||
FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||
argument: FormatArgPosition { index: Ok(index), .. },
|
||||
span: Some(span),
|
||||
..
|
||||
}) => Some((*span, *index)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Relocalizes the indexes of positional arguments in the format string
|
||||
fn relocalize_format_args_indexes(
|
||||
piece: &FormatArgsPiece,
|
||||
suggestion: &mut Vec<(Span, String)>,
|
||||
replaced_position: &[usize],
|
||||
) {
|
||||
if let FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||
argument:
|
||||
FormatArgPosition {
|
||||
index: Ok(index),
|
||||
// Only consider positional arguments
|
||||
kind: FormatArgPositionKind::Number,
|
||||
span: Some(span),
|
||||
},
|
||||
format_options,
|
||||
..
|
||||
}) = piece
|
||||
{
|
||||
if suggestion.iter().any(|(s, _)| s.overlaps(*span)) {
|
||||
// If the span is already in the suggestion, we don't need to process it again
|
||||
return;
|
||||
}
|
||||
|
||||
// lambda to get the decremented index based on the replaced positions
|
||||
let decremented_index = |index: usize| -> usize {
|
||||
let decrement = replaced_position.iter().filter(|&&i| i < index).count();
|
||||
index - decrement
|
||||
};
|
||||
|
||||
suggestion.push((*span, decremented_index(*index).to_string()));
|
||||
|
||||
// If there are format options, we need to handle them as well
|
||||
if *format_options != FormatOptions::default() {
|
||||
// lambda to process width and precision format counts and add them to the suggestion
|
||||
let mut process_format_count = |count: &Option<FormatCount>, formatter: &dyn Fn(usize) -> String| {
|
||||
if let Some(FormatCount::Argument(FormatArgPosition {
|
||||
index: Ok(format_arg_index),
|
||||
kind: FormatArgPositionKind::Number,
|
||||
span: Some(format_arg_span),
|
||||
})) = count
|
||||
{
|
||||
suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index))));
|
||||
}
|
||||
};
|
||||
|
||||
process_format_count(&format_options.width, &|index: usize| format!("{index}$"));
|
||||
process_format_count(&format_options.precision, &|index: usize| format!(".{index}$"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw
|
||||
///
|
||||
/// `r#"a"#` -> (`a`, true)
|
||||
///
|
||||
/// `"b"` -> (`b`, false)
|
||||
fn extract_str_literal(literal: &str) -> Option<(String, bool)> {
|
||||
let (literal, raw) = match literal.strip_prefix('r') {
|
||||
Some(stripped) => (stripped.trim_matches('#'), true),
|
||||
None => (literal, false),
|
||||
};
|
||||
|
||||
Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw))
|
||||
}
|
||||
|
||||
enum UnescapeErr {
|
||||
/// Should still be linted, can be manually resolved by author, e.g.
|
||||
///
|
||||
/// ```ignore
|
||||
/// print!(r"{}", '"');
|
||||
/// ```
|
||||
Lint,
|
||||
/// Should not be linted, e.g.
|
||||
///
|
||||
/// ```ignore
|
||||
/// print!(r"{}", '\r');
|
||||
/// ```
|
||||
Ignore,
|
||||
}
|
||||
|
||||
/// Unescape a normal string into a raw string
|
||||
fn conservative_unescape(literal: &str) -> Result<String, UnescapeErr> {
|
||||
let mut unescaped = String::with_capacity(literal.len());
|
||||
let mut chars = literal.chars();
|
||||
let mut err = false;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'#' => err = true,
|
||||
'\\' => match chars.next() {
|
||||
Some('\\') => unescaped.push('\\'),
|
||||
Some('"') => err = true,
|
||||
_ => return Err(UnescapeErr::Ignore),
|
||||
},
|
||||
_ => unescaped.push(ch),
|
||||
}
|
||||
}
|
||||
|
||||
if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) }
|
||||
}
|
||||
|
||||
/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces in
|
||||
/// `\u{xxxx}` are left unmodified
|
||||
#[expect(clippy::match_same_arms)]
|
||||
fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String {
|
||||
#[derive(Clone, Copy)]
|
||||
enum State {
|
||||
Normal,
|
||||
Backslash,
|
||||
UnicodeEscape,
|
||||
}
|
||||
|
||||
let mut escaped = String::with_capacity(literal.len());
|
||||
let mut state = State::Normal;
|
||||
|
||||
for ch in literal.chars() {
|
||||
state = match (ch, state) {
|
||||
// Escape braces outside of unicode escapes by doubling them up
|
||||
('{' | '}', State::Normal) => {
|
||||
escaped.push(ch);
|
||||
State::Normal
|
||||
},
|
||||
// If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise:
|
||||
//
|
||||
// \u{aaaa} \\ \x01
|
||||
// ^ ^ ^
|
||||
('\\', State::Normal) if preserve_unicode_escapes => State::Backslash,
|
||||
// \u{aaaa}
|
||||
// ^
|
||||
('u', State::Backslash) => State::UnicodeEscape,
|
||||
// \xAA \\
|
||||
// ^ ^
|
||||
(_, State::Backslash) => State::Normal,
|
||||
// \u{aaaa}
|
||||
// ^
|
||||
('}', State::UnicodeEscape) => State::Normal,
|
||||
_ => state,
|
||||
};
|
||||
|
||||
escaped.push(ch);
|
||||
}
|
||||
|
||||
escaped
|
||||
}
|
||||
38
clippy_lints/src/write/empty_string.rs
Normal file
38
clippy_lints/src/write/empty_string.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::macros::MacroCall;
|
||||
use clippy_utils::source::expand_past_previous_comma;
|
||||
use clippy_utils::sym;
|
||||
use rustc_ast::{FormatArgs, FormatArgsPiece};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::{PRINTLN_EMPTY_STRING, WRITELN_EMPTY_STRING};
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) {
|
||||
if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] {
|
||||
let mut span = format_args.span;
|
||||
|
||||
let lint = if name == "writeln" {
|
||||
span = expand_past_previous_comma(cx, span);
|
||||
|
||||
WRITELN_EMPTY_STRING
|
||||
} else {
|
||||
PRINTLN_EMPTY_STRING
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
lint,
|
||||
macro_call.span,
|
||||
format!("empty string literal in `{name}!`"),
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
"remove the empty string",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
285
clippy_lints/src/write/literal.rs
Normal file
285
clippy_lints/src/write/literal.rs
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::macros::format_arg_removal_span;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::sym;
|
||||
use rustc_ast::token::LitKind;
|
||||
use rustc_ast::{
|
||||
FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions,
|
||||
FormatPlaceholder, FormatTrait,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
|
||||
use super::{PRINT_LITERAL, WRITE_LITERAL};
|
||||
|
||||
pub(super) fn check(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 {
|
||||
counts[arg_index(&placeholder.argument)] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
span: Some(placeholder_span),
|
||||
format_trait: FormatTrait::Display,
|
||||
format_options,
|
||||
}) = piece
|
||||
&& *format_options == FormatOptions::default()
|
||||
&& let index = arg_index(argument)
|
||||
&& counts[index] == 1
|
||||
&& let Some(arg) = format_args.arguments.by_index(index)
|
||||
&& let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind
|
||||
&& !arg.expr.span.from_expansion()
|
||||
&& let Some(value_string) = arg.expr.span.get_source_text(cx)
|
||||
{
|
||||
let (replacement, replace_raw) = match lit.kind {
|
||||
LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
|
||||
Some(extracted) => extracted,
|
||||
None => return,
|
||||
},
|
||||
LitKind::Char => (
|
||||
match lit.symbol {
|
||||
sym::DOUBLE_QUOTE => "\\\"",
|
||||
sym::BACKSLASH_SINGLE_QUOTE => "'",
|
||||
_ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
|
||||
Some(stripped) => stripped,
|
||||
None => return,
|
||||
},
|
||||
}
|
||||
.to_string(),
|
||||
false,
|
||||
),
|
||||
LitKind::Bool => (lit.symbol.to_string(), false),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let Some(format_string_snippet) = format_args.span.get_source_text(cx) else {
|
||||
continue;
|
||||
};
|
||||
let format_string_is_raw = format_string_snippet.starts_with('r');
|
||||
|
||||
let replacement = match (format_string_is_raw, replace_raw) {
|
||||
(false, false) => Some(replacement),
|
||||
(false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")),
|
||||
(true, false) => match conservative_unescape(&replacement) {
|
||||
Ok(unescaped) => Some(unescaped),
|
||||
Err(UnescapeErr::Lint) => None,
|
||||
Err(UnescapeErr::Ignore) => continue,
|
||||
},
|
||||
(true, true) => {
|
||||
if replacement.contains(['#', '"']) {
|
||||
None
|
||||
} else {
|
||||
Some(replacement)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span));
|
||||
|
||||
if let Some((_, index)) = format_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 = escape_braces(&replacement, !format_string_is_raw && !replace_raw);
|
||||
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 {
|
||||
relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position);
|
||||
}
|
||||
}
|
||||
|
||||
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`
|
||||
fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> {
|
||||
match piece {
|
||||
FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||
argument: FormatArgPosition { index: Ok(index), .. },
|
||||
span: Some(span),
|
||||
..
|
||||
}) => Some((*span, *index)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Relocalizes the indexes of positional arguments in the format string
|
||||
fn relocalize_format_args_indexes(
|
||||
piece: &FormatArgsPiece,
|
||||
suggestion: &mut Vec<(Span, String)>,
|
||||
replaced_position: &[usize],
|
||||
) {
|
||||
if let FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||
argument:
|
||||
FormatArgPosition {
|
||||
index: Ok(index),
|
||||
// Only consider positional arguments
|
||||
kind: FormatArgPositionKind::Number,
|
||||
span: Some(span),
|
||||
},
|
||||
format_options,
|
||||
..
|
||||
}) = piece
|
||||
{
|
||||
if suggestion.iter().any(|(s, _)| s.overlaps(*span)) {
|
||||
// If the span is already in the suggestion, we don't need to process it again
|
||||
return;
|
||||
}
|
||||
|
||||
// lambda to get the decremented index based on the replaced positions
|
||||
let decremented_index = |index: usize| -> usize {
|
||||
let decrement = replaced_position.iter().filter(|&&i| i < index).count();
|
||||
index - decrement
|
||||
};
|
||||
|
||||
suggestion.push((*span, decremented_index(*index).to_string()));
|
||||
|
||||
// If there are format options, we need to handle them as well
|
||||
if *format_options != FormatOptions::default() {
|
||||
// lambda to process width and precision format counts and add them to the suggestion
|
||||
let mut process_format_count = |count: &Option<FormatCount>, formatter: &dyn Fn(usize) -> String| {
|
||||
if let Some(FormatCount::Argument(FormatArgPosition {
|
||||
index: Ok(format_arg_index),
|
||||
kind: FormatArgPositionKind::Number,
|
||||
span: Some(format_arg_span),
|
||||
})) = count
|
||||
{
|
||||
suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index))));
|
||||
}
|
||||
};
|
||||
|
||||
process_format_count(&format_options.width, &|index: usize| format!("{index}$"));
|
||||
process_format_count(&format_options.precision, &|index: usize| format!(".{index}$"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw
|
||||
///
|
||||
/// `r#"a"#` -> (`a`, true)
|
||||
///
|
||||
/// `"b"` -> (`b`, false)
|
||||
fn extract_str_literal(literal: &str) -> Option<(String, bool)> {
|
||||
let (literal, raw) = match literal.strip_prefix('r') {
|
||||
Some(stripped) => (stripped.trim_matches('#'), true),
|
||||
None => (literal, false),
|
||||
};
|
||||
|
||||
Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw))
|
||||
}
|
||||
|
||||
enum UnescapeErr {
|
||||
/// Should still be linted, can be manually resolved by author, e.g.
|
||||
///
|
||||
/// ```ignore
|
||||
/// print!(r"{}", '"');
|
||||
/// ```
|
||||
Lint,
|
||||
/// Should not be linted, e.g.
|
||||
///
|
||||
/// ```ignore
|
||||
/// print!(r"{}", '\r');
|
||||
/// ```
|
||||
Ignore,
|
||||
}
|
||||
|
||||
/// Unescape a normal string into a raw string
|
||||
fn conservative_unescape(literal: &str) -> Result<String, UnescapeErr> {
|
||||
let mut unescaped = String::with_capacity(literal.len());
|
||||
let mut chars = literal.chars();
|
||||
let mut err = false;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'#' => err = true,
|
||||
'\\' => match chars.next() {
|
||||
Some('\\') => unescaped.push('\\'),
|
||||
Some('"') => err = true,
|
||||
_ => return Err(UnescapeErr::Ignore),
|
||||
},
|
||||
_ => unescaped.push(ch),
|
||||
}
|
||||
}
|
||||
|
||||
if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) }
|
||||
}
|
||||
|
||||
/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces
|
||||
/// in `\u{xxxx}` are left unmodified
|
||||
#[expect(clippy::match_same_arms)]
|
||||
fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String {
|
||||
#[derive(Clone, Copy)]
|
||||
enum State {
|
||||
Normal,
|
||||
Backslash,
|
||||
UnicodeEscape,
|
||||
}
|
||||
|
||||
let mut escaped = String::with_capacity(literal.len());
|
||||
let mut state = State::Normal;
|
||||
|
||||
for ch in literal.chars() {
|
||||
state = match (ch, state) {
|
||||
// Escape braces outside of unicode escapes by doubling them up
|
||||
('{' | '}', State::Normal) => {
|
||||
escaped.push(ch);
|
||||
State::Normal
|
||||
},
|
||||
// If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise:
|
||||
//
|
||||
// \u{aaaa} \\ \x01
|
||||
// ^ ^ ^
|
||||
('\\', State::Normal) if preserve_unicode_escapes => State::Backslash,
|
||||
// \u{aaaa}
|
||||
// ^
|
||||
('u', State::Backslash) => State::UnicodeEscape,
|
||||
// \xAA \\
|
||||
// ^ ^
|
||||
(_, State::Backslash) => State::Normal,
|
||||
// \u{aaaa}
|
||||
// ^
|
||||
('}', State::UnicodeEscape) => State::Normal,
|
||||
_ => state,
|
||||
};
|
||||
|
||||
escaped.push(ch);
|
||||
}
|
||||
|
||||
escaped
|
||||
}
|
||||
354
clippy_lints/src/write/mod.rs
Normal file
354
clippy_lints/src/write/mod.rs
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::macros::{FormatArgsStorage, root_macro_call_first_node};
|
||||
use clippy_utils::{is_in_test, sym};
|
||||
use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
||||
mod empty_string;
|
||||
mod literal;
|
||||
mod use_debug;
|
||||
mod with_newline;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns when you use `println!("")` to
|
||||
/// print a newline.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// You should use `println!()`, which is simpler.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// println!("");
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// println!();
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub PRINTLN_EMPTY_STRING,
|
||||
style,
|
||||
"using `println!(\"\")` with an empty string"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns when you use `print!()` with a format
|
||||
/// string that ends in a newline.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// You should use `println!()` instead, which appends the
|
||||
/// newline.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # let name = "World";
|
||||
/// print!("Hello {}!\n", name);
|
||||
/// ```
|
||||
/// use println!() instead
|
||||
/// ```no_run
|
||||
/// # let name = "World";
|
||||
/// println!("Hello {}!", name);
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub PRINT_WITH_NEWLINE,
|
||||
style,
|
||||
"using `print!()` with a format string that ends in a single newline"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for printing on *stdout*. The purpose of this lint
|
||||
/// is to catch debugging remnants.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// People often print on *stdout* while debugging an
|
||||
/// application and might forget to remove those prints afterward.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Only catches `print!` and `println!` calls.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// println!("Hello world!");
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub PRINT_STDOUT,
|
||||
restriction,
|
||||
"printing on stdout"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for printing on *stderr*. The purpose of this lint
|
||||
/// is to catch debugging remnants.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// People often print on *stderr* while debugging an
|
||||
/// application and might forget to remove those prints afterward.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Only catches `eprint!` and `eprintln!` calls.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// eprintln!("Hello world!");
|
||||
/// ```
|
||||
#[clippy::version = "1.50.0"]
|
||||
pub PRINT_STDERR,
|
||||
restriction,
|
||||
"printing on stderr"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `Debug` formatting. The purpose of this
|
||||
/// lint is to catch debugging remnants.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// The purpose of the `Debug` trait is to facilitate debugging Rust code,
|
||||
/// and [no guarantees are made about its output][stability].
|
||||
/// It should not be used in user-facing output.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # let foo = "bar";
|
||||
/// println!("{:?}", foo);
|
||||
/// ```
|
||||
///
|
||||
/// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub USE_DEBUG,
|
||||
restriction,
|
||||
"use of `Debug`-based formatting"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns about the use of literals as `print!`/`println!` args.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using literals as `println!` args is inefficient
|
||||
/// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
|
||||
/// (i.e., just put the literal in the format string)
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// println!("{}", "foo");
|
||||
/// ```
|
||||
/// use the literal without formatting:
|
||||
/// ```no_run
|
||||
/// println!("foo");
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub PRINT_LITERAL,
|
||||
style,
|
||||
"printing a literal with a format string"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns when you use `writeln!(buf, "")` to
|
||||
/// print a newline.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// You should use `writeln!(buf)`, which is simpler.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// writeln!(buf, "");
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// writeln!(buf);
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub WRITELN_EMPTY_STRING,
|
||||
style,
|
||||
"using `writeln!(buf, \"\")` with an empty string"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns when you use `write!()` with a format
|
||||
/// string that
|
||||
/// ends in a newline.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// You should use `writeln!()` instead, which appends the
|
||||
/// newline.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// # let name = "World";
|
||||
/// write!(buf, "Hello {}!\n", name);
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// # let name = "World";
|
||||
/// writeln!(buf, "Hello {}!", name);
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub WRITE_WITH_NEWLINE,
|
||||
style,
|
||||
"using `write!()` with a format string that ends in a single newline"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This lint warns about the use of literals as `write!`/`writeln!` args.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using literals as `writeln!` args is inefficient
|
||||
/// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
|
||||
/// (i.e., just put the literal in the format string)
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// writeln!(buf, "{}", "foo");
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::fmt::Write;
|
||||
/// # let mut buf = String::new();
|
||||
/// writeln!(buf, "foo");
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub WRITE_LITERAL,
|
||||
style,
|
||||
"writing a literal with a format string"
|
||||
}
|
||||
|
||||
pub struct Write {
|
||||
format_args: FormatArgsStorage,
|
||||
// The outermost `impl Debug` we're currently in. While we're in one, `USE_DEBUG` is deactivated
|
||||
outermost_debug_impl: Option<OwnerId>,
|
||||
allow_print_in_tests: bool,
|
||||
}
|
||||
|
||||
impl Write {
|
||||
pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self {
|
||||
Self {
|
||||
format_args,
|
||||
outermost_debug_impl: None,
|
||||
allow_print_in_tests: conf.allow_print_in_tests,
|
||||
}
|
||||
}
|
||||
|
||||
fn in_debug_impl(&self) -> bool {
|
||||
self.outermost_debug_impl.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(Write => [
|
||||
PRINT_WITH_NEWLINE,
|
||||
PRINTLN_EMPTY_STRING,
|
||||
PRINT_STDOUT,
|
||||
PRINT_STDERR,
|
||||
USE_DEBUG,
|
||||
PRINT_LITERAL,
|
||||
WRITE_WITH_NEWLINE,
|
||||
WRITELN_EMPTY_STRING,
|
||||
WRITE_LITERAL,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Write {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
// Only check for `impl Debug`s if we're not already in one
|
||||
if self.outermost_debug_impl.is_none() && is_debug_impl(cx, item) {
|
||||
self.outermost_debug_impl = Some(item.owner_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
// Only clear `self.outermost_debug_impl` if we're escaping the _outermost_ debug impl
|
||||
if self.outermost_debug_impl == Some(item.owner_id) {
|
||||
self.outermost_debug_impl = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
|
||||
return;
|
||||
};
|
||||
let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) else {
|
||||
return;
|
||||
};
|
||||
let Some(name) = diag_name.as_str().strip_suffix("_macro") else {
|
||||
return;
|
||||
};
|
||||
|
||||
let is_build_script = cx
|
||||
.sess()
|
||||
.opts
|
||||
.crate_name
|
||||
.as_ref()
|
||||
.is_some_and(|crate_name| crate_name == "build_script_build");
|
||||
|
||||
let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id);
|
||||
match diag_name {
|
||||
sym::print_macro | sym::println_macro if !allowed_in_tests => {
|
||||
if !is_build_script {
|
||||
span_lint(cx, PRINT_STDOUT, macro_call.span, format!("use of `{name}!`"));
|
||||
}
|
||||
},
|
||||
sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => {
|
||||
span_lint(cx, PRINT_STDERR, macro_call.span, format!("use of `{name}!`"));
|
||||
},
|
||||
sym::write_macro | sym::writeln_macro => {},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) {
|
||||
// ignore `writeln!(w)` and `write!(v, some_macro!())`
|
||||
if format_args.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
match diag_name {
|
||||
sym::print_macro | sym::eprint_macro | sym::write_macro => {
|
||||
with_newline::check(cx, format_args, ¯o_call, name);
|
||||
},
|
||||
sym::println_macro | sym::eprintln_macro | sym::writeln_macro => {
|
||||
empty_string::check(cx, format_args, ¯o_call, name);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
literal::check(cx, format_args, name);
|
||||
|
||||
if !self.in_debug_impl() {
|
||||
use_debug::check(cx, format_args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(of_trait),
|
||||
..
|
||||
}) = &item.kind
|
||||
&& let Some(trait_id) = of_trait.trait_ref.trait_def_id()
|
||||
{
|
||||
cx.tcx.is_diagnostic_item(sym::Debug, trait_id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
18
clippy_lints/src/write/use_debug.rs
Normal file
18
clippy_lints/src/write/use_debug.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::{FormatArgs, FormatArgsPiece, FormatPlaceholder, FormatTrait};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::USE_DEBUG;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs) {
|
||||
for piece in &format_args.template {
|
||||
if let &FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||
span: Some(span),
|
||||
format_trait: FormatTrait::Debug,
|
||||
..
|
||||
}) = piece
|
||||
{
|
||||
span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting");
|
||||
}
|
||||
}
|
||||
}
|
||||
78
clippy_lints/src/write/with_newline.rs
Normal file
78
clippy_lints/src/write/with_newline.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +70,55 @@ fn main() {
|
|||
//~^ write_literal
|
||||
}
|
||||
|
||||
fn escaping() {
|
||||
let mut v = Vec::new();
|
||||
|
||||
writeln!(v, "{{hello}}");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r"{{hello}}");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "'");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "\"");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r"'");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(
|
||||
v,
|
||||
"some hello \
|
||||
//~^ write_literal
|
||||
world!",
|
||||
);
|
||||
writeln!(
|
||||
v,
|
||||
"some 1\
|
||||
2 \\ 3",
|
||||
//~^^^ write_literal
|
||||
);
|
||||
writeln!(v, "\\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r"\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r#"\"#);
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "\\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "\r");
|
||||
//~^ write_literal
|
||||
|
||||
// should not lint
|
||||
writeln!(v, r"{}", "\r");
|
||||
}
|
||||
|
||||
fn issue_13959() {
|
||||
let mut v = Vec::new();
|
||||
writeln!(v, "\"");
|
||||
|
|
|
|||
|
|
@ -70,6 +70,59 @@ fn main() {
|
|||
//~^ write_literal
|
||||
}
|
||||
|
||||
fn escaping() {
|
||||
let mut v = Vec::new();
|
||||
|
||||
writeln!(v, "{}", "{hello}");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r"{}", r"{hello}");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "{}", '\'');
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "{}", '"');
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r"{}", '\'');
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(
|
||||
v,
|
||||
"some {}",
|
||||
"hello \
|
||||
//~^ write_literal
|
||||
world!",
|
||||
);
|
||||
writeln!(
|
||||
v,
|
||||
"some {}\
|
||||
{} \\ {}",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
//~^^^ write_literal
|
||||
);
|
||||
writeln!(v, "{}", "\\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r"{}", "\\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r#"{}"#, "\\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "{}", r"\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "{}", "\r");
|
||||
//~^ write_literal
|
||||
|
||||
// should not lint
|
||||
writeln!(v, r"{}", "\r");
|
||||
}
|
||||
|
||||
fn issue_13959() {
|
||||
let mut v = Vec::new();
|
||||
writeln!(v, "{}", r#"""#);
|
||||
|
|
|
|||
|
|
@ -145,7 +145,156 @@ LL + writeln!(v, "hello {0} {1}, world {2}", 2, 3, 4);
|
|||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:75:23
|
||||
--> tests/ui/write_literal.rs:76:23
|
||||
|
|
||||
LL | writeln!(v, "{}", "{hello}");
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", "{hello}");
|
||||
LL + writeln!(v, "{{hello}}");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:79:24
|
||||
|
|
||||
LL | writeln!(v, r"{}", r"{hello}");
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, r"{}", r"{hello}");
|
||||
LL + writeln!(v, r"{{hello}}");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:82:23
|
||||
|
|
||||
LL | writeln!(v, "{}", '\'');
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", '\'');
|
||||
LL + writeln!(v, "'");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:85:23
|
||||
|
|
||||
LL | writeln!(v, "{}", '"');
|
||||
| ^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", '"');
|
||||
LL + writeln!(v, "\"");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:88:24
|
||||
|
|
||||
LL | writeln!(v, r"{}", '\'');
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, r"{}", '\'');
|
||||
LL + writeln!(v, r"'");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:94:9
|
||||
|
|
||||
LL | / "hello \
|
||||
LL | |
|
||||
LL | | world!",
|
||||
| |_______________^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL ~ "some hello \
|
||||
LL +
|
||||
LL ~ world!",
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:102:9
|
||||
|
|
||||
LL | / "1",
|
||||
LL | | "2",
|
||||
LL | | "3",
|
||||
| |___________^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL ~ "some 1\
|
||||
LL ~ 2 \\ 3",
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:107:23
|
||||
|
|
||||
LL | writeln!(v, "{}", "\\");
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", "\\");
|
||||
LL + writeln!(v, "\\");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:110:24
|
||||
|
|
||||
LL | writeln!(v, r"{}", "\\");
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, r"{}", "\\");
|
||||
LL + writeln!(v, r"\");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:113:26
|
||||
|
|
||||
LL | writeln!(v, r#"{}"#, "\\");
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, r#"{}"#, "\\");
|
||||
LL + writeln!(v, r#"\"#);
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:116:23
|
||||
|
|
||||
LL | writeln!(v, "{}", r"\");
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", r"\");
|
||||
LL + writeln!(v, "\\");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:119:23
|
||||
|
|
||||
LL | writeln!(v, "{}", "\r");
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", "\r");
|
||||
LL + writeln!(v, "\r");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:128:23
|
||||
|
|
||||
LL | writeln!(v, "{}", r#"""#);
|
||||
| ^^^^^^
|
||||
|
|
@ -157,7 +306,7 @@ LL + writeln!(v, "\"");
|
|||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:80:9
|
||||
--> tests/ui/write_literal.rs:133:9
|
||||
|
|
||||
LL | / r#"
|
||||
LL | |
|
||||
|
|
@ -182,7 +331,7 @@ LL ~ "
|
|||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:94:55
|
||||
--> tests/ui/write_literal.rs:147:55
|
||||
|
|
||||
LL | writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
|
||||
| ^^^
|
||||
|
|
@ -194,7 +343,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
|
|||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:96:52
|
||||
--> tests/ui/write_literal.rs:149:52
|
||||
|
|
||||
LL | writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
|
||||
| ^^^
|
||||
|
|
@ -206,7 +355,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
|
|||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:98:49
|
||||
--> tests/ui/write_literal.rs:151:49
|
||||
|
|
||||
LL | writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
|
||||
| ^^^
|
||||
|
|
@ -218,7 +367,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
|
|||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal.rs:100:43
|
||||
--> tests/ui/write_literal.rs:153:43
|
||||
|
|
||||
LL | writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
|
||||
| ^^^
|
||||
|
|
@ -229,5 +378,5 @@ LL - writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
|
|||
LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
|
||||
|
|
||||
|
||||
error: aborting due to 18 previous errors
|
||||
error: aborting due to 30 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
//@no-rustfix: overlapping suggestions
|
||||
#![allow(unused_must_use)]
|
||||
#![warn(clippy::write_literal)]
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
fn main() {
|
||||
let mut v = Vec::new();
|
||||
|
||||
writeln!(v, "{}", "{hello}");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r"{}", r"{hello}");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "{}", '\'');
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "{}", '"');
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r"{}", '"');
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r"{}", '\'');
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(
|
||||
v,
|
||||
"some {}",
|
||||
"hello \
|
||||
//~^ write_literal
|
||||
world!",
|
||||
);
|
||||
writeln!(
|
||||
v,
|
||||
"some {}\
|
||||
{} \\ {}",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
//~^^^ write_literal
|
||||
);
|
||||
writeln!(v, "{}", "\\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r"{}", "\\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, r#"{}"#, "\\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "{}", r"\");
|
||||
//~^ write_literal
|
||||
|
||||
writeln!(v, "{}", "\r");
|
||||
//~^ write_literal
|
||||
|
||||
// hard mode
|
||||
writeln!(v, r#"{}{}"#, '#', '"');
|
||||
//~^ write_literal
|
||||
|
||||
// should not lint
|
||||
writeln!(v, r"{}", "\r");
|
||||
}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:10:23
|
||||
|
|
||||
LL | writeln!(v, "{}", "{hello}");
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: `-D clippy::write-literal` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::write_literal)]`
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", "{hello}");
|
||||
LL + writeln!(v, "{{hello}}");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:13:24
|
||||
|
|
||||
LL | writeln!(v, r"{}", r"{hello}");
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, r"{}", r"{hello}");
|
||||
LL + writeln!(v, r"{{hello}}");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:16:23
|
||||
|
|
||||
LL | writeln!(v, "{}", '\'');
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", '\'');
|
||||
LL + writeln!(v, "'");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:19:23
|
||||
|
|
||||
LL | writeln!(v, "{}", '"');
|
||||
| ^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", '"');
|
||||
LL + writeln!(v, "\"");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:22:24
|
||||
|
|
||||
LL | writeln!(v, r"{}", '"');
|
||||
| ^^^
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:25:24
|
||||
|
|
||||
LL | writeln!(v, r"{}", '\'');
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, r"{}", '\'');
|
||||
LL + writeln!(v, r"'");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:31:9
|
||||
|
|
||||
LL | / "hello \
|
||||
LL | |
|
||||
LL | | world!",
|
||||
| |_______________^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL ~ "some hello \
|
||||
LL +
|
||||
LL ~ world!",
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:39:9
|
||||
|
|
||||
LL | / "1",
|
||||
LL | | "2",
|
||||
LL | | "3",
|
||||
| |___________^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL ~ "some 1\
|
||||
LL ~ 2 \\ 3",
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:44:23
|
||||
|
|
||||
LL | writeln!(v, "{}", "\\");
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", "\\");
|
||||
LL + writeln!(v, "\\");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:47:24
|
||||
|
|
||||
LL | writeln!(v, r"{}", "\\");
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, r"{}", "\\");
|
||||
LL + writeln!(v, r"\");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:50:26
|
||||
|
|
||||
LL | writeln!(v, r#"{}"#, "\\");
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, r#"{}"#, "\\");
|
||||
LL + writeln!(v, r#"\"#);
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:53:23
|
||||
|
|
||||
LL | writeln!(v, "{}", r"\");
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", r"\");
|
||||
LL + writeln!(v, "\\");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:56:23
|
||||
|
|
||||
LL | writeln!(v, "{}", "\r");
|
||||
| ^^^^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL - writeln!(v, "{}", "\r");
|
||||
LL + writeln!(v, "\r");
|
||||
|
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_2.rs:60:28
|
||||
|
|
||||
LL | writeln!(v, r#"{}{}"#, '#', '"');
|
||||
| ^^^^^^^^
|
||||
|
||||
error: aborting due to 14 previous errors
|
||||
|
||||
16
tests/ui/write_literal_unfixable.rs
Normal file
16
tests/ui/write_literal_unfixable.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//@no-rustfix
|
||||
#![allow(unused_must_use)]
|
||||
#![warn(clippy::write_literal)]
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
fn escaping() {
|
||||
let mut v = vec![];
|
||||
|
||||
writeln!(v, r"{}", '"');
|
||||
//~^ write_literal
|
||||
|
||||
// hard mode
|
||||
writeln!(v, r#"{}{}"#, '#', '"');
|
||||
//~^ write_literal
|
||||
}
|
||||
17
tests/ui/write_literal_unfixable.stderr
Normal file
17
tests/ui/write_literal_unfixable.stderr
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_unfixable.rs:10:24
|
||||
|
|
||||
LL | writeln!(v, r"{}", '"');
|
||||
| ^^^
|
||||
|
|
||||
= note: `-D clippy::write-literal` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::write_literal)]`
|
||||
|
||||
error: literal with an empty format string
|
||||
--> tests/ui/write_literal_unfixable.rs:14:28
|
||||
|
|
||||
LL | writeln!(v, r#"{}{}"#, '#', '"');
|
||||
| ^^^^^^^^
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue