@@ -7,11 +7,14 @@ use std::borrow::Cow;
77use syntax:: ptr:: P ;
88use syntax:: codemap:: Span ;
99
10- use utils:: { snippet, span_lint, span_note_and_lint, match_path, match_type, method_chain_args, match_trait_method,
11- walk_ptrs_ty_depth, walk_ptrs_ty, get_trait_def_id, implements_trait} ;
1210use utils:: {
13- BTREEMAP_ENTRY_PATH , DEFAULT_TRAIT_PATH , HASHMAP_ENTRY_PATH , OPTION_PATH ,
14- RESULT_PATH , STRING_PATH
11+ get_trait_def_id, implements_trait, in_external_macro, in_macro, match_path,
12+ match_trait_method, match_type, method_chain_args, snippet, span_lint, span_lint_and_then,
13+ span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth,
14+ } ;
15+ use utils:: {
16+ BTREEMAP_ENTRY_PATH , DEFAULT_TRAIT_PATH , HASHMAP_ENTRY_PATH , OPTION_PATH , RESULT_PATH ,
17+ STRING_PATH
1518} ;
1619use utils:: MethodArgs ;
1720use rustc:: middle:: cstore:: CrateStore ;
@@ -176,6 +179,17 @@ declare_lint!(pub SEARCH_IS_SOME, Warn,
176179 "using an iterator search followed by `is_some()`, which is more succinctly \
177180 expressed as a call to `any()`") ;
178181
182+ /// **What it does:** This lint `Warn`s on using `.chars().next()` on a `str` to check if it
183+ /// starts with a given char.
184+ ///
185+ /// **Why is this bad?** Readability, this can be written more concisely as `_.starts_with(_)`.
186+ ///
187+ /// **Known problems:** None.
188+ ///
189+ /// **Example:** `name.chars().next() == Some('_')`
190+ declare_lint ! ( pub CHARS_NEXT_CMP , Warn ,
191+ "using `.chars().next()` to check if a string starts with a char" ) ;
192+
179193/// **What it does:** This lint checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`, etc., and
180194/// suggests to use `or_else`, `unwrap_or_else`, etc., or `unwrap_or_default` instead.
181195///
@@ -210,39 +224,56 @@ impl LintPass for MethodsPass {
210224 OK_EXPECT ,
211225 OPTION_MAP_UNWRAP_OR ,
212226 OPTION_MAP_UNWRAP_OR_ELSE ,
213- OR_FUN_CALL )
227+ OR_FUN_CALL ,
228+ CHARS_NEXT_CMP )
214229 }
215230}
216231
217232impl LateLintPass for MethodsPass {
218233 fn check_expr ( & mut self , cx : & LateContext , expr : & Expr ) {
219- if let ExprMethodCall ( name, _, ref args) = expr. node {
220- // Chain calls
221- if let Some ( arglists) = method_chain_args ( expr, & [ "unwrap" ] ) {
222- lint_unwrap ( cx, expr, arglists[ 0 ] ) ;
223- } else if let Some ( arglists) = method_chain_args ( expr, & [ "to_string" ] ) {
224- lint_to_string ( cx, expr, arglists[ 0 ] ) ;
225- } else if let Some ( arglists) = method_chain_args ( expr, & [ "ok" , "expect" ] ) {
226- lint_ok_expect ( cx, expr, arglists[ 0 ] ) ;
227- } else if let Some ( arglists) = method_chain_args ( expr, & [ "map" , "unwrap_or" ] ) {
228- lint_map_unwrap_or ( cx, expr, arglists[ 0 ] , arglists[ 1 ] ) ;
229- } else if let Some ( arglists) = method_chain_args ( expr, & [ "map" , "unwrap_or_else" ] ) {
230- lint_map_unwrap_or_else ( cx, expr, arglists[ 0 ] , arglists[ 1 ] ) ;
231- } else if let Some ( arglists) = method_chain_args ( expr, & [ "filter" , "next" ] ) {
232- lint_filter_next ( cx, expr, arglists[ 0 ] ) ;
233- } else if let Some ( arglists) = method_chain_args ( expr, & [ "find" , "is_some" ] ) {
234- lint_search_is_some ( cx, expr, "find" , arglists[ 0 ] , arglists[ 1 ] ) ;
235- } else if let Some ( arglists) = method_chain_args ( expr, & [ "position" , "is_some" ] ) {
236- lint_search_is_some ( cx, expr, "position" , arglists[ 0 ] , arglists[ 1 ] ) ;
237- } else if let Some ( arglists) = method_chain_args ( expr, & [ "rposition" , "is_some" ] ) {
238- lint_search_is_some ( cx, expr, "rposition" , arglists[ 0 ] , arglists[ 1 ] ) ;
239- }
234+ if in_macro ( cx, expr. span ) {
235+ return ;
236+ }
237+
238+ match expr. node {
239+ ExprMethodCall ( name, _, ref args) => {
240+ // Chain calls
241+ if let Some ( arglists) = method_chain_args ( expr, & [ "unwrap" ] ) {
242+ lint_unwrap ( cx, expr, arglists[ 0 ] ) ;
243+ } else if let Some ( arglists) = method_chain_args ( expr, & [ "to_string" ] ) {
244+ lint_to_string ( cx, expr, arglists[ 0 ] ) ;
245+ } else if let Some ( arglists) = method_chain_args ( expr, & [ "ok" , "expect" ] ) {
246+ lint_ok_expect ( cx, expr, arglists[ 0 ] ) ;
247+ } else if let Some ( arglists) = method_chain_args ( expr, & [ "map" , "unwrap_or" ] ) {
248+ lint_map_unwrap_or ( cx, expr, arglists[ 0 ] , arglists[ 1 ] ) ;
249+ } else if let Some ( arglists) = method_chain_args ( expr, & [ "map" , "unwrap_or_else" ] ) {
250+ lint_map_unwrap_or_else ( cx, expr, arglists[ 0 ] , arglists[ 1 ] ) ;
251+ } else if let Some ( arglists) = method_chain_args ( expr, & [ "filter" , "next" ] ) {
252+ lint_filter_next ( cx, expr, arglists[ 0 ] ) ;
253+ } else if let Some ( arglists) = method_chain_args ( expr, & [ "find" , "is_some" ] ) {
254+ lint_search_is_some ( cx, expr, "find" , arglists[ 0 ] , arglists[ 1 ] ) ;
255+ } else if let Some ( arglists) = method_chain_args ( expr, & [ "position" , "is_some" ] ) {
256+ lint_search_is_some ( cx, expr, "position" , arglists[ 0 ] , arglists[ 1 ] ) ;
257+ } else if let Some ( arglists) = method_chain_args ( expr, & [ "rposition" , "is_some" ] ) {
258+ lint_search_is_some ( cx, expr, "rposition" , arglists[ 0 ] , arglists[ 1 ] ) ;
259+ }
240260
241- lint_or_fun_call ( cx, expr, & name. node . as_str ( ) , & args) ;
261+ lint_or_fun_call ( cx, expr, & name. node . as_str ( ) , & args) ;
262+ }
263+ ExprBinary ( op, ref lhs, ref rhs) if op. node == BiEq || op. node == BiNe => {
264+ if !lint_chars_next ( cx, expr, lhs, rhs, op. node == BiEq ) {
265+ lint_chars_next ( cx, expr, rhs, lhs, op. node == BiEq ) ;
266+ }
267+ }
268+ _ => ( ) ,
242269 }
243270 }
244271
245272 fn check_item ( & mut self , cx : & LateContext , item : & Item ) {
273+ if in_external_macro ( cx, item. span ) {
274+ return ;
275+ }
276+
246277 if let ItemImpl ( _, _, _, None , ref ty, ref items) = item. node {
247278 for implitem in items {
248279 let name = implitem. name ;
@@ -570,6 +601,41 @@ fn lint_search_is_some(cx: &LateContext, expr: &Expr, search_method: &str, searc
570601 }
571602}
572603
604+ /// Checks for the `CHARS_NEXT_CMP` lint.
605+ fn lint_chars_next ( cx : & LateContext , expr : & Expr , chain : & Expr , other : & Expr , eq : bool ) -> bool {
606+ if_let_chain ! { [
607+ let Some ( args) = method_chain_args( chain, & [ "chars" , "next" ] ) ,
608+ let ExprCall ( ref fun, ref arg_char) = other. node,
609+ arg_char. len( ) == 1 ,
610+ let ExprPath ( None , ref path) = fun. node,
611+ path. segments. len( ) == 1 && path. segments[ 0 ] . identifier. name. as_str( ) == "Some"
612+ ] , {
613+ let self_ty = walk_ptrs_ty( cx. tcx. expr_ty_adjusted( & args[ 0 ] [ 0 ] ) ) ;
614+
615+ if self_ty. sty != ty:: TyStr {
616+ return false ;
617+ }
618+
619+ span_lint_and_then( cx,
620+ CHARS_NEXT_CMP ,
621+ expr. span,
622+ "you should use the `starts_with` method" ,
623+ |db| {
624+ let sugg = format!( "{}{}.starts_with({})" ,
625+ if eq { "" } else { "!" } ,
626+ snippet( cx, args[ 0 ] [ 0 ] . span, "_" ) ,
627+ snippet( cx, arg_char[ 0 ] . span, "_" )
628+ ) ;
629+
630+ db. span_suggestion( expr. span, "like this" , sugg) ;
631+ } ) ;
632+
633+ return true ;
634+ } }
635+
636+ false
637+ }
638+
573639// Given a `Result<T, E>` type, return its error type (`E`)
574640fn get_error_type < ' a > ( cx : & LateContext , ty : ty:: Ty < ' a > ) -> Option < ty:: Ty < ' a > > {
575641 if !match_type ( cx, ty, & RESULT_PATH ) {
0 commit comments