diff --git a/src/collapsible_if.rs b/src/collapsible_if.rs index be34458e0dc..8a41f208938 100644 --- a/src/collapsible_if.rs +++ b/src/collapsible_if.rs @@ -18,7 +18,7 @@ use rustc::middle::def::*; use syntax::ast::*; use syntax::ptr::P; use syntax::codemap::{Span, Spanned, ExpnInfo}; -use utils::{in_macro, span_help_and_lint, snippet}; +use utils::{in_macro, span_help_and_lint, snippet, snippet_block}; declare_lint! { pub COLLAPSIBLE_IF, @@ -55,7 +55,7 @@ fn check_expr_expd(cx: &Context, e: &Expr, info: Option<&ExpnInfo>) { "this if statement can be collapsed", &format!("try\nif {} && {} {}", check_to_string(cx, check), check_to_string(cx, check_inner), - snippet(cx, content.span, ".."))); + snippet_block(cx, content.span, ".."))); } } } diff --git a/src/lib.rs b/src/lib.rs index 9135ecaca6c..01a2d65606c 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![feature(plugin_registrar, box_syntax)] #![feature(rustc_private, collections)] +#![feature(str_split_at)] #![allow(unused_imports, unknown_lints)] #[macro_use] diff --git a/src/misc.rs b/src/misc.rs index 7372bfed6c5..861e4a73dd2 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -7,7 +7,7 @@ use rustc::lint::{Context, LintPass, LintArray, Lint, Level}; use rustc::middle::ty; use syntax::codemap::{Span, Spanned}; -use utils::{match_path, snippet, span_lint, span_help_and_lint, walk_ptrs_ty}; +use utils::{match_path, snippet, snippet_block, span_lint, span_help_and_lint, walk_ptrs_ty}; /// Handles uncategorized lints /// Currently handles linting of if-let-able matches @@ -37,7 +37,7 @@ impl LintPass for MiscPass { // an enum is extended. So we only consider cases where a `_` wildcard is used if arms[1].pats[0].node == PatWild(PatWildSingle) && arms[0].pats.len() == 1 { - let body_code = snippet(cx, arms[0].body.span, ".."); + let body_code = snippet_block(cx, arms[0].body.span, ".."); let suggestion = if let ExprBlock(_) = arms[0].body.node { body_code.into_owned() } else { diff --git a/src/utils.rs b/src/utils.rs index c54a7f56f7e..67a89b067e6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -51,6 +51,44 @@ pub fn snippet<'a>(cx: &Context, span: Span, default: &'a str) -> Cow<'a, str> { cx.sess().codemap().span_to_snippet(span).map(From::from).unwrap_or(Cow::Borrowed(default)) } +/// convert a span (from a block) to a code snippet if available, otherwise use default, e.g. +/// `snippet(cx, expr.span, "..")` +/// This trims the code of indentation, except for the first line +/// Use it for blocks or block-like things which need to be printed as such +pub fn snippet_block<'a>(cx: &Context, span: Span, default: &'a str) -> Cow<'a, str> { + let snip = snippet(cx, span, default); + trim_multiline(snip, true) +} + +/// Trim indentation from a multiline string +/// with possibility of ignoring the first line +pub fn trim_multiline(s: Cow, ignore_first: bool) -> Cow { + let s = trim_multiline_inner(s, ignore_first, ' '); + let s = trim_multiline_inner(s, ignore_first, '\t'); + trim_multiline_inner(s, ignore_first, ' ') +} + +fn trim_multiline_inner(s: Cow, ignore_first: bool, ch: char) -> Cow { + let x = s.lines().skip(ignore_first as usize) + .filter_map(|l| { if l.len() > 0 { // ignore empty lines + Some(l.char_indices() + .find(|&(_,x)| x != ch) + .unwrap_or((l.len(), ch)).0) + } else {None}}) + .min().unwrap_or(0); + if x > 0 { + Cow::Owned(s.lines().enumerate().map(|(i,l)| if (ignore_first && i == 0) || + l.len() == 0 { + l + } else { + l.split_at(x).1 + }).collect::>() + .join("\n")) + } else { + s + } +} + /// get a parent expr if any – this is useful to constrain a lint pub fn get_parent_expr<'c>(cx: &'c Context, e: &Expr) -> Option<&'c Expr> { let map = &cx.tcx.map; diff --git a/tests/trim_multiline.rs b/tests/trim_multiline.rs new file mode 100644 index 00000000000..e29ee4922cd --- /dev/null +++ b/tests/trim_multiline.rs @@ -0,0 +1,54 @@ +/// test the multiline-trim function +#[allow(plugin_as_library)] +extern crate clippy; + +use clippy::utils::trim_multiline; + +#[test] +fn test_single_line() { + assert_eq!("", trim_multiline("".into(), false)); + assert_eq!("...", trim_multiline("...".into(), false)); + assert_eq!("...", trim_multiline(" ...".into(), false)); + assert_eq!("...", trim_multiline("\t...".into(), false)); + assert_eq!("...", trim_multiline("\t\t...".into(), false)); +} + +#[test] +fn test_block() { + assert_eq!("\ +if x { + y +} else { + z +}", trim_multiline(" if x { + y + } else { + z + }".into(), false)); + assert_eq!("\ +if x { +\ty +} else { +\tz +}", trim_multiline(" if x { + \ty + } else { + \tz + }".into(), false)); +} + +#[test] +fn test_empty_line() { + assert_eq!("\ +if x { + y + +} else { + z +}", trim_multiline(" if x { + y + + } else { + z + }".into(), false)); +}