-use crate::utils::{snippet, span_lint, span_lint_and_sugg};
+use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg};
use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
use rustc::{declare_tool_lint, lint_array};
+use rustc_errors::Applicability;
use std::borrow::Cow;
use syntax::ast::*;
use syntax::parse::{parser, token};
-use syntax::tokenstream::{ThinTokenStream, TokenStream};
+use syntax::tokenstream::TokenStream;
/// **What it does:** This lint warns when you use `println!("")` to
/// print a newline.
"using `println!(\"\")`",
"replace it with",
"println!()".to_string(),
+ Applicability::MachineApplicable,
);
}
}
} else if mac.node.path == "print" {
span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false).0 {
- if fmtstr.ends_with("\\n") &&
- // don't warn about strings with several `\n`s (#3126)
- fmtstr.matches("\\n").count() == 1
- {
+ if check_newlines(&fmtstr) {
span_lint(
cx,
PRINT_WITH_NEWLINE,
}
} else if mac.node.path == "write" {
if let Some(fmtstr) = check_tts(cx, &mac.node.tts, true).0 {
- if fmtstr.ends_with("\\n") &&
- // don't warn about strings with several `\n`s (#3126)
- fmtstr.matches("\\n").count() == 1
- {
+ if check_newlines(&fmtstr) {
span_lint(
cx,
WRITE_WITH_NEWLINE,
let check_tts = check_tts(cx, &mac.node.tts, true);
if let Some(fmtstr) = check_tts.0 {
if fmtstr == "" {
- let suggestion = check_tts
- .1
- .map_or(Cow::Borrowed("v"), |expr| snippet(cx, expr.span, "v"));
+ let mut applicability = Applicability::MachineApplicable;
+ let suggestion = check_tts.1.map_or_else(
+ move || {
+ applicability = Applicability::HasPlaceholders;
+ Cow::Borrowed("v")
+ },
+ move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability),
+ );
span_lint_and_sugg(
cx,
format!("using `writeln!({}, \"\")`", suggestion).as_str(),
"replace it with",
format!("writeln!({})", suggestion),
+ applicability,
);
}
}
}
}
-fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -> (Option<String>, Option<Expr>) {
- let tts = TokenStream::from(tts.clone());
+/// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
+/// options. The first part of the tuple is `format_str` of the macros. The second part of the tuple
+/// is in the `write[ln]!` case the expression the `format_str` should be written to.
+///
+/// Example:
+///
+/// Calling this function on
+/// ```rust,ignore
+/// writeln!(buf, "string to write: {}", something)
+/// ```
+/// will return
+/// ```rust,ignore
+/// (Some("string to write: {}"), Some(buf))
+/// ```
+fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<String>, Option<Expr>) {
+ use fmt_macros::*;
+ let tts = tts.clone();
let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false);
let mut expr: Option<Expr> = None;
if is_write {
Ok(token) => token.0.to_string(),
Err(_) => return (None, expr),
};
- use fmt_macros::*;
let tmp = fmtstr.clone();
let mut args = vec![];
- let mut fmt_parser = Parser::new(&tmp, None);
+ let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
while let Some(piece) = fmt_parser.next() {
if !fmt_parser.errors.is_empty() {
return (None, expr);
let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
let mut idx = 0;
loop {
- if !parser.eat(&token::Comma) {
- assert!(parser.eat(&token::Eof));
- return (Some(fmtstr), expr);
- }
- let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
- Ok(expr) => expr,
- Err(_) => return (Some(fmtstr), None),
- };
const SIMPLE: FormatSpec<'_> = FormatSpec {
fill: None,
align: AlignUnknown,
width: CountImplied,
ty: "",
};
+ if !parser.eat(&token::Comma) {
+ return (Some(fmtstr), expr);
+ }
+ let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
+ Ok(expr) => expr,
+ Err(_) => return (Some(fmtstr), None),
+ };
match &token_expr.node {
ExprKind::Lit(_) => {
let mut all_simple = true;
let mut seen = false;
for arg in &args {
match arg.position {
- ArgumentImplicitlyIs(n) | ArgumentIs(n) => if n == idx {
- all_simple &= arg.format == SIMPLE;
- seen = true;
+ ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
+ if n == idx {
+ all_simple &= arg.format == SIMPLE;
+ seen = true;
+ }
},
ArgumentNamed(_) => {},
}
for arg in &args {
match arg.position {
ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
- ArgumentNamed(name) => if *p == name {
- seen = true;
- all_simple &= arg.format == SIMPLE;
+ ArgumentNamed(name) => {
+ if *p == name {
+ seen = true;
+ all_simple &= arg.format == SIMPLE;
+ }
},
}
}
}
}
}
+
+// Checks if `s` constains a single newline that terminates it
+fn check_newlines(s: &str) -> bool {
+ if s.len() < 2 {
+ return false;
+ }
+
+ let bytes = s.as_bytes();
+ if bytes[bytes.len() - 2] != b'\\' || bytes[bytes.len() - 1] != b'n' {
+ return false;
+ }
+
+ let mut escaping = false;
+ for (index, &byte) in bytes.iter().enumerate() {
+ if escaping {
+ if byte == b'n' {
+ return index == bytes.len() - 1;
+ }
+ escaping = false;
+ } else if byte == b'\\' {
+ escaping = true;
+ }
+ }
+
+ false
+}