Fix false negative in TRIVIAL_REGEX

This commit is contained in:
mcarton 2016-02-06 18:06:39 +01:00
parent f53a830c8c
commit d9a2a7ac3c
2 changed files with 65 additions and 42 deletions

View file

@ -55,32 +55,40 @@ impl LateLintPass for RegexPass {
], { ], {
if let ExprLit(ref lit) = args[0].node { if let ExprLit(ref lit) = args[0].node {
if let LitStr(ref r, _) = lit.node { if let LitStr(ref r, _) = lit.node {
if let Err(e) = regex_syntax::Expr::parse(r) { match regex_syntax::Expr::parse(r) {
span_lint(cx, Ok(r) => {
INVALID_REGEX, if let Some(repl) = is_trivial_regex(&r) {
str_span(args[0].span, &r, e.position()), span_help_and_lint(cx, TRIVIAL_REGEX, args[0].span,
&format!("regex syntax error: {}", &"trivial regex",
e.description())); &format!("consider using {}", repl));
} }
else if let Some(repl) = is_trivial_regex(r) { }
span_help_and_lint(cx, TRIVIAL_REGEX, args[0].span, Err(e) => {
&"trivial regex", span_lint(cx,
&format!("consider using {}", repl)); INVALID_REGEX,
str_span(args[0].span, &r, e.position()),
&format!("regex syntax error: {}",
e.description()));
}
} }
} }
} else if let Some(r) = const_str(cx, &*args[0]) { } else if let Some(r) = const_str(cx, &*args[0]) {
if let Err(e) = regex_syntax::Expr::parse(&r) { match regex_syntax::Expr::parse(&r) {
span_lint(cx, Ok(r) => {
INVALID_REGEX, if let Some(repl) = is_trivial_regex(&r) {
args[0].span, span_help_and_lint(cx, TRIVIAL_REGEX, args[0].span,
&format!("regex syntax error on position {}: {}", &"trivial regex",
e.position(), &format!("consider using {}", repl));
e.description())); }
} }
else if let Some(repl) = is_trivial_regex(&r) { Err(e) => {
span_help_and_lint(cx, TRIVIAL_REGEX, args[0].span, span_lint(cx,
&"trivial regex", INVALID_REGEX,
&format!("{}", repl)); args[0].span,
&format!("regex syntax error on position {}: {}",
e.position(),
e.description()));
}
} }
} }
}} }}
@ -103,25 +111,31 @@ fn const_str(cx: &LateContext, e: &Expr) -> Option<InternedString> {
} }
} }
fn is_trivial_regex(s: &str) -> Option<&'static str> { fn is_trivial_regex(s: &regex_syntax::Expr) -> Option<&'static str> {
// some unlikely but valid corner cases use regex_syntax::Expr;
match s {
"" | "^" | "$" => return Some("the regex is unlikely to be useful as it is"),
"^$" => return Some("consider using `str::is_empty`"),
_ => (),
}
let (start, end, repl) = match (s.starts_with('^'), s.ends_with('$')) { match *s {
(true, true) => (1, s.len()-1, "consider using `==` on `str`s"), Expr::Empty | Expr::StartText | Expr::EndText => Some("the regex is unlikely to be useful as it is"),
(false, true) => (0, s.len()-1, "consider using `str::ends_with`"), Expr::Literal {..} => Some("consider using `str::contains`"),
(true, false) => (1, s.len(), "consider using `str::starts_with`"), Expr::Concat(ref exprs) => {
(false, false) => (0, s.len(), "consider using `str::contains`"), match exprs.len() {
}; 2 => match (&exprs[0], &exprs[1]) {
(&Expr::StartText, &Expr::EndText) => Some("consider using `str::is_empty`"),
if !s.chars().take(end).skip(start).any(regex_syntax::is_punct) { (&Expr::StartText, &Expr::Literal {..}) => Some("consider using `str::starts_with`"),
Some(repl) (&Expr::Literal {..}, &Expr::EndText) => Some("consider using `str::ends_with`"),
} _ => None,
else { },
None 3 => {
if let (&Expr::StartText, &Expr::Literal {..}, &Expr::EndText) = (&exprs[0], &exprs[1], &exprs[2]) {
Some("consider using `==` on `str`s")
}
else {
None
}
},
_ => None,
}
}
_ => None,
} }
} }

View file

@ -45,16 +45,25 @@ fn trivial_regex() {
//~^ERROR: trivial regex //~^ERROR: trivial regex
//~|HELP consider using `str::contains` //~|HELP consider using `str::contains`
let trivial_backslash = Regex::new("a\\.b");
//~^ERROR: trivial regex
//~|HELP consider using `str::contains`
// unlikely corner cases // unlikely corner cases
let trivial_empty = Regex::new(""); let trivial_empty = Regex::new("");
//~^ERROR: trivial regex //~^ERROR: trivial regex
//~|HELP the regex is unlikely to be useful //~|HELP the regex is unlikely to be useful
let trivial_empty = Regex::new("^");
//~^ERROR: trivial regex
//~|HELP the regex is unlikely to be useful
let trivial_empty = Regex::new("^$"); let trivial_empty = Regex::new("^$");
//~^ERROR: trivial regex //~^ERROR: trivial regex
//~|HELP consider using `str::is_empty` //~|HELP consider using `str::is_empty`
// non-trivial regexes // non-trivial regexes
let non_trivial_dot = Regex::new("a.b");
let non_trivial_eq = Regex::new("^foo|bar$"); let non_trivial_eq = Regex::new("^foo|bar$");
let non_trivial_starts_with = Regex::new("^foo|bar"); let non_trivial_starts_with = Regex::new("^foo|bar");
let non_trivial_ends_with = Regex::new("^foo|bar"); let non_trivial_ends_with = Regex::new("^foo|bar");