5 use rustc
::middle
::const_val
::ConstVal
;
6 use rustc_const_eval
::ConstContext
;
7 use rustc
::ty
::subst
::Substs
;
8 use std
::collections
::HashSet
;
10 use syntax
::ast
::{LitKind, NodeId, StrStyle}
;
11 use syntax
::codemap
::{BytePos, Span}
;
12 use syntax
::symbol
::InternedString
;
13 use utils
::{is_expn_of, match_def_path, match_type, opt_def_id, paths, span_help_and_lint, span_lint}
;
15 /// **What it does:** Checks [regex](https://crates.io/crates/regex) creation
16 /// (with `Regex::new`,`RegexBuilder::new` or `RegexSet::new`) for correct
19 /// **Why is this bad?** This will lead to a runtime panic.
21 /// **Known problems:** None.
30 "invalid regular expressions"
33 /// **What it does:** Checks for trivial [regex](https://crates.io/crates/regex)
34 /// creation (with `Regex::new`, `RegexBuilder::new` or `RegexSet::new`).
36 /// **Why is this bad?** Matching the regex can likely be replaced by `==` or
37 /// `str::starts_with`, `str::ends_with` or `std::contains` or other `str`
40 /// **Known problems:** None.
44 /// Regex::new("^foobar")
49 "trivial regular expressions"
52 /// **What it does:** Checks for usage of `regex!(_)` which (as of now) is
53 /// usually slower than `Regex::new(_)` unless called in a loop (which is a bad
56 /// **Why is this bad?** Performance, at least for now. The macro version is
57 /// likely to catch up long-term, but for now the dynamic version is faster.
59 /// **Known problems:** None.
68 "use of `regex!(_)` instead of `Regex::new(_)`"
71 #[derive(Clone, Default)]
77 impl LintPass
for Pass
{
78 fn get_lints(&self) -> LintArray
{
79 lint_array
!(INVALID_REGEX
, REGEX_MACRO
, TRIVIAL_REGEX
)
83 impl<'a
, 'tcx
> LateLintPass
<'a
, 'tcx
> for Pass
{
84 fn check_crate(&mut self, _
: &LateContext
<'a
, 'tcx
>, _
: &'tcx Crate
) {
88 fn check_block(&mut self, cx
: &LateContext
<'a
, 'tcx
>, block
: &'tcx Block
) {
90 if self.last
.is_none();
91 if let Some(ref expr
) = block
.expr
;
92 if match_type(cx
, cx
.tables
.expr_ty(expr
), &paths
::REGEX
);
93 if let Some(span
) = is_expn_of(expr
.span
, "regex");
95 if !self.spans
.contains(&span
) {
100 Please use `Regex::new(_)`, which is faster for now.");
101 self.spans
.insert(span
);
103 self.last
= Some(block
.id
);
108 fn check_block_post(&mut self, _
: &LateContext
<'a
, 'tcx
>, block
: &'tcx Block
) {
109 if self.last
.map_or(false, |id
| block
.id
== id
) {
114 fn check_expr(&mut self, cx
: &LateContext
<'a
, 'tcx
>, expr
: &'tcx Expr
) {
116 if let ExprCall(ref fun
, ref args
) = expr
.node
;
117 if let ExprPath(ref qpath
) = fun
.node
;
119 if let Some(def_id
) = opt_def_id(cx
.tables
.qpath_def(qpath
, fun
.hir_id
));
121 if match_def_path(cx
.tcx
, def_id
, &paths
::REGEX_NEW
) ||
122 match_def_path(cx
.tcx
, def_id
, &paths
::REGEX_BUILDER_NEW
) {
123 check_regex(cx
, &args
[0], true);
124 } else if match_def_path(cx
.tcx
, def_id
, &paths
::REGEX_BYTES_NEW
) ||
125 match_def_path(cx
.tcx
, def_id
, &paths
::REGEX_BYTES_BUILDER_NEW
) {
126 check_regex(cx
, &args
[0], false);
127 } else if match_def_path(cx
.tcx
, def_id
, &paths
::REGEX_SET_NEW
) {
128 check_set(cx
, &args
[0], true);
129 } else if match_def_path(cx
.tcx
, def_id
, &paths
::REGEX_BYTES_SET_NEW
) {
130 check_set(cx
, &args
[0], false);
137 #[allow(cast_possible_truncation)]
138 fn str_span(base
: Span
, s
: &str, c
: usize) -> Span
{
139 let mut si
= s
.char_indices().skip(c
);
141 match (si
.next(), si
.next()) {
142 (Some((l
, _
)), Some((h
, _
))) => {
143 Span
::new(base
.lo() + BytePos(l
as u32), base
.lo() + BytePos(h
as u32), base
.ctxt())
149 fn const_str
<'a
, 'tcx
>(cx
: &LateContext
<'a
, 'tcx
>, e
: &'tcx Expr
) -> Option
<InternedString
> {
150 let parent_item
= cx
.tcx
.hir
.get_parent(e
.id
);
151 let parent_def_id
= cx
.tcx
.hir
.local_def_id(parent_item
);
152 let substs
= Substs
::identity_for_item(cx
.tcx
, parent_def_id
);
153 match ConstContext
::new(cx
.tcx
, cx
.param_env
.and(substs
), cx
.tables
).eval(e
) {
155 val
: ConstVal
::Str(r
),
162 fn is_trivial_regex(s
: ®ex_syntax
::Expr
) -> Option
<&'
static str> {
163 use regex_syntax
::Expr
;
166 Expr
::Empty
| Expr
::StartText
| Expr
::EndText
=> Some("the regex is unlikely to be useful as it is"),
167 Expr
::Literal { .. }
=> Some("consider using `str::contains`"),
168 Expr
::Concat(ref exprs
) => match exprs
.len() {
169 2 => match (&exprs
[0], &exprs
[1]) {
170 (&Expr
::StartText
, &Expr
::EndText
) => Some("consider using `str::is_empty`"),
171 (&Expr
::StartText
, &Expr
::Literal { .. }
) => Some("consider using `str::starts_with`"),
172 (&Expr
::Literal { .. }
, &Expr
::EndText
) => Some("consider using `str::ends_with`"),
175 3 => if let (&Expr
::StartText
, &Expr
::Literal { .. }
, &Expr
::EndText
) = (&exprs
[0], &exprs
[1], &exprs
[2]) {
176 Some("consider using `==` on `str`s")
186 fn check_set
<'a
, 'tcx
>(cx
: &LateContext
<'a
, 'tcx
>, expr
: &'tcx Expr
, utf8
: bool
) {
188 if let ExprAddrOf(_
, ref expr
) = expr
.node
;
189 if let ExprArray(ref exprs
) = expr
.node
;
192 check_regex(cx
, expr
, utf8
);
198 fn check_regex
<'a
, 'tcx
>(cx
: &LateContext
<'a
, 'tcx
>, expr
: &'tcx Expr
, utf8
: bool
) {
199 let builder
= regex_syntax
::ExprBuilder
::new().unicode(utf8
);
201 if let ExprLit(ref lit
) = expr
.node
{
202 if let LitKind
::Str(ref r
, style
) = lit
.node
{
204 let offset
= if let StrStyle
::Raw(n
) = style { 1 + n }
else { 0 }
;
205 match builder
.parse(r
) {
206 Ok(r
) => if let Some(repl
) = is_trivial_regex(&r
) {
212 &format
!("consider using {}", repl
),
219 str_span(expr
.span
, r
, e
.position() + offset
),
220 &format
!("regex syntax error: {}", e
.description()),
225 } else if let Some(r
) = const_str(cx
, expr
) {
226 match builder
.parse(&r
) {
227 Ok(r
) => if let Some(repl
) = is_trivial_regex(&r
) {
233 &format
!("consider using {}", repl
),
241 &format
!("regex syntax error on position {}: {}", e
.position(), e
.description()),