]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/regex.rs
Update to rustc master
[rust.git] / clippy_lints / src / regex.rs
1 use regex_syntax;
2 use rustc::hir::*;
3 use rustc::lint::*;
4 use std::collections::HashSet;
5 use std::error::Error;
6 use syntax::ast::{LitKind, NodeId, StrStyle};
7 use syntax::codemap::{BytePos, Span};
8 use utils::{is_expn_of, match_def_path, match_type, opt_def_id, paths, span_help_and_lint, span_lint};
9 use consts::{constant, Constant};
10
11 /// **What it does:** Checks [regex](https://crates.io/crates/regex) creation
12 /// (with `Regex::new`,`RegexBuilder::new` or `RegexSet::new`) for correct
13 /// regex syntax.
14 ///
15 /// **Why is this bad?** This will lead to a runtime panic.
16 ///
17 /// **Known problems:** None.
18 ///
19 /// **Example:**
20 /// ```rust
21 /// Regex::new("|")
22 /// ```
23 declare_lint! {
24     pub INVALID_REGEX,
25     Deny,
26     "invalid regular expressions"
27 }
28
29 /// **What it does:** Checks for trivial [regex](https://crates.io/crates/regex)
30 /// creation (with `Regex::new`, `RegexBuilder::new` or `RegexSet::new`).
31 ///
32 /// **Why is this bad?** Matching the regex can likely be replaced by `==` or
33 /// `str::starts_with`, `str::ends_with` or `std::contains` or other `str`
34 /// methods.
35 ///
36 /// **Known problems:** None.
37 ///
38 /// **Example:**
39 /// ```rust
40 /// Regex::new("^foobar")
41 /// ```
42 declare_lint! {
43     pub TRIVIAL_REGEX,
44     Warn,
45     "trivial regular expressions"
46 }
47
48 /// **What it does:** Checks for usage of `regex!(_)` which (as of now) is
49 /// usually slower than `Regex::new(_)` unless called in a loop (which is a bad
50 /// idea anyway).
51 ///
52 /// **Why is this bad?** Performance, at least for now. The macro version is
53 /// likely to catch up long-term, but for now the dynamic version is faster.
54 ///
55 /// **Known problems:** None.
56 ///
57 /// **Example:**
58 /// ```rust
59 /// regex!("foo|bar")
60 /// ```
61 declare_lint! {
62     pub REGEX_MACRO,
63     Warn,
64     "use of `regex!(_)` instead of `Regex::new(_)`"
65 }
66
67 #[derive(Clone, Default)]
68 pub struct Pass {
69     spans: HashSet<Span>,
70     last: Option<NodeId>,
71 }
72
73 impl LintPass for Pass {
74     fn get_lints(&self) -> LintArray {
75         lint_array!(INVALID_REGEX, REGEX_MACRO, TRIVIAL_REGEX)
76     }
77 }
78
79 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
80     fn check_crate(&mut self, _: &LateContext<'a, 'tcx>, _: &'tcx Crate) {
81         self.spans.clear();
82     }
83
84     fn check_block(&mut self, cx: &LateContext<'a, 'tcx>, block: &'tcx Block) {
85         if_chain! {
86             if self.last.is_none();
87             if let Some(ref expr) = block.expr;
88             if match_type(cx, cx.tables.expr_ty(expr), &paths::REGEX);
89             if let Some(span) = is_expn_of(expr.span, "regex");
90             then {
91                 if !self.spans.contains(&span) {
92                     span_lint(cx,
93                               REGEX_MACRO,
94                               span,
95                               "`regex!(_)` found. \
96                               Please use `Regex::new(_)`, which is faster for now.");
97                     self.spans.insert(span);
98                 }
99                 self.last = Some(block.id);
100             }
101         }
102     }
103
104     fn check_block_post(&mut self, _: &LateContext<'a, 'tcx>, block: &'tcx Block) {
105         if self.last.map_or(false, |id| block.id == id) {
106             self.last = None;
107         }
108     }
109
110     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
111         if_chain! {
112             if let ExprCall(ref fun, ref args) = expr.node;
113             if let ExprPath(ref qpath) = fun.node;
114             if args.len() == 1;
115             if let Some(def_id) = opt_def_id(cx.tables.qpath_def(qpath, fun.hir_id));
116             then {
117                 if match_def_path(cx.tcx, def_id, &paths::REGEX_NEW) ||
118                    match_def_path(cx.tcx, def_id, &paths::REGEX_BUILDER_NEW) {
119                     check_regex(cx, &args[0], true);
120                 } else if match_def_path(cx.tcx, def_id, &paths::REGEX_BYTES_NEW) ||
121                    match_def_path(cx.tcx, def_id, &paths::REGEX_BYTES_BUILDER_NEW) {
122                     check_regex(cx, &args[0], false);
123                 } else if match_def_path(cx.tcx, def_id, &paths::REGEX_SET_NEW) {
124                     check_set(cx, &args[0], true);
125                 } else if match_def_path(cx.tcx, def_id, &paths::REGEX_BYTES_SET_NEW) {
126                     check_set(cx, &args[0], false);
127                 }
128             }
129         }
130     }
131 }
132
133 #[allow(cast_possible_truncation)]
134 fn str_span(base: Span, s: &str, c: usize) -> Span {
135     let mut si = s.char_indices().skip(c);
136
137     match (si.next(), si.next()) {
138         (Some((l, _)), Some((h, _))) => {
139             Span::new(base.lo() + BytePos(l as u32), base.lo() + BytePos(h as u32), base.ctxt())
140         },
141         _ => base,
142     }
143 }
144
145 fn const_str<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) -> Option<String> {
146     constant(cx, e).and_then(|(c, _)| match c {
147         Constant::Str(s) => Some(s),
148         _ => None,
149     })
150 }
151
152 fn is_trivial_regex(s: &regex_syntax::Expr) -> Option<&'static str> {
153     use regex_syntax::Expr;
154
155     match *s {
156         Expr::Empty | Expr::StartText | Expr::EndText => Some("the regex is unlikely to be useful as it is"),
157         Expr::Literal { .. } => Some("consider using `str::contains`"),
158         Expr::Concat(ref exprs) => match exprs.len() {
159             2 => match (&exprs[0], &exprs[1]) {
160                 (&Expr::StartText, &Expr::EndText) => Some("consider using `str::is_empty`"),
161                 (&Expr::StartText, &Expr::Literal { .. }) => Some("consider using `str::starts_with`"),
162                 (&Expr::Literal { .. }, &Expr::EndText) => Some("consider using `str::ends_with`"),
163                 _ => None,
164             },
165             3 => if let (&Expr::StartText, &Expr::Literal { .. }, &Expr::EndText) = (&exprs[0], &exprs[1], &exprs[2]) {
166                 Some("consider using `==` on `str`s")
167             } else {
168                 None
169             },
170             _ => None,
171         },
172         _ => None,
173     }
174 }
175
176 fn check_set<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr, utf8: bool) {
177     if_chain! {
178         if let ExprAddrOf(_, ref expr) = expr.node;
179         if let ExprArray(ref exprs) = expr.node;
180         then {
181             for expr in exprs {
182                 check_regex(cx, expr, utf8);
183             }
184         }
185     }
186 }
187
188 fn check_regex<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr, utf8: bool) {
189     let builder = regex_syntax::ExprBuilder::new().unicode(utf8);
190
191     if let ExprLit(ref lit) = expr.node {
192         if let LitKind::Str(ref r, style) = lit.node {
193             let r = &r.as_str();
194             let offset = if let StrStyle::Raw(n) = style { 1 + n } else { 0 };
195             match builder.parse(r) {
196                 Ok(r) => if let Some(repl) = is_trivial_regex(&r) {
197                     span_help_and_lint(
198                         cx,
199                         TRIVIAL_REGEX,
200                         expr.span,
201                         "trivial regex",
202                         &format!("consider using {}", repl),
203                     );
204                 },
205                 Err(e) => {
206                     span_lint(
207                         cx,
208                         INVALID_REGEX,
209                         str_span(expr.span, r, e.position() + offset),
210                         &format!("regex syntax error: {}", e.description()),
211                     );
212                 },
213             }
214         }
215     } else if let Some(r) = const_str(cx, expr) {
216         match builder.parse(&r) {
217             Ok(r) => if let Some(repl) = is_trivial_regex(&r) {
218                 span_help_and_lint(
219                     cx,
220                     TRIVIAL_REGEX,
221                     expr.span,
222                     "trivial regex",
223                     &format!("consider using {}", repl),
224                 );
225             },
226             Err(e) => {
227                 span_lint(
228                     cx,
229                     INVALID_REGEX,
230                     expr.span,
231                     &format!("regex syntax error on position {}: {}", e.position(), e.description()),
232                 );
233             },
234         }
235     }
236 }