2016-03-19 16:48:29 +00:00
//! This lint catches both string addition and string addition + assignment
2015-08-11 18:22:20 +00:00
//!
2015-08-05 13:10:45 +00:00
//! Note that since we have two lints where one subsumes the other, we try to
//! disable the subsumed lint unless it has a higher level
use rustc ::lint ::* ;
2016-04-07 15:46:48 +00:00
use rustc ::hir ::* ;
2015-08-16 06:54:43 +00:00
use syntax ::codemap ::Spanned ;
2015-08-21 16:48:36 +00:00
use utils ::STRING_PATH ;
2016-02-24 16:38:57 +00:00
use utils ::SpanlessEq ;
2016-03-14 21:03:30 +00:00
use utils ::{ match_type , span_lint , span_lint_and_then , walk_ptrs_ty , get_parent_expr } ;
2015-08-05 13:10:45 +00:00
2016-02-05 23:41:54 +00:00
/// **What it does:** This lint matches code of the form `x = x + y` (without `let`!).
2015-12-11 00:22:27 +00:00
///
2016-02-18 19:12:33 +00:00
/// **Why is this bad?** It's not really bad, but some people think that the `.push_str(_)` method is more readable.
2015-12-11 00:22:27 +00:00
///
2016-02-18 19:12:33 +00:00
/// **Known problems:** None.
2015-12-11 00:22:27 +00:00
///
/// **Example:**
///
/// ```
/// let mut x = "Hello".to_owned();
/// x = x + ", World";
/// ```
2015-08-05 13:10:45 +00:00
declare_lint! {
pub STRING_ADD_ASSIGN ,
2015-08-12 19:17:21 +00:00
Allow ,
2015-08-13 08:32:35 +00:00
" using `x = x + ..` where x is a `String`; suggests using `push_str()` instead "
2015-08-05 13:10:45 +00:00
}
2016-02-05 23:41:54 +00:00
/// **What it does:** The `string_add` lint matches all instances of `x + _` where `x` is of type `String`, but only if [`string_add_assign`](#string_add_assign) does *not* match.
2015-12-11 00:22:27 +00:00
///
/// **Why is this bad?** It's not bad in and of itself. However, this particular `Add` implementation is asymmetric (the other operand need not be `String`, but `x` does), while addition as mathematically defined is symmetric, also the `String::push_str(_)` function is a perfectly good replacement. Therefore some dislike it and wish not to have it in their code.
///
/// That said, other people think that String addition, having a long tradition in other languages is actually fine, which is why we decided to make this particular lint `allow` by default.
///
/// **Known problems:** None
///
/// **Example:**
///
/// ```
/// let x = "Hello".to_owned();
/// x + ", World"
/// ```
2015-08-12 13:50:56 +00:00
declare_lint! {
2015-08-12 13:57:50 +00:00
pub STRING_ADD ,
Allow ,
2015-08-13 08:32:35 +00:00
" using `x + ..` where x is a `String`; suggests using `push_str()` instead "
2015-08-12 13:50:56 +00:00
}
2016-01-19 18:14:49 +00:00
/// **What it does:** This lint matches the `as_bytes` method called on string
2016-02-05 23:41:54 +00:00
/// literals that contain only ascii characters.
2016-01-19 18:14:49 +00:00
///
/// **Why is this bad?** Byte string literals (e.g. `b"foo"`) can be used instead. They are shorter but less discoverable than `as_bytes()`.
///
/// **Example:**
///
/// ```
/// let bs = "a byte string".as_bytes();
/// ```
declare_lint! {
pub STRING_LIT_AS_BYTES ,
Warn ,
" calling `as_bytes` on a string literal; suggests using a byte string literal instead "
}
2015-08-12 13:50:56 +00:00
#[ derive(Copy, Clone) ]
2015-08-05 13:10:45 +00:00
pub struct StringAdd ;
impl LintPass for StringAdd {
2015-08-12 13:50:56 +00:00
fn get_lints ( & self ) -> LintArray {
2015-08-12 14:42:42 +00:00
lint_array! ( STRING_ADD , STRING_ADD_ASSIGN )
2015-08-12 13:50:56 +00:00
}
2015-09-19 02:53:04 +00:00
}
2015-08-12 13:50:56 +00:00
2015-09-19 02:53:04 +00:00
impl LateLintPass for StringAdd {
fn check_expr ( & mut self , cx : & LateContext , e : & Expr ) {
2015-11-24 17:44:40 +00:00
if let ExprBinary ( Spanned { node : BiAdd , .. } , ref left , _ ) = e . node {
2015-08-12 13:57:50 +00:00
if is_string ( cx , left ) {
if let Allow = cx . current_level ( STRING_ADD_ASSIGN ) {
// the string_add_assign is allow, so no duplicates
} else {
let parent = get_parent_expr ( cx , e ) ;
if let Some ( ref p ) = parent {
2015-11-24 17:44:40 +00:00
if let ExprAssign ( ref target , _ ) = p . node {
2015-08-12 13:57:50 +00:00
// avoid duplicate matches
2016-02-06 19:13:25 +00:00
if SpanlessEq ::new ( cx ) . eq_expr ( target , left ) {
2016-01-04 04:26:12 +00:00
return ;
}
2015-08-12 13:57:50 +00:00
}
}
}
2016-01-04 04:26:12 +00:00
span_lint ( cx ,
STRING_ADD ,
e . span ,
" you added something to a string. Consider using `String::push_str()` instead " ) ;
2015-08-12 13:57:50 +00:00
}
2015-11-24 17:44:40 +00:00
} else if let ExprAssign ( ref target , ref src ) = e . node {
2015-08-21 10:19:07 +00:00
if is_string ( cx , target ) & & is_add ( cx , src , target ) {
2016-01-04 04:26:12 +00:00
span_lint ( cx ,
STRING_ADD_ASSIGN ,
e . span ,
" you assigned the result of adding something to this string. Consider using \
` String ::push_str ( ) ` instead " );
2015-08-05 13:10:45 +00:00
}
}
}
}
2015-09-19 02:53:04 +00:00
fn is_string ( cx : & LateContext , e : & Expr ) -> bool {
2015-08-21 17:00:33 +00:00
match_type ( cx , walk_ptrs_ty ( cx . tcx . expr_ty ( e ) ) , & STRING_PATH )
2015-08-05 13:10:45 +00:00
}
2015-09-19 02:53:04 +00:00
fn is_add ( cx : & LateContext , src : & Expr , target : & Expr ) -> bool {
2015-08-21 18:44:48 +00:00
match src . node {
2016-02-06 19:13:25 +00:00
ExprBinary ( Spanned { node : BiAdd , .. } , ref left , _ ) = > SpanlessEq ::new ( cx ) . eq_expr ( target , left ) ,
2016-01-04 04:26:12 +00:00
ExprBlock ( ref block ) = > {
block . stmts . is_empty ( ) & & block . expr . as_ref ( ) . map_or ( false , | expr | is_add ( cx , expr , target ) )
}
_ = > false ,
2015-08-05 13:10:45 +00:00
}
}
2016-01-19 18:14:49 +00:00
#[ derive(Copy, Clone) ]
pub struct StringLitAsBytes ;
impl LintPass for StringLitAsBytes {
fn get_lints ( & self ) -> LintArray {
lint_array! ( STRING_LIT_AS_BYTES )
}
}
impl LateLintPass for StringLitAsBytes {
fn check_expr ( & mut self , cx : & LateContext , e : & Expr ) {
use std ::ascii ::AsciiExt ;
2016-02-12 17:35:44 +00:00
use syntax ::ast ::LitKind ;
2016-01-19 18:43:29 +00:00
use utils ::{ snippet , in_macro } ;
2016-01-19 18:14:49 +00:00
if let ExprMethodCall ( ref name , _ , ref args ) = e . node {
if name . node . as_str ( ) = = " as_bytes " {
if let ExprLit ( ref lit ) = args [ 0 ] . node {
2016-02-12 17:35:44 +00:00
if let LitKind ::Str ( ref lit_content , _ ) = lit . node {
2016-03-14 21:00:01 +00:00
if lit_content . chars ( ) . all ( | c | c . is_ascii ( ) ) & & ! in_macro ( cx , args [ 0 ] . span ) {
2016-03-14 21:03:30 +00:00
span_lint_and_then ( cx ,
STRING_LIT_AS_BYTES ,
e . span ,
" calling `as_bytes()` on a string literal " ,
| db | {
let sugg = format! ( " b {} " ,
snippet ( cx , args [ 0 ] . span , r # ""foo""# ) ) ;
db . span_suggestion ( e . span ,
" consider using a byte string literal instead " ,
sugg ) ;
} ) ;
2016-01-19 18:14:49 +00:00
}
}
}
}
}
}
}