2015-08-05 08:10:45 -05:00
//! This LintPass catches both string addition and string addition + assignment
2015-08-11 13:22:20 -05:00
//!
2015-08-05 08:10:45 -05: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 ::* ;
2015-09-03 09:42:17 -05:00
use rustc_front ::hir ::* ;
2015-08-16 01:54:43 -05:00
use syntax ::codemap ::Spanned ;
2016-02-06 13:13:25 -06:00
use utils ::{ match_type , span_lint , walk_ptrs_ty , get_parent_expr } ;
use utils ::SpanlessEq ;
2015-08-21 11:48:36 -05:00
use utils ::STRING_PATH ;
2015-08-05 08:10:45 -05:00
2016-02-05 17:41:54 -06:00
/// **What it does:** This lint matches code of the form `x = x + y` (without `let`!).
2015-12-10 18:22:27 -06:00
///
/// **Why is this bad?** Because this expression needs another copy as opposed to `x.push_str(y)` (in practice LLVM will usually elide it, though). Despite [llogiq](https://github.com/llogiq)'s reservations, this lint also is `allow` by default, as some people opine that it's more readable.
///
/// **Known problems:** None. Well apart from the lint being `allow` by default. :smile:
///
/// **Example:**
///
/// ```
/// let mut x = "Hello".to_owned();
/// x = x + ", World";
/// ```
2015-08-05 08:10:45 -05:00
declare_lint! {
pub STRING_ADD_ASSIGN ,
2015-08-12 14:17:21 -05:00
Allow ,
2015-08-13 03:32:35 -05:00
" using `x = x + ..` where x is a `String`; suggests using `push_str()` instead "
2015-08-05 08:10:45 -05:00
}
2016-02-05 17:41:54 -06: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-10 18:22:27 -06: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 08:50:56 -05:00
declare_lint! {
2015-08-12 08:57:50 -05:00
pub STRING_ADD ,
Allow ,
2015-08-13 03:32:35 -05:00
" using `x + ..` where x is a `String`; suggests using `push_str()` instead "
2015-08-12 08:50:56 -05:00
}
2016-01-19 12:14:49 -06:00
/// **What it does:** This lint matches the `as_bytes` method called on string
2016-02-05 17:41:54 -06:00
/// literals that contain only ascii characters.
2016-01-19 12:14:49 -06: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 08:50:56 -05:00
#[ derive(Copy, Clone) ]
2015-08-05 08:10:45 -05:00
pub struct StringAdd ;
impl LintPass for StringAdd {
2015-08-12 08:50:56 -05:00
fn get_lints ( & self ) -> LintArray {
2015-08-12 09:42:42 -05:00
lint_array! ( STRING_ADD , STRING_ADD_ASSIGN )
2015-08-12 08:50:56 -05:00
}
2015-09-18 21:53:04 -05:00
}
2015-08-12 08:50:56 -05:00
2015-09-18 21:53:04 -05:00
impl LateLintPass for StringAdd {
fn check_expr ( & mut self , cx : & LateContext , e : & Expr ) {
2015-11-24 11:44:40 -06:00
if let ExprBinary ( Spanned { node : BiAdd , .. } , ref left , _ ) = e . node {
2015-08-12 08:57:50 -05: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 11:44:40 -06:00
if let ExprAssign ( ref target , _ ) = p . node {
2015-08-12 08:57:50 -05:00
// avoid duplicate matches
2016-02-06 13:13:25 -06:00
if SpanlessEq ::new ( cx ) . eq_expr ( target , left ) {
2016-01-03 22:26:12 -06:00
return ;
}
2015-08-12 08:57:50 -05:00
}
}
}
2016-01-03 22:26:12 -06:00
span_lint ( cx ,
STRING_ADD ,
e . span ,
" you added something to a string. Consider using `String::push_str()` instead " ) ;
2015-08-12 08:57:50 -05:00
}
2015-11-24 11:44:40 -06:00
} else if let ExprAssign ( ref target , ref src ) = e . node {
2015-08-21 05:19:07 -05:00
if is_string ( cx , target ) & & is_add ( cx , src , target ) {
2016-01-03 22:26:12 -06: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 08:10:45 -05:00
}
}
}
}
2015-09-18 21:53:04 -05:00
fn is_string ( cx : & LateContext , e : & Expr ) -> bool {
2015-08-21 12:00:33 -05:00
match_type ( cx , walk_ptrs_ty ( cx . tcx . expr_ty ( e ) ) , & STRING_PATH )
2015-08-05 08:10:45 -05:00
}
2015-09-18 21:53:04 -05:00
fn is_add ( cx : & LateContext , src : & Expr , target : & Expr ) -> bool {
2015-08-21 13:44:48 -05:00
match src . node {
2016-02-06 13:13:25 -06:00
ExprBinary ( Spanned { node : BiAdd , .. } , ref left , _ ) = > SpanlessEq ::new ( cx ) . eq_expr ( target , left ) ,
2016-01-03 22:26:12 -06: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 08:10:45 -05:00
}
}
2016-01-19 12:14:49 -06: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 11:35:44 -06:00
use syntax ::ast ::LitKind ;
2016-01-19 12:43:29 -06:00
use utils ::{ snippet , in_macro } ;
2016-01-19 12:14:49 -06: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 11:35:44 -06:00
if let LitKind ::Str ( ref lit_content , _ ) = lit . node {
2016-01-19 12:43:29 -06:00
if lit_content . chars ( ) . all ( | c | c . is_ascii ( ) ) & & ! in_macro ( cx , e . span ) {
2016-01-19 12:14:49 -06:00
let msg = format! ( " calling `as_bytes()` on a string literal. \
Consider using a byte string literal instead : \
` b { } ` " ,
snippet ( cx , args [ 0 ] . span , r # ""foo""# ) ) ;
span_lint ( cx , STRING_LIT_AS_BYTES , e . span , & msg ) ;
}
}
}
}
}
}
}