use std::ops::Range;
use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then};
-use rustc_ast::ast::{Expr, ExprKind, Item, ItemKind, MacCall, StrLit, StrStyle};
+use if_chain::if_chain;
+use rustc_ast::ast::{
+ Expr, ExprKind, ImplKind, Item, ItemKind, LitKind, MacCall, StrLit, StrStyle,
+};
use rustc_ast::token;
use rustc_ast::tokenstream::TokenStream;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_parse::parser;
use rustc_session::{declare_tool_lint, impl_lint_pass};
-use rustc_span::symbol::Symbol;
-use rustc_span::{BytePos, Span};
+use rustc_span::symbol::kw;
+use rustc_span::{sym, BytePos, Span};
declare_clippy_lint! {
/// **What it does:** This lint warns when you use `println!("")` to
"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 is this bad?** 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:**
+ /// ```rust
+ /// eprintln!("Hello world!");
+ /// ```
+ pub PRINT_STDERR,
+ restriction,
+ "printing on stderr"
+}
+
declare_clippy_lint! {
/// **What it does:** Checks for use of `Debug` formatting. The purpose of this
/// lint is to catch debugging remnants.
/// ```rust
/// # use std::fmt::Write;
/// # let mut buf = String::new();
- ///
/// // Bad
/// writeln!(buf, "");
///
/// # use std::fmt::Write;
/// # let mut buf = String::new();
/// # let name = "World";
- ///
/// // Bad
/// write!(buf, "Hello {}!\n", name);
///
/// ```rust
/// # use std::fmt::Write;
/// # let mut buf = String::new();
- ///
/// // Bad
/// writeln!(buf, "{}", "foo");
///
PRINT_WITH_NEWLINE,
PRINTLN_EMPTY_STRING,
PRINT_STDOUT,
+ PRINT_STDERR,
USE_DEBUG,
PRINT_LITERAL,
WRITE_WITH_NEWLINE,
impl EarlyLintPass for Write {
fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) {
- if let ItemKind::Impl {
+ if let ItemKind::Impl(box ImplKind {
of_trait: Some(trait_ref),
..
- } = &item.kind
+ }) = &item.kind
{
let trait_name = trait_ref
.path
.expect("path has at least one segment")
.ident
.name;
- if trait_name == sym!(Debug) {
+ if trait_name == sym::Debug {
self.in_debug_impl = true;
}
}
.map_or(false, |crate_name| crate_name == "build_script_build")
}
- if mac.path == sym!(println) {
- if !is_build_script(cx) {
- span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`");
- }
- if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
- if fmt_str.symbol == Symbol::intern("") {
- span_lint_and_sugg(
- cx,
- PRINTLN_EMPTY_STRING,
- mac.span(),
- "using `println!(\"\")`",
- "replace it with",
- "println!()".to_string(),
- Applicability::MachineApplicable,
- );
- }
- }
- } else if mac.path == sym!(print) {
+ if mac.path == sym!(print) {
if !is_build_script(cx) {
span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`");
}
- if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
- if check_newlines(&fmt_str) {
- span_lint_and_then(
- cx,
- PRINT_WITH_NEWLINE,
- mac.span(),
- "using `print!()` with a format string that ends in a single newline",
- |err| {
- err.multipart_suggestion(
- "use `println!` instead",
- vec![
- (mac.path.span, String::from("println")),
- (newline_span(&fmt_str), String::new()),
- ],
- Applicability::MachineApplicable,
- );
- },
- );
- }
+ self.lint_print_with_newline(cx, mac);
+ } else if mac.path == sym!(println) {
+ if !is_build_script(cx) {
+ span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`");
}
+ self.lint_println_empty_string(cx, mac);
+ } else if mac.path == sym!(eprint) {
+ span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprint!`");
+ self.lint_print_with_newline(cx, mac);
+ } else if mac.path == sym!(eprintln) {
+ span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprintln!`");
+ self.lint_println_empty_string(cx, mac);
} else if mac.path == sym!(write) {
if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), true) {
if check_newlines(&fmt_str) {
}
} else if mac.path == sym!(writeln) {
if let (Some(fmt_str), expr) = self.check_tts(cx, mac.args.inner_tokens(), true) {
- if fmt_str.symbol == Symbol::intern("") {
+ if fmt_str.symbol == kw::Empty {
let mut applicability = Applicability::MachineApplicable;
// FIXME: remove this `#[allow(...)]` once the issue #5822 gets fixed
#[allow(clippy::option_if_let_else)]
/// (Some("string to write: {}"), Some(buf))
/// ```
#[allow(clippy::too_many_lines)]
- fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool) -> (Option<StrLit>, Option<Expr>) {
+ fn check_tts<'a>(
+ &self,
+ cx: &EarlyContext<'a>,
+ tts: TokenStream,
+ is_write: bool,
+ ) -> (Option<StrLit>, Option<Expr>) {
use rustc_parse_format::{
- AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, ParseMode, Parser,
- Piece,
+ AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied,
+ FormatSpec, ParseMode, Parser, Piece,
};
let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None);
if let Piece::NextArgument(arg) = piece {
if !self.in_debug_impl && arg.format.ty == "?" {
// FIXME: modify rustc's fmt string parser to give us the current span
- span_lint(cx, USE_DEBUG, parser.prev_token.span, "use of `Debug`-based formatting");
+ span_lint(
+ cx,
+ USE_DEBUG,
+ parser.prev_token.span,
+ "use of `Debug`-based formatting",
+ );
}
args.push(arg);
}
return (Some(fmtstr), None);
};
match &token_expr.kind {
- ExprKind::Lit(_) => {
+ ExprKind::Lit(lit)
+ if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)) =>
+ {
let mut all_simple = true;
let mut seen = false;
for arg in &args {
all_simple &= arg.format == SIMPLE;
seen = true;
}
- },
- ArgumentNamed(_) => {},
+ }
+ ArgumentNamed(_) => {}
}
}
if all_simple && seen {
span_lint(cx, lint, token_expr.span, "literal with an empty format string");
}
idx += 1;
- },
+ }
ExprKind::Assign(lhs, rhs, _) => {
- if let ExprKind::Lit(_) = rhs.kind {
- if let ExprKind::Path(_, p) = &lhs.kind {
+ if_chain! {
+ if let ExprKind::Lit(ref lit) = rhs.kind;
+ if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..));
+ if let ExprKind::Path(_, p) = &lhs.kind;
+ then {
let mut all_simple = true;
let mut seen = false;
for arg in &args {
}
}
}
- },
+ }
_ => idx += 1,
}
}
}
+
+ fn lint_println_empty_string(&self, cx: &EarlyContext<'_>, mac: &MacCall) {
+ if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
+ if fmt_str.symbol == kw::Empty {
+ let name = mac.path.segments[0].ident.name;
+ span_lint_and_sugg(
+ cx,
+ PRINTLN_EMPTY_STRING,
+ mac.span(),
+ &format!("using `{}!(\"\")`", name),
+ "replace it with",
+ format!("{}!()", name),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+
+ fn lint_print_with_newline(&self, cx: &EarlyContext<'_>, mac: &MacCall) {
+ if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
+ if check_newlines(&fmt_str) {
+ let name = mac.path.segments[0].ident.name;
+ let suggested = format!("{}ln", name);
+ span_lint_and_then(
+ cx,
+ PRINT_WITH_NEWLINE,
+ mac.span(),
+ &format!(
+ "using `{}!()` with a format string that ends in a single newline",
+ name
+ ),
+ |err| {
+ err.multipart_suggestion(
+ &format!("use `{}!` instead", suggested),
+ vec![
+ (mac.path.span, suggested),
+ (newline_span(&fmt_str), String::new()),
+ ],
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+ }
}
/// Checks if the format string contains a single newline that terminates it.