From 83487c060ff64498fb9a3eb472ce71af1d26fe89 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar <manishsmail@gmail.com> Date: Wed, 12 Aug 2015 16:44:14 +0530 Subject: [PATCH 1/6] Add trim_multiline utility (fixes #139) --- src/collapsible_if.rs | 4 ++-- src/lib.rs | 1 + src/misc.rs | 4 ++-- src/utils.rs | 29 +++++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) 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..5b9c995589b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -51,6 +51,35 @@ 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<'a>(s: Cow<'a, str>, ignore_first: bool) -> Cow<'a, str> { + let x = s.lines().skip(ignore_first as usize) + .map(|l| l.char_indices() + .find(|&(_,x)| x != ' ') + .unwrap_or((l.len(),' ')).0) + .min().unwrap_or(0); + if x > 0 { + Cow::Owned(s.lines().enumerate().map(|(i,l)| if ignore_first && i==0 { + l + } else { + l.split_at(x).1 + }).collect::<Vec<_>>() + .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; From fbbb44d93bb566d5b3346b180a659ebdcd815fe5 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar <manishsmail@gmail.com> Date: Thu, 13 Aug 2015 13:27:30 +0530 Subject: [PATCH 2/6] Handle tabs --- src/utils.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 5b9c995589b..3be6993b759 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -63,10 +63,16 @@ pub fn snippet_block<'a>(cx: &Context, span: Span, default: &'a str) -> Cow<'a, /// Trim indentation from a multiline string /// with possibility of ignoring the first line pub fn trim_multiline<'a>(s: Cow<'a, str>, ignore_first: bool) -> Cow<'a, str> { + 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<'a>(s: Cow<'a, str>, ignore_first: bool, ch: char) -> Cow<'a, str> { let x = s.lines().skip(ignore_first as usize) .map(|l| l.char_indices() - .find(|&(_,x)| x != ' ') - .unwrap_or((l.len(),' ')).0) + .find(|&(_,x)| x != ch) + .unwrap_or((l.len(), ch)).0) .min().unwrap_or(0); if x > 0 { Cow::Owned(s.lines().enumerate().map(|(i,l)| if ignore_first && i==0 { From f4b5d215332da92256bcc4d0c4b9f8b7ef6d6f72 Mon Sep 17 00:00:00 2001 From: llogiq <bogusandre@gmail.com> Date: Thu, 13 Aug 2015 15:46:00 +0200 Subject: [PATCH 3/6] added a few unit tests to trim_multiline --- tests/trim_multiline.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/trim_multiline.rs diff --git a/tests/trim_multiline.rs b/tests/trim_multiline.rs new file mode 100644 index 00000000000..988e5d1012f --- /dev/null +++ b/tests/trim_multiline.rs @@ -0,0 +1,38 @@ +/// 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)); +} From dece5a6cb53248d67a761b8bd94e47cef776fcd6 Mon Sep 17 00:00:00 2001 From: llogiq <bogusandre@gmail.com> Date: Thu, 13 Aug 2015 15:48:48 +0200 Subject: [PATCH 4/6] added empty line test --- tests/trim_multiline.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/trim_multiline.rs b/tests/trim_multiline.rs index 988e5d1012f..e29ee4922cd 100644 --- a/tests/trim_multiline.rs +++ b/tests/trim_multiline.rs @@ -36,3 +36,19 @@ if x { \tz }".into(), false)); } + +#[test] +fn test_empty_line() { + assert_eq!("\ +if x { + y + +} else { + z +}", trim_multiline(" if x { + y + + } else { + z + }".into(), false)); +} From 5ce8e7ba85fa50e067a5262fccd130e071b679fb Mon Sep 17 00:00:00 2001 From: Manish Goregaokar <manishsmail@gmail.com> Date: Thu, 13 Aug 2015 19:29:12 +0530 Subject: [PATCH 5/6] trim_multiline: ignore empty lines --- src/utils.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 3be6993b759..490d2f6d1f6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -70,12 +70,15 @@ pub fn trim_multiline<'a>(s: Cow<'a, str>, ignore_first: bool) -> Cow<'a, str> { fn trim_multiline_inner<'a>(s: Cow<'a, str>, ignore_first: bool, ch: char) -> Cow<'a, str> { let x = s.lines().skip(ignore_first as usize) - .map(|l| l.char_indices() - .find(|&(_,x)| x != ch) - .unwrap_or((l.len(), ch)).0) + .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 { + Cow::Owned(s.lines().enumerate().map(|(i,l)| if (ignore_first && i == 0) || + l.len() == 0 { l } else { l.split_at(x).1 From 763ae1f3ae9644110f0af893c79719be00288311 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar <manishsmail@gmail.com> Date: Thu, 13 Aug 2015 23:20:00 +0530 Subject: [PATCH 6/6] Fix dogfood --- src/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 490d2f6d1f6..67a89b067e6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -62,13 +62,13 @@ pub fn snippet_block<'a>(cx: &Context, span: Span, default: &'a str) -> Cow<'a, /// Trim indentation from a multiline string /// with possibility of ignoring the first line -pub fn trim_multiline<'a>(s: Cow<'a, str>, ignore_first: bool) -> Cow<'a, str> { +pub fn trim_multiline(s: Cow<str>, ignore_first: bool) -> Cow<str> { 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<'a>(s: Cow<'a, str>, ignore_first: bool, ch: char) -> Cow<'a, str> { +fn trim_multiline_inner(s: Cow<str>, ignore_first: bool, ch: char) -> Cow<str> { let x = s.lines().skip(ignore_first as usize) .filter_map(|l| { if l.len() > 0 { // ignore empty lines Some(l.char_indices()