2016-02-04 23:36:06 +00:00
use regex_syntax ;
use std ::error ::Error ;
2016-02-08 22:48:04 +00:00
use std ::collections ::HashSet ;
2016-02-05 15:48:35 +00:00
use syntax ::ast ::Lit_ ::LitStr ;
use syntax ::codemap ::{ Span , BytePos } ;
use syntax ::parse ::token ::InternedString ;
2016-02-04 23:36:06 +00:00
use rustc_front ::hir ::* ;
2016-02-09 05:18:08 +00:00
use rustc_front ::intravisit ::{ Visitor , walk_block } ;
2016-02-04 23:36:06 +00:00
use rustc ::middle ::const_eval ::{ eval_const_expr_partial , ConstVal } ;
use rustc ::middle ::const_eval ::EvalHint ::ExprTypeChecked ;
use rustc ::lint ::* ;
2016-02-09 05:18:08 +00:00
use utils ::{ is_expn_of , match_path , match_type , REGEX_NEW_PATH , span_lint , span_help_and_lint } ;
2016-02-04 23:36:06 +00:00
2016-02-05 23:41:54 +00:00
/// **What it does:** This lint checks `Regex::new(_)` invocations for correct regex syntax.
2016-02-04 23:36:06 +00:00
///
/// **Why is this bad?** This will lead to a runtime panic.
///
/// **Known problems:** None.
///
/// **Example:** `Regex::new("|")`
declare_lint! {
pub INVALID_REGEX ,
Deny ,
" finds invalid regular expressions in `Regex::new(_)` invocations "
}
2016-02-05 22:10:48 +00:00
/// **What it does:** This lint checks for `Regex::new(_)` invocations with trivial regex.
///
/// **Why is this bad?** This can likely be replaced by `==` or `str::starts_with`,
/// `str::ends_with` or `std::contains` or other `str` methods.
///
/// **Known problems:** None.
///
/// **Example:** `Regex::new("^foobar")`
declare_lint! {
pub TRIVIAL_REGEX ,
Warn ,
" finds trivial regular expressions in `Regex::new(_)` invocations "
}
2016-02-07 21:50:54 +00:00
/// **What it does:** This lint checks for usage of `regex!(_)` which as of now is usually slower than `Regex::new(_)` unless called in a loop (which is a bad idea anyway).
///
/// **Why is this bad?** Performance, at least for now. The macro version is likely to catch up long-term, but for now the dynamic version is faster.
///
/// **Known problems:** None
///
/// **Example:** `regex!("foo|bar")`
declare_lint! {
pub REGEX_MACRO ,
2016-02-08 22:48:04 +00:00
Warn ,
2016-02-07 21:50:54 +00:00
" finds use of `regex!(_)`, suggests `Regex::new(_)` instead "
}
2016-02-04 23:36:06 +00:00
#[ derive(Copy,Clone) ]
pub struct RegexPass ;
impl LintPass for RegexPass {
fn get_lints ( & self ) -> LintArray {
2016-02-07 21:50:54 +00:00
lint_array! ( INVALID_REGEX , REGEX_MACRO , TRIVIAL_REGEX )
2016-02-04 23:36:06 +00:00
}
}
impl LateLintPass for RegexPass {
2016-02-08 22:48:04 +00:00
fn check_crate ( & mut self , cx : & LateContext , krate : & Crate ) {
let mut visitor = RegexVisitor { cx : cx , spans : HashSet ::new ( ) } ;
krate . visit_all_items ( & mut visitor ) ;
2016-02-07 21:50:54 +00:00
}
2016-02-04 23:36:06 +00:00
fn check_expr ( & mut self , cx : & LateContext , expr : & Expr ) {
if_let_chain! { [
let ExprCall ( ref fun , ref args ) = expr . node ,
let ExprPath ( _ , ref path ) = fun . node ,
2016-02-05 15:48:35 +00:00
match_path ( path , & REGEX_NEW_PATH ) & & args . len ( ) = = 1
2016-02-04 23:36:06 +00:00
] , {
2016-02-05 15:48:35 +00:00
if let ExprLit ( ref lit ) = args [ 0 ] . node {
if let LitStr ( ref r , _ ) = lit . node {
2016-02-06 17:06:39 +00:00
match regex_syntax ::Expr ::parse ( r ) {
Ok ( r ) = > {
if let Some ( repl ) = is_trivial_regex ( & r ) {
span_help_and_lint ( cx , TRIVIAL_REGEX , args [ 0 ] . span ,
& " trivial regex " ,
& format! ( " consider using {} " , repl ) ) ;
}
}
Err ( e ) = > {
span_lint ( cx ,
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 ] ) {
match regex_syntax ::Expr ::parse ( & r ) {
Ok ( r ) = > {
if let Some ( repl ) = is_trivial_regex ( & r ) {
span_help_and_lint ( cx , TRIVIAL_REGEX , args [ 0 ] . span ,
& " trivial regex " ,
& format! ( " consider using {} " , repl ) ) ;
}
}
Err ( e ) = > {
2016-02-05 15:48:35 +00:00
span_lint ( cx ,
INVALID_REGEX ,
2016-02-06 17:06:39 +00:00
args [ 0 ] . span ,
& format! ( " regex syntax error on position {} : {} " ,
e . position ( ) ,
2016-02-05 15:48:35 +00:00
e . description ( ) ) ) ;
}
2016-02-05 22:10:48 +00:00
}
2016-02-05 15:48:35 +00:00
}
2016-02-04 23:36:06 +00:00
} }
}
}
2016-02-05 15:48:35 +00:00
#[ allow(cast_possible_truncation) ]
fn str_span ( base : Span , s : & str , c : usize ) -> Span {
let lo = match s . char_indices ( ) . nth ( c ) {
Some ( ( b , _ ) ) = > base . lo + BytePos ( b as u32 ) ,
_ = > base . hi
} ;
Span { lo : lo , hi : lo , .. base }
}
fn const_str ( cx : & LateContext , e : & Expr ) -> Option < InternedString > {
match eval_const_expr_partial ( cx . tcx , e , ExprTypeChecked , None ) {
Ok ( ConstVal ::Str ( r ) ) = > Some ( r ) ,
_ = > None
}
}
2016-02-05 22:10:48 +00:00
2016-02-06 17:06:39 +00:00
fn is_trivial_regex ( s : & regex_syntax ::Expr ) -> Option < & 'static str > {
use regex_syntax ::Expr ;
2016-02-05 22:10:48 +00:00
2016-02-06 17:06:39 +00:00
match * s {
Expr ::Empty | Expr ::StartText | Expr ::EndText = > Some ( " the regex is unlikely to be useful as it is " ) ,
Expr ::Literal { .. } = > Some ( " consider using `str::contains` " ) ,
Expr ::Concat ( ref exprs ) = > {
match exprs . len ( ) {
2 = > match ( & exprs [ 0 ] , & exprs [ 1 ] ) {
( & Expr ::StartText , & Expr ::EndText ) = > Some ( " consider using `str::is_empty` " ) ,
( & Expr ::StartText , & Expr ::Literal { .. } ) = > Some ( " consider using `str::starts_with` " ) ,
( & Expr ::Literal { .. } , & Expr ::EndText ) = > Some ( " consider using `str::ends_with` " ) ,
_ = > 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 ,
2016-02-05 22:10:48 +00:00
}
}
2016-02-07 21:50:54 +00:00
struct RegexVisitor < ' v , ' t : ' v > {
cx : & ' v LateContext < ' v , ' t > ,
2016-02-08 22:48:04 +00:00
spans : HashSet < Span > ,
2016-02-07 21:50:54 +00:00
}
impl < ' v , ' t : ' v > Visitor < ' v > for RegexVisitor < ' v , ' t > {
2016-02-09 05:18:08 +00:00
fn visit_block ( & mut self , block : & ' v Block ) {
if_let_chain! { [
let Some ( ref expr ) = block . expr ,
match_type ( self . cx , self . cx . tcx . expr_ty ( expr ) , & [ " regex " , " re " , " Regex " ] ) ,
let Some ( span ) = is_expn_of ( self . cx , expr . span , " regex " )
] , {
if self . spans . contains ( & span ) {
return ;
}
span_lint ( self . cx ,
REGEX_MACRO ,
span ,
" `regex!(_)` found. \
Please use ` Regex ::new ( _ ) ` , which is faster for now . " );
self . spans . insert ( span ) ;
2016-02-07 21:50:54 +00:00
return ;
2016-02-09 05:18:08 +00:00
} }
walk_block ( self , block ) ;
2016-02-07 21:50:54 +00:00
}
}