--- /dev/null
+# Contributing to rust-clippy
+
+Hello fellow Rustacean! Great to see your interest in compiler internals and lints!
+
+## Getting started
+
+All issues on Clippy are mentored, if you want help with a bug just ask @Manishearth or @llogiq.
+
+Some issues are easier than others. The [E-easy](https://github.com/Manishearth/rust-clippy/labels/E-easy)
+label can be used to find the easy issues. If you want to work on an issue, please leave a comment
+so that we can assign it to you!
+
+Issues marked [T-AST](https://github.com/Manishearth/rust-clippy/labels/T-AST) involve simple
+matching of the syntax tree structure, and are generally easier than
+[T-middle](https://github.com/Manishearth/rust-clippy/labels/T-middle) issues, which involve types
+and resolved paths.
+
+Issues marked [E-medium](https://github.com/Manishearth/rust-clippy/labels/E-medium) are generally
+pretty easy too, though it's recommended you work on an E-easy issue first.
+
+[Llogiq's blog post on lints](https://llogiq.github.io/2015/06/04/workflows.html) is a nice primer
+to lint-writing, though it does get into advanced stuff. Most lints consist of an implementation of
+`LintPass` with one or more of its default methods overridden. See the existing lints for examples
+of this.
+
+T-AST issues will generally need you to match against a predefined syntax structure. To figure out
+how this syntax structure is encoded in the AST, it is recommended to run `rustc -Z ast-json` on an
+example of the structure and compare with the
+[nodes in the AST docs](http://manishearth.github.io/rust-internals-docs/syntax/ast/). Usually
+the lint will end up to be a nested series of matches and ifs,
+[like so](https://github.com/Manishearth/rust-clippy/blob/de5ccdfab68a5e37689f3c950ed1532ba9d652a0/src/misc.rs#L34)
+
+T-middle issues can be more involved and require verifying types. The
+[`middle::ty`](http://manishearth.github.io/rust-internals-docs/rustc/middle/ty) module contains a
+lot of methods that are useful, though one of the most useful would be `expr_ty` (gives the type of
+an AST expression). `match_def_path()` in Clippy's `utils` module can also be useful.
+
+## Contributions
+
+Clippy welcomes contributions from everyone.
+
+Contributions to Clippy should be made in the form of GitHub pull requests. Each pull request will
+be reviewed by a core contributor (someone with permission to land patches) and either landed in the
+main tree or given feedback for changes that would be required.
+
+## Conduct
+
+We follow the [Rust Code of Conduct](http://www.rust-lang.org/conduct.html).
+
+
+<!-- adapted from https://github.com/servo/servo/blob/master/CONTRIBUTING.md -->
[package]
name = "clippy"
-version = "0.0.4"
+version = "0.0.9"
authors = [
"Manish Goregaokar <manishsmail@gmail.com>",
"Andre Bogus <bogusandre@gmail.com>"
[dev-dependencies]
compiletest_rs = "*"
+regex = "*"
+regex_macros = "*"
+lazy_static = "*"
+
+[features]
+
+structured_logging = []
- `len_zero`: Warns on `_.len() == 0` and suggests using `_.is_empty()` (or similar comparisons with `>` or `!=`)
- `len_without_is_empty`: Warns on traits or impls that have a `.len()` but no `.is_empty()` method
- `cmp_owned`: Warns on creating owned instances for comparing with others, e.g. `x == "foo".to_string()`
+ - `inline_always`: Warns on `#[inline(always)]`, because in most cases it is a bad idea
+ - `collapsible_if`: Warns on cases where two nested `if`-expressions can be collapsed into one, e.g. `if x { if y { foo() } }` can be written as `if x && y { foo() }`
+ - `zero_width_space`: Warns on encountering a unicode zero-width space
+ - `string_add_assign`: Warns on `x = x + ..` where `x` is a `String` and suggests using `push_str(..)` instead.
To use, add the following lines to your Cargo.toml:
More to come, please [file an issue](https://github.com/Manishearth/rust-clippy/issues) if you have ideas!
##Usage
+
+Compiler plugins are highly unstable and will only work with a nightly Rust for now. Since stable Rust is backwards compatible, you should be able to compile your stable programs with nightly Rust with clippy plugged in to circumvent this.
+
Add in your `Cargo.toml`:
```toml
[dependencies.clippy]
*`deny` produces error instead of warnings*
+To have cargo compile your crate with clippy without needing `#![plugin(clippy)]`
+in your code, you can use:
+
+```
+cargo rustc -- -L /path/to/clippy_so -Z extra-plugins=clippy
+```
+
##License
Licensed under [MPL](https://www.mozilla.org/MPL/2.0/). If you're having issues with the license, let me know and I'll try to change it to something more permissive.
use syntax::ptr::P;
use syntax::codemap::Span;
use std::f64::consts as f64;
+use utils::span_lint;
declare_lint! {
pub APPROX_CONSTANT,
if let Ok(value) = str.parse::<f64>() {
for &(constant, name) in KNOWN_CONSTS {
if within_epsilon(constant, value) {
- cx.span_lint(APPROX_CONSTANT, span, &format!(
+ span_lint(cx, APPROX_CONSTANT, span, &format!(
"Approximate value of {}::{} found, consider using it directly.", module, &name));
}
}
--- /dev/null
+/// checks for attributes
+
+use rustc::plugin::Registry;
+use rustc::lint::*;
+use syntax::ast::*;
+use syntax::ptr::P;
+use syntax::codemap::{Span, ExpnInfo};
+use syntax::parse::token::InternedString;
+use utils::{in_macro, match_path, span_lint};
+
+declare_lint! { pub INLINE_ALWAYS, Warn,
+ "#[inline(always)] is usually a bad idea."}
+
+
+#[derive(Copy,Clone)]
+pub struct AttrPass;
+
+impl LintPass for AttrPass {
+ fn get_lints(&self) -> LintArray {
+ lint_array!(INLINE_ALWAYS)
+ }
+
+ fn check_item(&mut self, cx: &Context, item: &Item) {
+ if is_relevant_item(item) {
+ cx.sess().codemap().with_expn_info(item.span.expn_id,
+ |info| check_attrs(cx, info, &item.ident, &item.attrs))
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &Context, item: &ImplItem) {
+ if is_relevant_impl(item) {
+ cx.sess().codemap().with_expn_info(item.span.expn_id,
+ |info| check_attrs(cx, info, &item.ident, &item.attrs))
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &Context, item: &TraitItem) {
+ if is_relevant_trait(item) {
+ cx.sess().codemap().with_expn_info(item.span.expn_id,
+ |info| check_attrs(cx, info, &item.ident, &item.attrs))
+ }
+ }
+}
+
+fn is_relevant_item(item: &Item) -> bool {
+ if let &ItemFn(_, _, _, _, _, ref block) = &item.node {
+ is_relevant_block(block)
+ } else { false }
+}
+
+fn is_relevant_impl(item: &ImplItem) -> bool {
+ match item.node {
+ MethodImplItem(_, ref block) => is_relevant_block(block),
+ _ => false
+ }
+}
+
+fn is_relevant_trait(item: &TraitItem) -> bool {
+ match item.node {
+ MethodTraitItem(_, None) => true,
+ MethodTraitItem(_, Some(ref block)) => is_relevant_block(block),
+ _ => false
+ }
+}
+
+fn is_relevant_block(block: &Block) -> bool {
+ for stmt in block.stmts.iter() {
+ match stmt.node {
+ StmtDecl(_, _) => return true,
+ StmtExpr(ref expr, _) | StmtSemi(ref expr, _) => {
+ return is_relevant_expr(expr);
+ }
+ _ => ()
+ }
+ }
+ block.expr.as_ref().map_or(false, |e| is_relevant_expr(&*e))
+}
+
+fn is_relevant_expr(expr: &Expr) -> bool {
+ match expr.node {
+ ExprBlock(ref block) => is_relevant_block(block),
+ ExprRet(Some(ref e)) | ExprParen(ref e) =>
+ is_relevant_expr(&*e),
+ ExprRet(None) | ExprBreak(_) | ExprMac(_) => false,
+ ExprCall(ref path_expr, _) => {
+ if let ExprPath(_, ref path) = path_expr.node {
+ !match_path(path, &["std", "rt", "begin_unwind"])
+ } else { true }
+ }
+ _ => true
+ }
+}
+
+fn check_attrs(cx: &Context, info: Option<&ExpnInfo>, ident: &Ident,
+ attrs: &[Attribute]) {
+ if in_macro(cx, info) { return; }
+
+ for attr in attrs {
+ if let MetaList(ref inline, ref values) = attr.node.value.node {
+ if values.len() != 1 || inline != &"inline" { continue; }
+ if let MetaWord(ref always) = values[0].node {
+ if always != &"always" { continue; }
+ span_lint(cx, INLINE_ALWAYS, attr.span, &format!(
+ "You have declared #[inline(always)] on {}. This \
+ is usually a bad idea. Are you sure?",
+ ident.name.as_str()));
+ }
+ }
+ }
+}
use syntax::ast_util::{is_comparison_binop, binop_to_string};
use syntax::ptr::P;
use syntax::codemap::Span;
+use utils::span_lint;
declare_lint! {
pub BAD_BIT_MASK,
}
}
-fn check_bit_mask(cx: &Context, bit_op: BinOp_, cmp_op: BinOp_, mask_value: u64, cmp_value: u64, span: &Span) {
+fn check_bit_mask(cx: &Context, bit_op: BinOp_, cmp_op: BinOp_,
+ mask_value: u64, cmp_value: u64, span: &Span) {
match cmp_op {
BiEq | BiNe => match bit_op {
BiBitAnd => if mask_value & cmp_value != mask_value {
- cx.span_lint(BAD_BIT_MASK, *span, &format!("incompatible bit mask: _ & {} can never be equal to {}", mask_value,
- cmp_value));
+ if cmp_value != 0 {
+ span_lint(cx, BAD_BIT_MASK, *span, &format!(
+ "incompatible bit mask: _ & {} can never be equal to {}",
+ mask_value, cmp_value));
+ }
} else {
if mask_value == 0 {
- cx.span_lint(BAD_BIT_MASK, *span, &format!("&-masking with zero"));
+ span_lint(cx, BAD_BIT_MASK, *span,
+ &format!("&-masking with zero"));
}
},
BiBitOr => if mask_value | cmp_value != cmp_value {
- cx.span_lint(BAD_BIT_MASK, *span, &format!("incompatible bit mask: _ | {} can never be equal to {}", mask_value,
- cmp_value));
+ span_lint(cx, BAD_BIT_MASK, *span, &format!(
+ "incompatible bit mask: _ | {} can never be equal to {}",
+ mask_value, cmp_value));
},
_ => ()
},
BiLt | BiGe => match bit_op {
BiBitAnd => if mask_value < cmp_value {
- cx.span_lint(BAD_BIT_MASK, *span, &format!(
+ span_lint(cx, BAD_BIT_MASK, *span, &format!(
"incompatible bit mask: _ & {} will always be lower than {}",
mask_value, cmp_value));
} else {
if mask_value == 0 {
- cx.span_lint(BAD_BIT_MASK, *span, &format!("&-masking with zero"));
+ span_lint(cx, BAD_BIT_MASK, *span,
+ &format!("&-masking with zero"));
}
},
BiBitOr => if mask_value >= cmp_value {
- cx.span_lint(BAD_BIT_MASK, *span, &format!(
+ span_lint(cx, BAD_BIT_MASK, *span, &format!(
"incompatible bit mask: _ | {} will never be lower than {}",
mask_value, cmp_value));
} else {
if mask_value < cmp_value {
- cx.span_lint(INEFFECTIVE_BIT_MASK, *span, &format!(
+ span_lint(cx, INEFFECTIVE_BIT_MASK, *span, &format!(
"ineffective bit mask: x | {} compared to {} is the same as x compared directly",
mask_value, cmp_value));
}
},
BiLe | BiGt => match bit_op {
BiBitAnd => if mask_value <= cmp_value {
- cx.span_lint(BAD_BIT_MASK, *span, &format!(
+ span_lint(cx, BAD_BIT_MASK, *span, &format!(
"incompatible bit mask: _ & {} will never be higher than {}",
mask_value, cmp_value));
} else {
if mask_value == 0 {
- cx.span_lint(BAD_BIT_MASK, *span, &format!("&-masking with zero"));
+ span_lint(cx, BAD_BIT_MASK, *span,
+ &format!("&-masking with zero"));
}
},
BiBitOr => if mask_value > cmp_value {
- cx.span_lint(BAD_BIT_MASK, *span, &format!(
+ span_lint(cx, BAD_BIT_MASK, *span, &format!(
"incompatible bit mask: _ | {} will always be higher than {}",
mask_value, cmp_value));
} else {
if mask_value < cmp_value {
- cx.span_lint(INEFFECTIVE_BIT_MASK, *span, &format!(
+ span_lint(cx, INEFFECTIVE_BIT_MASK, *span, &format!(
"ineffective bit mask: x | {} compared to {} is the same as x compared directly",
mask_value, cmp_value));
}
use rustc::middle::def::*;
use syntax::ast::*;
use syntax::ptr::P;
-use syntax::codemap::{Span, Spanned};
+use syntax::codemap::{Span, Spanned, ExpnInfo};
use syntax::print::pprust::expr_to_string;
+use utils::{in_macro, span_lint};
declare_lint! {
pub COLLAPSIBLE_IF,
lint_array!(COLLAPSIBLE_IF)
}
- fn check_expr(&mut self, cx: &Context, e: &Expr) {
- if let ExprIf(ref check, ref then_block, None) = e.node {
- let expr = check_block(then_block);
- let expr = match expr {
- Some(e) => e,
- None => return
- };
- if let ExprIf(ref check_inner, _, None) = expr.node {
- let (check, check_inner) = (check_to_string(check), check_to_string(check_inner));
- cx.span_lint(COLLAPSIBLE_IF, e.span,
- &format!("This if statement can be collapsed. Try: if {} && {}", check, check_inner));
- }
- }
- }
+ fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+ cx.sess().codemap().with_expn_info(expr.span.expn_id,
+ |info| check_expr_expd(cx, expr, info))
+ }
+}
+
+fn check_expr_expd(cx: &Context, e: &Expr, info: Option<&ExpnInfo>) {
+ if in_macro(cx, info) { return; }
+
+ if let ExprIf(ref check, ref then, None) = e.node {
+ if let Some(&Expr{ node: ExprIf(ref check_inner, _, None), ..}) =
+ single_stmt_of_block(then) {
+ span_lint(cx, COLLAPSIBLE_IF, e.span, &format!(
+ "This if statement can be collapsed. Try: if {} && {}\n{:?}",
+ check_to_string(check), check_to_string(check_inner), e));
+ }
+ }
}
fn requires_brackets(e: &Expr) -> bool {
}
}
-fn check_block(b: &Block) -> Option<&P<Expr>> {
- if b.stmts.len() == 1 && b.expr.is_none() {
- let stmt = &b.stmts[0];
- return match stmt.node {
- StmtExpr(ref e, _) => Some(e),
- _ => None
- };
- }
- if let Some(ref e) = b.expr {
- return Some(e);
+fn single_stmt_of_block(block: &Block) -> Option<&Expr> {
+ if block.stmts.len() == 1 && block.expr.is_none() {
+ if let StmtExpr(ref expr, _) = block.stmts[0].node {
+ single_stmt_of_expr(expr)
+ } else { None }
+ } else {
+ if block.stmts.is_empty() {
+ if let Some(ref p) = block.expr { Some(&*p) } else { None }
+ } else { None }
}
- None
+}
+
+fn single_stmt_of_expr(expr: &Expr) -> Option<&Expr> {
+ if let ExprBlock(ref block) = expr.node {
+ single_stmt_of_block(block)
+ } else { Some(expr) }
}
use syntax::ast_util as ast_util;
use syntax::ptr::P;
use syntax::codemap as code;
+use utils::span_lint;
declare_lint! {
pub EQ_OP,
fn check_expr(&mut self, cx: &Context, e: &Expr) {
if let ExprBinary(ref op, ref left, ref right) = e.node {
if is_cmp_or_bit(op) && is_exp_equal(left, right) {
- cx.span_lint(EQ_OP, e.span, &format!(
- "equal expressions as operands to {}",
- ast_util::binop_to_string(op.node)));
+ span_lint(cx, EQ_OP, e.span, &format!(
+ "equal expressions as operands to {}",
+ ast_util::binop_to_string(op.node)));
}
}
}
}
-fn is_exp_equal(left : &Expr, right : &Expr) -> bool {
- match (&left.node, &right.node) {
- (&ExprBinary(ref lop, ref ll, ref lr),
- &ExprBinary(ref rop, ref rl, ref rr)) =>
- lop.node == rop.node &&
- is_exp_equal(ll, rl) && is_exp_equal(lr, rr),
- (&ExprBox(ref lpl, ref lbox), &ExprBox(ref rpl, ref rbox)) =>
- both(lpl, rpl, |l, r| is_exp_equal(l, r)) &&
- is_exp_equal(lbox, rbox),
- (&ExprCall(ref lcallee, ref largs),
- &ExprCall(ref rcallee, ref rargs)) => is_exp_equal(lcallee,
- rcallee) && is_exps_equal(largs, rargs),
- (&ExprCast(ref lc, ref lty), &ExprCast(ref rc, ref rty)) =>
- is_ty_equal(lty, rty) && is_exp_equal(lc, rc),
- (&ExprField(ref lfexp, ref lfident),
- &ExprField(ref rfexp, ref rfident)) =>
- lfident.node == rfident.node && is_exp_equal(lfexp, rfexp),
- (&ExprLit(ref l), &ExprLit(ref r)) => l.node == r.node,
- (&ExprMethodCall(ref lident, ref lcty, ref lmargs),
- &ExprMethodCall(ref rident, ref rcty, ref rmargs)) =>
- lident.node == rident.node && is_tys_equal(lcty, rcty) &&
- is_exps_equal(lmargs, rmargs),
- (&ExprParen(ref lparen), _) => is_exp_equal(lparen, right),
- (_, &ExprParen(ref rparen)) => is_exp_equal(left, rparen),
- (&ExprPath(ref lqself, ref lsubpath),
- &ExprPath(ref rqself, ref rsubpath)) =>
- both(lqself, rqself, |l, r| is_qself_equal(l, r)) &&
- is_path_equal(lsubpath, rsubpath),
- (&ExprTup(ref ltup), &ExprTup(ref rtup)) =>
- is_exps_equal(ltup, rtup),
- (&ExprUnary(lunop, ref l), &ExprUnary(runop, ref r)) =>
- lunop == runop && is_exp_equal(l, r),
- (&ExprVec(ref l), &ExprVec(ref r)) => is_exps_equal(l, r),
- _ => false
- }
+pub fn is_exp_equal(left : &Expr, right : &Expr) -> bool {
+ match (&left.node, &right.node) {
+ (&ExprBinary(ref lop, ref ll, ref lr),
+ &ExprBinary(ref rop, ref rl, ref rr)) =>
+ lop.node == rop.node &&
+ is_exp_equal(ll, rl) && is_exp_equal(lr, rr),
+ (&ExprBox(ref lpl, ref lbox), &ExprBox(ref rpl, ref rbox)) =>
+ both(lpl, rpl, |l, r| is_exp_equal(l, r)) &&
+ is_exp_equal(lbox, rbox),
+ (&ExprCall(ref lcallee, ref largs),
+ &ExprCall(ref rcallee, ref rargs)) => is_exp_equal(lcallee,
+ rcallee) && is_exps_equal(largs, rargs),
+ (&ExprCast(ref lc, ref lty), &ExprCast(ref rc, ref rty)) =>
+ is_ty_equal(lty, rty) && is_exp_equal(lc, rc),
+ (&ExprField(ref lfexp, ref lfident),
+ &ExprField(ref rfexp, ref rfident)) =>
+ lfident.node == rfident.node && is_exp_equal(lfexp, rfexp),
+ (&ExprLit(ref l), &ExprLit(ref r)) => l.node == r.node,
+ (&ExprMethodCall(ref lident, ref lcty, ref lmargs),
+ &ExprMethodCall(ref rident, ref rcty, ref rmargs)) =>
+ lident.node == rident.node && is_tys_equal(lcty, rcty) &&
+ is_exps_equal(lmargs, rmargs),
+ (&ExprParen(ref lparen), _) => is_exp_equal(lparen, right),
+ (_, &ExprParen(ref rparen)) => is_exp_equal(left, rparen),
+ (&ExprPath(ref lqself, ref lsubpath),
+ &ExprPath(ref rqself, ref rsubpath)) =>
+ both(lqself, rqself, |l, r| is_qself_equal(l, r)) &&
+ is_path_equal(lsubpath, rsubpath),
+ (&ExprTup(ref ltup), &ExprTup(ref rtup)) =>
+ is_exps_equal(ltup, rtup),
+ (&ExprUnary(lunop, ref l), &ExprUnary(runop, ref r)) =>
+ lunop == runop && is_exp_equal(l, r),
+ (&ExprVec(ref l), &ExprVec(ref r)) => is_exps_equal(l, r),
+ _ => false
+ }
}
fn is_exps_equal(left : &[P<Expr>], right : &[P<Expr>]) -> bool {
- over(left, right, |l, r| is_exp_equal(l, r))
+ over(left, right, |l, r| is_exp_equal(l, r))
}
fn is_path_equal(left : &Path, right : &Path) -> bool {
- left.global == right.global && left.segments == right.segments
+ // The == of idents doesn't work with different contexts,
+ // we have to be explicit about hygiene
+ left.global == right.global && over(&left.segments, &right.segments,
+ |l, r| l.identifier.name == r.identifier.name
+ && l.identifier.ctxt == r.identifier.ctxt
+ && l.parameters == r.parameters)
}
fn is_qself_equal(left : &QSelf, right : &QSelf) -> bool {
- left.ty.node == right.ty.node && left.position == right.position
+ left.ty.node == right.ty.node && left.position == right.position
}
fn is_ty_equal(left : &Ty, right : &Ty) -> bool {
- match (&left.node, &right.node) {
- (&TyVec(ref lvec), &TyVec(ref rvec)) => is_ty_equal(lvec, rvec),
- (&TyFixedLengthVec(ref lfvty, ref lfvexp),
- &TyFixedLengthVec(ref rfvty, ref rfvexp)) =>
- is_ty_equal(lfvty, rfvty) && is_exp_equal(lfvexp, rfvexp),
- (&TyPtr(ref lmut), &TyPtr(ref rmut)) => is_mut_ty_equal(lmut, rmut),
- (&TyRptr(ref ltime, ref lrmut), &TyRptr(ref rtime, ref rrmut)) =>
- both(ltime, rtime, is_lifetime_equal) &&
- is_mut_ty_equal(lrmut, rrmut),
- (&TyBareFn(ref lbare), &TyBareFn(ref rbare)) =>
- is_bare_fn_ty_equal(lbare, rbare),
+ match (&left.node, &right.node) {
+ (&TyVec(ref lvec), &TyVec(ref rvec)) => is_ty_equal(lvec, rvec),
+ (&TyFixedLengthVec(ref lfvty, ref lfvexp),
+ &TyFixedLengthVec(ref rfvty, ref rfvexp)) =>
+ is_ty_equal(lfvty, rfvty) && is_exp_equal(lfvexp, rfvexp),
+ (&TyPtr(ref lmut), &TyPtr(ref rmut)) => is_mut_ty_equal(lmut, rmut),
+ (&TyRptr(ref ltime, ref lrmut), &TyRptr(ref rtime, ref rrmut)) =>
+ both(ltime, rtime, is_lifetime_equal) &&
+ is_mut_ty_equal(lrmut, rrmut),
+ (&TyBareFn(ref lbare), &TyBareFn(ref rbare)) =>
+ is_bare_fn_ty_equal(lbare, rbare),
(&TyTup(ref ltup), &TyTup(ref rtup)) => is_tys_equal(ltup, rtup),
- (&TyPath(ref lq, ref lpath), &TyPath(ref rq, ref rpath)) =>
- both(lq, rq, is_qself_equal) && is_path_equal(lpath, rpath),
+ (&TyPath(ref lq, ref lpath), &TyPath(ref rq, ref rpath)) =>
+ both(lq, rq, is_qself_equal) && is_path_equal(lpath, rpath),
(&TyObjectSum(ref lsumty, ref lobounds),
- &TyObjectSum(ref rsumty, ref robounds)) =>
- is_ty_equal(lsumty, rsumty) &&
- is_param_bounds_equal(lobounds, robounds),
- (&TyPolyTraitRef(ref ltbounds), &TyPolyTraitRef(ref rtbounds)) =>
- is_param_bounds_equal(ltbounds, rtbounds),
+ &TyObjectSum(ref rsumty, ref robounds)) =>
+ is_ty_equal(lsumty, rsumty) &&
+ is_param_bounds_equal(lobounds, robounds),
+ (&TyPolyTraitRef(ref ltbounds), &TyPolyTraitRef(ref rtbounds)) =>
+ is_param_bounds_equal(ltbounds, rtbounds),
(&TyParen(ref lty), &TyParen(ref rty)) => is_ty_equal(lty, rty),
(&TyTypeof(ref lof), &TyTypeof(ref rof)) => is_exp_equal(lof, rof),
- (&TyInfer, &TyInfer) => true,
- _ => false
- }
+ (&TyInfer, &TyInfer) => true,
+ _ => false
+ }
}
fn is_param_bound_equal(left : &TyParamBound, right : &TyParamBound)
- -> bool {
- match(left, right) {
- (&TraitTyParamBound(ref lpoly, ref lmod),
- &TraitTyParamBound(ref rpoly, ref rmod)) =>
- lmod == rmod && is_poly_traitref_equal(lpoly, rpoly),
+ -> bool {
+ match(left, right) {
+ (&TraitTyParamBound(ref lpoly, ref lmod),
+ &TraitTyParamBound(ref rpoly, ref rmod)) =>
+ lmod == rmod && is_poly_traitref_equal(lpoly, rpoly),
(&RegionTyParamBound(ref ltime), &RegionTyParamBound(ref rtime)) =>
- is_lifetime_equal(ltime, rtime),
+ is_lifetime_equal(ltime, rtime),
_ => false
- }
+ }
}
fn is_poly_traitref_equal(left : &PolyTraitRef, right : &PolyTraitRef)
- -> bool {
- is_lifetimedefs_equal(&left.bound_lifetimes, &right.bound_lifetimes)
- && is_path_equal(&left.trait_ref.path, &right.trait_ref.path)
+ -> bool {
+ is_lifetimedefs_equal(&left.bound_lifetimes, &right.bound_lifetimes)
+ && is_path_equal(&left.trait_ref.path, &right.trait_ref.path)
}
fn is_param_bounds_equal(left : &TyParamBounds, right : &TyParamBounds)
- -> bool {
- over(left, right, is_param_bound_equal)
+ -> bool {
+ over(left, right, is_param_bound_equal)
}
fn is_mut_ty_equal(left : &MutTy, right : &MutTy) -> bool {
- left.mutbl == right.mutbl && is_ty_equal(&left.ty, &right.ty)
+ left.mutbl == right.mutbl && is_ty_equal(&left.ty, &right.ty)
}
fn is_bare_fn_ty_equal(left : &BareFnTy, right : &BareFnTy) -> bool {
- left.unsafety == right.unsafety && left.abi == right.abi &&
- is_lifetimedefs_equal(&left.lifetimes, &right.lifetimes) &&
- is_fndecl_equal(&left.decl, &right.decl)
+ left.unsafety == right.unsafety && left.abi == right.abi &&
+ is_lifetimedefs_equal(&left.lifetimes, &right.lifetimes) &&
+ is_fndecl_equal(&left.decl, &right.decl)
}
fn is_fndecl_equal(left : &P<FnDecl>, right : &P<FnDecl>) -> bool {
- left.variadic == right.variadic &&
- is_args_equal(&left.inputs, &right.inputs) &&
- is_fnret_ty_equal(&left.output, &right.output)
+ left.variadic == right.variadic &&
+ is_args_equal(&left.inputs, &right.inputs) &&
+ is_fnret_ty_equal(&left.output, &right.output)
}
fn is_fnret_ty_equal(left : &FunctionRetTy, right : &FunctionRetTy)
- -> bool {
- match (left, right) {
- (&NoReturn(_), &NoReturn(_)) |
- (&DefaultReturn(_), &DefaultReturn(_)) => true,
- (&Return(ref lty), &Return(ref rty)) => is_ty_equal(lty, rty),
- _ => false
- }
+ -> bool {
+ match (left, right) {
+ (&NoReturn(_), &NoReturn(_)) |
+ (&DefaultReturn(_), &DefaultReturn(_)) => true,
+ (&Return(ref lty), &Return(ref rty)) => is_ty_equal(lty, rty),
+ _ => false
+ }
}
fn is_arg_equal(l: &Arg, r : &Arg) -> bool {
- is_ty_equal(&l.ty, &r.ty) && is_pat_equal(&l.pat, &r.pat)
+ is_ty_equal(&l.ty, &r.ty) && is_pat_equal(&l.pat, &r.pat)
}
fn is_args_equal(left : &[Arg], right : &[Arg]) -> bool {
- over(left, right, is_arg_equal)
+ over(left, right, is_arg_equal)
}
fn is_pat_equal(left : &Pat, right : &Pat) -> bool {
- match(&left.node, &right.node) {
- (&PatWild(lwild), &PatWild(rwild)) => lwild == rwild,
- (&PatIdent(ref lmode, ref lident, Option::None),
- &PatIdent(ref rmode, ref rident, Option::None)) =>
- lmode == rmode && is_ident_equal(&lident.node, &rident.node),
- (&PatIdent(ref lmode, ref lident, Option::Some(ref lpat)),
- &PatIdent(ref rmode, ref rident, Option::Some(ref rpat))) =>
- lmode == rmode && is_ident_equal(&lident.node, &rident.node) &&
- is_pat_equal(lpat, rpat),
+ match(&left.node, &right.node) {
+ (&PatWild(lwild), &PatWild(rwild)) => lwild == rwild,
+ (&PatIdent(ref lmode, ref lident, Option::None),
+ &PatIdent(ref rmode, ref rident, Option::None)) =>
+ lmode == rmode && is_ident_equal(&lident.node, &rident.node),
+ (&PatIdent(ref lmode, ref lident, Option::Some(ref lpat)),
+ &PatIdent(ref rmode, ref rident, Option::Some(ref rpat))) =>
+ lmode == rmode && is_ident_equal(&lident.node, &rident.node) &&
+ is_pat_equal(lpat, rpat),
(&PatEnum(ref lpath, ref lenum), &PatEnum(ref rpath, ref renum)) =>
- is_path_equal(lpath, rpath) && both(lenum, renum, |l, r|
- is_pats_equal(l, r)),
+ is_path_equal(lpath, rpath) && both(lenum, renum, |l, r|
+ is_pats_equal(l, r)),
(&PatStruct(ref lpath, ref lfieldpat, lbool),
- &PatStruct(ref rpath, ref rfieldpat, rbool)) =>
- lbool == rbool && is_path_equal(lpath, rpath) &&
- is_spanned_fieldpats_equal(lfieldpat, rfieldpat),
+ &PatStruct(ref rpath, ref rfieldpat, rbool)) =>
+ lbool == rbool && is_path_equal(lpath, rpath) &&
+ is_spanned_fieldpats_equal(lfieldpat, rfieldpat),
(&PatTup(ref ltup), &PatTup(ref rtup)) => is_pats_equal(ltup, rtup),
(&PatBox(ref lboxed), &PatBox(ref rboxed)) =>
- is_pat_equal(lboxed, rboxed),
+ is_pat_equal(lboxed, rboxed),
(&PatRegion(ref lpat, ref lmut), &PatRegion(ref rpat, ref rmut)) =>
- is_pat_equal(lpat, rpat) && lmut == rmut,
- (&PatLit(ref llit), &PatLit(ref rlit)) => is_exp_equal(llit, rlit),
+ is_pat_equal(lpat, rpat) && lmut == rmut,
+ (&PatLit(ref llit), &PatLit(ref rlit)) => is_exp_equal(llit, rlit),
(&PatRange(ref lfrom, ref lto), &PatRange(ref rfrom, ref rto)) =>
- is_exp_equal(lfrom, rfrom) && is_exp_equal(lto, rto),
+ is_exp_equal(lfrom, rfrom) && is_exp_equal(lto, rto),
(&PatVec(ref lfirst, Option::None, ref llast),
- &PatVec(ref rfirst, Option::None, ref rlast)) =>
- is_pats_equal(lfirst, rfirst) && is_pats_equal(llast, rlast),
+ &PatVec(ref rfirst, Option::None, ref rlast)) =>
+ is_pats_equal(lfirst, rfirst) && is_pats_equal(llast, rlast),
(&PatVec(ref lfirst, Option::Some(ref lpat), ref llast),
- &PatVec(ref rfirst, Option::Some(ref rpat), ref rlast)) =>
- is_pats_equal(lfirst, rfirst) && is_pat_equal(lpat, rpat) &&
- is_pats_equal(llast, rlast),
- // I don't match macros for now, the code is slow enough as is ;-)
- _ => false
- }
+ &PatVec(ref rfirst, Option::Some(ref rpat), ref rlast)) =>
+ is_pats_equal(lfirst, rfirst) && is_pat_equal(lpat, rpat) &&
+ is_pats_equal(llast, rlast),
+ // I don't match macros for now, the code is slow enough as is ;-)
+ _ => false
+ }
}
fn is_spanned_fieldpats_equal(left : &[code::Spanned<FieldPat>],
- right : &[code::Spanned<FieldPat>]) -> bool {
- over(left, right, |l, r| is_fieldpat_equal(&l.node, &r.node))
+ right : &[code::Spanned<FieldPat>]) -> bool {
+ over(left, right, |l, r| is_fieldpat_equal(&l.node, &r.node))
}
fn is_fieldpat_equal(left : &FieldPat, right : &FieldPat) -> bool {
- left.is_shorthand == right.is_shorthand &&
- is_ident_equal(&left.ident, &right.ident) &&
- is_pat_equal(&left.pat, &right.pat)
+ left.is_shorthand == right.is_shorthand &&
+ is_ident_equal(&left.ident, &right.ident) &&
+ is_pat_equal(&left.pat, &right.pat)
}
fn is_ident_equal(left : &Ident, right : &Ident) -> bool {
- &left.name == &right.name && left.ctxt == right.ctxt
+ &left.name == &right.name && left.ctxt == right.ctxt
}
fn is_pats_equal(left : &[P<Pat>], right : &[P<Pat>]) -> bool {
- over(left, right, |l, r| is_pat_equal(l, r))
+ over(left, right, |l, r| is_pat_equal(l, r))
}
fn is_lifetimedef_equal(left : &LifetimeDef, right : &LifetimeDef)
- -> bool {
- is_lifetime_equal(&left.lifetime, &right.lifetime) &&
- over(&left.bounds, &right.bounds, is_lifetime_equal)
+ -> bool {
+ is_lifetime_equal(&left.lifetime, &right.lifetime) &&
+ over(&left.bounds, &right.bounds, is_lifetime_equal)
}
fn is_lifetimedefs_equal(left : &[LifetimeDef], right : &[LifetimeDef])
- -> bool {
- over(left, right, is_lifetimedef_equal)
+ -> bool {
+ over(left, right, is_lifetimedef_equal)
}
fn is_lifetime_equal(left : &Lifetime, right : &Lifetime) -> bool {
- left.name == right.name
+ left.name == right.name
}
fn is_tys_equal(left : &[P<Ty>], right : &[P<Ty>]) -> bool {
- over(left, right, |l, r| is_ty_equal(l, r))
+ over(left, right, |l, r| is_ty_equal(l, r))
}
fn over<X, F>(left: &[X], right: &[X], mut eq_fn: F) -> bool
- where F: FnMut(&X, &X) -> bool {
+ where F: FnMut(&X, &X) -> bool {
left.len() == right.len() && left.iter().zip(right).all(|(x, y)|
- eq_fn(x, y))
+ eq_fn(x, y))
}
fn both<X, F>(l: &Option<X>, r: &Option<X>, mut eq_fn : F) -> bool
- where F: FnMut(&X, &X) -> bool {
- l.as_ref().map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false,
- |y| eq_fn(x, y)))
+ where F: FnMut(&X, &X) -> bool {
+ l.as_ref().map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false,
+ |y| eq_fn(x, y)))
}
fn is_cmp_or_bit(op : &BinOp) -> bool {
use syntax::codemap::{Span, Spanned};
use syntax::print::pprust::expr_to_string;
+use utils::span_lint;
+
#[allow(missing_copy_implementations)]
pub struct EtaPass;
fn check_expr(&mut self, cx: &Context, expr: &Expr) {
if let ExprClosure(_, ref decl, ref blk) = expr.node {
- if blk.stmts.len() != 0 {
+ if !blk.stmts.is_empty() {
// || {foo(); bar()}; can't be reduced here
return;
}
return
}
}
- cx.span_lint(REDUNDANT_CLOSURE, expr.span,
+ span_lint(cx, REDUNDANT_CLOSURE, expr.span,
&format!("Redundant closure found, consider using `{}` in its place",
expr_to_string(caller))[..])
}
use syntax::ptr::P;
use syntax::codemap::Span;
+use utils::{span_lint, snippet};
+
declare_lint! { pub IDENTITY_OP, Warn,
"Warn on identity operations, e.g. '_ + 0'"}
fn check(cx: &Context, e: &Expr, m: i8, span: Span, arg: Span) {
if have_lit(cx, e, m) {
- let map = cx.sess().codemap();
- cx.span_lint(IDENTITY_OP, span, &format!(
+ span_lint(cx, IDENTITY_OP, span, &format!(
"The operation is ineffective. Consider reducing it to '{}'",
- &*map.span_to_snippet(arg).unwrap_or("..".to_string())));
+ snippet(cx, arg, "..")));
}
}
extern crate rustc_typeck as typeck;
+use std::rc::Rc;
+use std::cell::RefCell;
use syntax::ptr::P;
-use syntax::ast::*;
use rustc::lint::{Context, LintPass, LintArray, Lint};
-use rustc::middle::ty::{self, node_id_to_type, sty, ty_ptr, ty_rptr, mt, MethodTraitItemId};
+use rustc::util::nodemap::DefIdMap;
+use rustc::middle::ty::{self, TypeVariants, TypeAndMut, MethodTraitItemId, ImplOrTraitItemId};
use rustc::middle::def::{DefTy, DefStruct, DefTrait};
use syntax::codemap::{Span, Spanned};
+use syntax::ast::*;
+use misc::walk_ty;
+use utils::span_lint;
declare_lint!(pub LEN_ZERO, Warn,
"Warn when .is_empty() could be used instead of checking .len()");
fn check_trait_items(cx: &Context, item: &Item, trait_items: &[P<TraitItem>]) {
fn is_named_self(item: &TraitItem, name: &str) -> bool {
- item.ident.as_str() == name && item.attrs.len() == 0
+ item.ident.name == name && if let MethodTraitItem(ref sig, _) =
+ item.node { is_self_sig(sig) } else { false }
}
if !trait_items.iter().any(|i| is_named_self(i, "is_empty")) {
- //cx.span_lint(LEN_WITHOUT_IS_EMPTY, item.span, &format!("trait {}", item.ident.as_str()));
+ //span_lint(cx, LEN_WITHOUT_IS_EMPTY, item.span, &format!("trait {}", item.ident.as_str()));
for i in trait_items {
if is_named_self(i, "len") {
- cx.span_lint(LEN_WITHOUT_IS_EMPTY, i.span,
- &format!("Trait '{}' has a '.len()' method, but no \
- '.is_empty()' method. Consider adding one.",
- item.ident.as_str()));
+ span_lint(cx, LEN_WITHOUT_IS_EMPTY, i.span,
+ &format!("Trait '{}' has a '.len(_: &Self)' method, but no \
+ '.is_empty(_: &Self)' method. Consider adding one.",
+ item.ident.name));
}
};
}
fn check_impl_items(cx: &Context, item: &Item, impl_items: &[P<ImplItem>]) {
fn is_named_self(item: &ImplItem, name: &str) -> bool {
- item.ident.as_str() == name && item.attrs.len() == 0
+ item.ident.name == name && if let MethodImplItem(ref sig, _) =
+ item.node { is_self_sig(sig) } else { false }
}
if !impl_items.iter().any(|i| is_named_self(i, "is_empty")) {
for i in impl_items {
if is_named_self(i, "len") {
- cx.span_lint(LEN_WITHOUT_IS_EMPTY, i.span,
- &format!("Item '{}' has a '.len()' method, but no \
- '.is_empty()' method. Consider adding one.",
- item.ident.as_str()));
+ let s = i.span;
+ span_lint(cx, LEN_WITHOUT_IS_EMPTY,
+ Span{ lo: s.lo, hi: s.lo, expn_id: s.expn_id },
+ &format!("Item '{}' has a '.len(_: &Self)' method, but no \
+ '.is_empty(_: &Self)' method. Consider adding one.",
+ item.ident.name));
return;
}
}
}
}
+fn is_self_sig(sig: &MethodSig) -> bool {
+ if let SelfStatic = sig.explicit_self.node {
+ false } else { sig.decl.inputs.len() == 1 }
+}
+
fn check_cmp(cx: &Context, span: Span, left: &Expr, right: &Expr, empty: &str) {
match (&left.node, &right.node) {
(&ExprLit(ref lit), &ExprMethodCall(ref method, _, ref args)) =>
fn check_len_zero(cx: &Context, span: Span, method: &SpannedIdent,
args: &[P<Expr>], lit: &Lit, empty: &str) {
if let &Spanned{node: LitInt(0, _), ..} = lit {
- if method.node.as_str() == "len" && args.len() == 1 {
- cx.span_lint(LEN_ZERO, span, &format!(
- "Consider replacing the len comparison with '{}_.is_empty()' if available",
+ if method.node.name == "len" && args.len() == 1 &&
+ has_is_empty(cx, &*args[0]) {
+ span_lint(cx, LEN_ZERO, span, &format!(
+ "Consider replacing the len comparison with '{}_.is_empty()'",
empty))
}
}
}
+
+/// check if this type has an is_empty method
+fn has_is_empty(cx: &Context, expr: &Expr) -> bool {
+ /// get a ImplOrTraitItem and return true if it matches is_empty(self)
+ fn is_is_empty(cx: &Context, id: &ImplOrTraitItemId) -> bool {
+ if let &MethodTraitItemId(def_id) = id {
+ if let ty::MethodTraitItem(ref method) =
+ cx.tcx.impl_or_trait_item(def_id) {
+ method.name.as_str() == "is_empty"
+ && method.fty.sig.skip_binder().inputs.len() == 1
+ } else { false }
+ } else { false }
+ }
+
+ /// check the inherent impl's items for an is_empty(self) method
+ fn has_is_empty_impl(cx: &Context, id: &DefId) -> bool {
+ let impl_items = cx.tcx.impl_items.borrow();
+ cx.tcx.inherent_impls.borrow().get(id).map_or(false,
+ |ids| ids.iter().any(|iid| impl_items.get(iid).map_or(false,
+ |iids| iids.iter().any(|i| is_is_empty(cx, i)))))
+ }
+
+ let ty = &walk_ty(&cx.tcx.expr_ty(expr));
+ match ty.sty {
+ ty::TyTrait(_) => cx.tcx.trait_item_def_ids.borrow().get(
+ &ty.ty_to_def_id().expect("trait impl not found")).map_or(false,
+ |ids| ids.iter().any(|i| is_is_empty(cx, i))),
+ ty::TyProjection(_) => ty.ty_to_def_id().map_or(false,
+ |id| has_is_empty_impl(cx, &id)),
+ ty::TyEnum(ref id, _) | ty::TyStruct(ref id, _) =>
+ has_is_empty_impl(cx, &id.did),
+ ty::TyArray(..) => true,
+ _ => false,
+ }
+}
#![feature(plugin_registrar, box_syntax)]
#![feature(rustc_private, collections)]
-
#![allow(unused_imports)]
#[macro_use]
pub mod identity_op;
pub mod mut_mut;
pub mod len_zero;
+pub mod attrs;
pub mod collapsible_if;
+pub mod unicode;
+pub mod utils;
+pub mod strings;
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
reg.register_lint_pass(box mut_mut::MutMut as LintPassObject);
reg.register_lint_pass(box len_zero::LenZero as LintPassObject);
reg.register_lint_pass(box misc::CmpOwned as LintPassObject);
+ reg.register_lint_pass(box attrs::AttrPass as LintPassObject);
reg.register_lint_pass(box collapsible_if::CollapsibleIf as LintPassObject);
reg.register_lint_pass(box misc::ModuloOne as LintPassObject);
-
+ reg.register_lint_pass(box unicode::Unicode as LintPassObject);
+ reg.register_lint_pass(box strings::StringAdd as LintPassObject);
+ reg.register_lint_pass(box misc::NeedlessReturn as LintPassObject);
+
reg.register_lint_group("clippy", vec![types::BOX_VEC, types::LINKEDLIST,
misc::SINGLE_MATCH, misc::STR_TO_STRING,
misc::TOPLEVEL_REF_ARG, eq_op::EQ_OP,
- bit_mask::BAD_BIT_MASK,
+ bit_mask::BAD_BIT_MASK,
bit_mask::INEFFECTIVE_BIT_MASK,
ptr_arg::PTR_ARG,
needless_bool::NEEDLESS_BOOL,
mut_mut::MUT_MUT,
len_zero::LEN_ZERO,
len_zero::LEN_WITHOUT_IS_EMPTY,
+ attrs::INLINE_ALWAYS,
collapsible_if::COLLAPSIBLE_IF,
+ unicode::ZERO_WIDTH_SPACE,
+ strings::STRING_ADD_ASSIGN,
+ misc::NEEDLESS_RETURN,
misc::MODULO_ONE,
]);
}
use syntax::ast_util::{is_comparison_binop, binop_to_string};
use syntax::visit::{FnKind};
use rustc::lint::{Context, LintPass, LintArray, Lint, Level};
-use rustc::middle::ty::{self, expr_ty, ty_str, ty_ptr, ty_rptr, ty_float};
+use rustc::middle::ty;
use syntax::codemap::{Span, Spanned};
+use utils::{match_path, snippet, span_lint, span_help_and_lint};
-use types::span_note_and_lint;
-
-fn walk_ty<'t>(ty: ty::Ty<'t>) -> ty::Ty<'t> {
- match ty.sty {
- ty_ptr(ref tm) | ty_rptr(_, ref tm) => walk_ty(tm.ty),
- _ => ty
- }
+pub fn walk_ty<'t>(ty: ty::Ty<'t>) -> ty::Ty<'t> {
+ match ty.sty {
+ ty::TyRef(_, ref tm) | ty::TyRawPtr(ref tm) => walk_ty(tm.ty),
+ _ => ty
+ }
}
/// Handles uncategorized lints
if arms.len() == 2 {
if arms[0].guard.is_none() && arms[1].pats.len() == 1 {
match arms[1].body.node {
- ExprTup(ref v) if v.len() == 0 && arms[1].guard.is_none() => (),
- ExprBlock(ref b) if b.stmts.len() == 0 && arms[1].guard.is_none() => (),
+ ExprTup(ref v) if v.is_empty() && arms[1].guard.is_none() => (),
+ ExprBlock(ref b) if b.stmts.is_empty() && arms[1].guard.is_none() => (),
_ => return
}
// In some cases, an exhaustive match is preferred to catch situations when
// an enum is extended. So we only consider cases where a `_` wildcard is used
- if arms[1].pats[0].node == PatWild(PatWildSingle) && arms[0].pats.len() == 1 {
- let map = cx.sess().codemap();
- span_note_and_lint(cx, SINGLE_MATCH, expr.span,
- "You seem to be trying to use match for destructuring a single type. Did you mean to use `if let`?",
+ if arms[1].pats[0].node == PatWild(PatWildSingle) &&
+ arms[0].pats.len() == 1 {
+ span_help_and_lint(cx, SINGLE_MATCH, expr.span,
+ "You seem to be trying to use match for \
+ destructuring a single type. Did you mean to \
+ use `if let`?",
&*format!("Try if let {} = {} {{ ... }}",
- &*map.span_to_snippet(arms[0].pats[0].span).unwrap_or("..".to_string()),
- &*map.span_to_snippet(ex.span).unwrap_or("..".to_string()))
+ snippet(cx, arms[0].pats[0].span, ".."),
+ snippet(cx, ex.span, ".."))
);
}
}
fn check_expr(&mut self, cx: &Context, expr: &ast::Expr) {
match expr.node {
ast::ExprMethodCall(ref method, _, ref args)
- if method.node.as_str() == "to_string"
+ if method.node.name == "to_string"
&& is_str(cx, &*args[0]) => {
- cx.span_lint(STR_TO_STRING, expr.span, "str.to_owned() is faster");
+ span_lint(cx, STR_TO_STRING, expr.span, "str.to_owned() is faster");
},
_ => ()
}
fn is_str(cx: &Context, expr: &ast::Expr) -> bool {
- match walk_ty(expr_ty(cx.tcx, expr)).sty {
- ty_str => true,
+ match walk_ty(cx.tcx.expr_ty(expr)).sty {
+ ty::TyStr => true,
_ => false
}
}
fn check_fn(&mut self, cx: &Context, _: FnKind, decl: &FnDecl, _: &Block, _: Span, _: NodeId) {
for ref arg in decl.inputs.iter() {
if let PatIdent(BindByRef(_), _, _) = arg.pat.node {
- cx.span_lint(
+ span_lint(cx,
TOPLEVEL_REF_ARG,
arg.pat.span,
"`ref` directly on a function argument is ignored. Have you considered using a reference type instead?"
pub struct CmpNan;
impl LintPass for CmpNan {
- fn get_lints(&self) -> LintArray {
+ fn get_lints(&self) -> LintArray {
lint_array!(CMP_NAN)
- }
-
- fn check_expr(&mut self, cx: &Context, expr: &Expr) {
- if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
- if is_comparison_binop(cmp.node) {
- if let &ExprPath(_, ref path) = &left.node {
- check_nan(cx, path, expr.span);
- }
- if let &ExprPath(_, ref path) = &right.node {
- check_nan(cx, path, expr.span);
- }
- }
- }
- }
+ }
+
+ fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+ if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
+ if is_comparison_binop(cmp.node) {
+ if let &ExprPath(_, ref path) = &left.node {
+ check_nan(cx, path, expr.span);
+ }
+ if let &ExprPath(_, ref path) = &right.node {
+ check_nan(cx, path, expr.span);
+ }
+ }
+ }
+ }
}
fn check_nan(cx: &Context, path: &Path, span: Span) {
- path.segments.last().map(|seg| if seg.identifier.as_str() == "NAN" {
- cx.span_lint(CMP_NAN, span, "Doomed comparison with NAN, use std::{f32,f64}::is_nan instead");
- });
+ path.segments.last().map(|seg| if seg.identifier.name == "NAN" {
+ span_lint(cx, CMP_NAN, span,
+ "Doomed comparison with NAN, use std::{f32,f64}::is_nan instead");
+ });
}
declare_lint!(pub FLOAT_CMP, Warn,
- "Warn on ==/!= comparison of floaty values");
-
+ "Warn on ==/!= comparison of floaty values");
+
#[derive(Copy,Clone)]
pub struct FloatCmp;
impl LintPass for FloatCmp {
- fn get_lints(&self) -> LintArray {
+ fn get_lints(&self) -> LintArray {
lint_array!(FLOAT_CMP)
- }
-
- fn check_expr(&mut self, cx: &Context, expr: &Expr) {
- if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
- let op = cmp.node;
- if (op == BiEq || op == BiNe) && (is_float(cx, left) || is_float(cx, right)) {
- let map = cx.sess().codemap();
- cx.span_lint(FLOAT_CMP, expr.span, &format!(
- "{}-Comparison of f32 or f64 detected. You may want to change this to 'abs({} - {}) < epsilon' for some suitable value of epsilon",
- binop_to_string(op), &*map.span_to_snippet(left.span).unwrap_or("..".to_string()),
- &*map.span_to_snippet(right.span).unwrap_or("..".to_string())));
- }
- }
- }
+ }
+
+ fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+ if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
+ let op = cmp.node;
+ if (op == BiEq || op == BiNe) && (is_float(cx, left) || is_float(cx, right)) {
+ span_lint(cx, FLOAT_CMP, expr.span, &format!(
+ "{}-Comparison of f32 or f64 detected. You may want to change this to 'abs({} - {}) < epsilon' for some suitable value of epsilon",
+ binop_to_string(op), snippet(cx, left.span, ".."),
+ snippet(cx, right.span, "..")));
+ }
+ }
+ }
}
fn is_float(cx: &Context, expr: &Expr) -> bool {
- if let ty_float(_) = walk_ty(expr_ty(cx.tcx, expr)).sty { true } else { false }
+ if let ty::TyFloat(_) = walk_ty(cx.tcx.expr_ty(expr)).sty {
+ true
+ } else {
+ false
+ }
}
declare_lint!(pub PRECEDENCE, Warn,
- "Warn on mixing bit ops with integer arithmetic without parenthesis");
-
+ "Warn on mixing bit ops with integer arithmetic without parenthesis");
+
#[derive(Copy,Clone)]
pub struct Precedence;
impl LintPass for Precedence {
- fn get_lints(&self) -> LintArray {
+ fn get_lints(&self) -> LintArray {
lint_array!(PRECEDENCE)
- }
-
- fn check_expr(&mut self, cx: &Context, expr: &Expr) {
- if let ExprBinary(Spanned { node: op, ..}, ref left, ref right) = expr.node {
- if is_bit_op(op) && (is_arith_expr(left) || is_arith_expr(right)) {
- cx.span_lint(PRECEDENCE, expr.span,
- "Operator precedence can trip the unwary. Consider adding parenthesis to the subexpression.");
- }
- }
- }
+ }
+
+ fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+ if let ExprBinary(Spanned { node: op, ..}, ref left, ref right) = expr.node {
+ if is_bit_op(op) && (is_arith_expr(left) || is_arith_expr(right)) {
+ span_lint(cx, PRECEDENCE, expr.span,
+ "Operator precedence can trip the unwary. Consider adding parenthesis to the subexpression.");
+ }
+ }
+ }
}
fn is_arith_expr(expr : &Expr) -> bool {
- match expr.node {
- ExprBinary(Spanned { node: op, ..}, _, _) => is_arith_op(op),
- _ => false
- }
+ match expr.node {
+ ExprBinary(Spanned { node: op, ..}, _, _) => is_arith_op(op),
+ _ => false
+ }
}
fn is_bit_op(op : BinOp_) -> bool {
- match op {
- BiBitXor | BiBitAnd | BiBitOr | BiShl | BiShr => true,
- _ => false
- }
+ match op {
+ BiBitXor | BiBitAnd | BiBitOr | BiShl | BiShr => true,
+ _ => false
+ }
}
fn is_arith_op(op : BinOp_) -> bool {
- match op {
- BiAdd | BiSub | BiMul | BiDiv | BiRem => true,
- _ => false
- }
+ match op {
+ BiAdd | BiSub | BiMul | BiDiv | BiRem => true,
+ _ => false
+ }
}
declare_lint!(pub CMP_OWNED, Warn,
- "Warn on creating an owned string just for comparison");
-
+ "Warn on creating an owned string just for comparison");
+
#[derive(Copy,Clone)]
pub struct CmpOwned;
impl LintPass for CmpOwned {
- fn get_lints(&self) -> LintArray {
+ fn get_lints(&self) -> LintArray {
lint_array!(CMP_OWNED)
- }
-
- fn check_expr(&mut self, cx: &Context, expr: &Expr) {
- if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
- if is_comparison_binop(cmp.node) {
- check_to_owned(cx, left, right.span);
- check_to_owned(cx, right, left.span)
- }
- }
- }
+ }
+
+ fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+ if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
+ if is_comparison_binop(cmp.node) {
+ check_to_owned(cx, left, right.span);
+ check_to_owned(cx, right, left.span)
+ }
+ }
+ }
}
fn check_to_owned(cx: &Context, expr: &Expr, other_span: Span) {
- match &expr.node {
- &ExprMethodCall(Spanned{node: ref ident, ..}, _, ref args) => {
- let name = ident.as_str();
- if name == "to_string" ||
- name == "to_owned" && is_str_arg(cx, args) {
- cx.span_lint(CMP_OWNED, expr.span, &format!(
- "this creates an owned instance just for comparison. \
- Consider using {}.as_slice() to compare without allocation",
- cx.sess().codemap().span_to_snippet(other_span).unwrap_or(
- "..".to_string())))
- }
- },
- &ExprCall(ref path, _) => {
- if let &ExprPath(None, ref path) = &path.node {
- if path.segments.iter().zip(["String", "from_str"].iter()).all(
- |(seg, name)| &seg.identifier.as_str() == name) {
- cx.span_lint(CMP_OWNED, expr.span, &format!(
- "this creates an owned instance just for comparison. \
- Consider using {}.as_slice() to compare without allocation",
- cx.sess().codemap().span_to_snippet(other_span).unwrap_or(
- "..".to_string())))
- }
- }
- },
- _ => ()
- }
+ match &expr.node {
+ &ExprMethodCall(Spanned{node: ref ident, ..}, _, ref args) => {
+ let name = ident.name;
+ if name == "to_string" ||
+ name == "to_owned" && is_str_arg(cx, args) {
+ span_lint(cx, CMP_OWNED, expr.span, &format!(
+ "this creates an owned instance just for comparison. \
+ Consider using {}.as_slice() to compare without allocation",
+ snippet(cx, other_span, "..")))
+ }
+ },
+ &ExprCall(ref path, _) => {
+ if let &ExprPath(None, ref path) = &path.node {
+ if match_path(path, &["String", "from_str"]) ||
+ match_path(path, &["String", "from"]) {
+ span_lint(cx, CMP_OWNED, expr.span, &format!(
+ "this creates an owned instance just for comparison. \
+ Consider using {}.as_slice() to compare without allocation",
+ snippet(cx, other_span, "..")))
+ }
+ }
+ },
+ _ => ()
+ }
}
fn is_str_arg(cx: &Context, args: &[P<Expr>]) -> bool {
- args.len() == 1 && if let ty_str =
- walk_ty(expr_ty(cx.tcx, &*args[0])).sty { true } else { false }
+ args.len() == 1 && if let ty::TyStr =
+ walk_ty(cx.tcx.expr_ty(&*args[0])).sty { true } else { false }
+}
+
+declare_lint!(pub NEEDLESS_RETURN, Warn,
+ "Warn on using a return statement where an expression would be enough");
+
+#[derive(Copy,Clone)]
+pub struct NeedlessReturn;
+
+impl NeedlessReturn {
+ // Check the final stmt or expr in a block for unnecessary return.
+ fn check_block_return(&mut self, cx: &Context, block: &Block) {
+ if let Some(ref expr) = block.expr {
+ self.check_final_expr(cx, expr);
+ } else if let Some(stmt) = block.stmts.last() {
+ if let StmtSemi(ref expr, _) = stmt.node {
+ if let ExprRet(Some(ref inner)) = expr.node {
+ self.emit_lint(cx, (expr.span, inner.span));
+ }
+ }
+ }
+ }
+
+ // Check a the final expression in a block if it's a return.
+ fn check_final_expr(&mut self, cx: &Context, expr: &Expr) {
+ match expr.node {
+ // simple return is always "bad"
+ ExprRet(Some(ref inner)) => {
+ self.emit_lint(cx, (expr.span, inner.span));
+ }
+ // a whole block? check it!
+ ExprBlock(ref block) => {
+ self.check_block_return(cx, block);
+ }
+ // an if/if let expr, check both exprs
+ // note, if without else is going to be a type checking error anyways
+ // (except for unit type functions) so we don't match it
+ ExprIf(_, ref ifblock, Some(ref elsexpr)) |
+ ExprIfLet(_, _, ref ifblock, Some(ref elsexpr)) => {
+ self.check_block_return(cx, ifblock);
+ self.check_final_expr(cx, elsexpr);
+ }
+ // a match expr, check all arms
+ ExprMatch(_, ref arms, _) => {
+ for arm in arms {
+ self.check_final_expr(cx, &*arm.body);
+ }
+ }
+ _ => { }
+ }
+ }
+
+ fn emit_lint(&mut self, cx: &Context, spans: (Span, Span)) {
+ span_lint(cx, NEEDLESS_RETURN, spans.0, &format!(
+ "unneeded return statement. Consider using {} \
+ without the trailing semicolon",
+ snippet(cx, spans.1, "..")))
+ }
+}
+
+impl LintPass for NeedlessReturn {
+ fn get_lints(&self) -> LintArray {
+ lint_array!(NEEDLESS_RETURN)
+ }
+
+ fn check_fn(&mut self, cx: &Context, _: FnKind, _: &FnDecl,
+ block: &Block, _: Span, _: ast::NodeId) {
+ self.check_block_return(cx, block);
+ }
}
use syntax::ptr::P;
use syntax::ast::*;
use rustc::lint::{Context, LintPass, LintArray, Lint};
-use rustc::middle::ty::{expr_ty, sty, ty_ptr, ty_rptr, mt};
-use syntax::codemap::ExpnInfo;
+use rustc::middle::ty::{TypeVariants, TypeAndMut, TyRef};
+use syntax::codemap::{BytePos, ExpnInfo, Span};
+use utils::{in_macro, span_lint};
declare_lint!(pub MUT_MUT, Warn,
"Warn on usage of double-mut refs, e.g. '&mut &mut ...'");
}
fn check_ty(&mut self, cx: &Context, ty: &Ty) {
- unwrap_mut(ty).and_then(unwrap_mut).map_or((), |_| cx.span_lint(MUT_MUT,
+ unwrap_mut(ty).and_then(unwrap_mut).map_or((), |_| span_lint(cx, MUT_MUT,
ty.span, "Generally you want to avoid &mut &mut _ if possible."))
}
}
fn check_expr_expd(cx: &Context, expr: &Expr, info: Option<&ExpnInfo>) {
- if in_external_macro(info) { return; }
+ if in_macro(cx, info) { return; }
fn unwrap_addr(expr : &Expr) -> Option<&Expr> {
match expr.node {
unwrap_addr(expr).map_or((), |e| {
unwrap_addr(e).map(|_| {
- cx.span_lint(MUT_MUT, expr.span,
+ span_lint(cx, MUT_MUT, expr.span,
"Generally you want to avoid &mut &mut _ if possible.")
}).unwrap_or_else(|| {
- if let ty_rptr(_, mt{ty: _, mutbl: MutMutable}) =
- expr_ty(cx.tcx, e).sty {
- cx.span_lint(MUT_MUT, expr.span,
+ if let TyRef(_, TypeAndMut{ty: _, mutbl: MutMutable}) =
+ cx.tcx.expr_ty(e).sty {
+ span_lint(cx, MUT_MUT, expr.span,
"This expression mutably borrows a mutable reference. \
Consider reborrowing")
}
})
}
-fn in_external_macro(info: Option<&ExpnInfo>) -> bool {
- info.map_or(false, |i| i.callee.span.is_some())
-}
-
fn unwrap_mut(ty : &Ty) -> Option<&Ty> {
match ty.node {
TyPtr(MutTy{ ty: ref pty, mutbl: MutMutable }) => Option::Some(pty),
use syntax::ast_util::{is_comparison_binop, binop_to_string};
use syntax::ptr::P;
use syntax::codemap::Span;
+use utils::{de_p, span_lint};
declare_lint! {
pub NEEDLESS_BOOL,
fn check_expr(&mut self, cx: &Context, e: &Expr) {
if let ExprIf(_, ref then_block, Option::Some(ref else_expr)) = e.node {
match (fetch_bool_block(then_block), fetch_bool_expr(else_expr)) {
- (Option::Some(true), Option::Some(true)) => { cx.span_lint(NEEDLESS_BOOL, e.span, "your if-then-else expression will always return true"); },
- (Option::Some(true), Option::Some(false)) => { cx.span_lint(NEEDLESS_BOOL, e.span, "you can reduce your if-statement to its predicate"); },
- (Option::Some(false), Option::Some(true)) => { cx.span_lint(NEEDLESS_BOOL, e.span, "you can reduce your if-statement to '!' + your predicate"); },
- (Option::Some(false), Option::Some(false)) => { cx.span_lint(NEEDLESS_BOOL, e.span, "your if-then-else expression will always return false"); },
+ (Option::Some(true), Option::Some(true)) => {
+ span_lint(cx, NEEDLESS_BOOL, e.span,
+ "your if-then-else expression will always return true"); },
+ (Option::Some(true), Option::Some(false)) => {
+ span_lint(cx, NEEDLESS_BOOL, e.span,
+ "you can reduce your if-statement to its predicate"); },
+ (Option::Some(false), Option::Some(true)) => {
+ span_lint(cx, NEEDLESS_BOOL, e.span,
+ "you can reduce your if-statement to '!' + your predicate"); },
+ (Option::Some(false), Option::Some(false)) => {
+ span_lint(cx, NEEDLESS_BOOL, e.span,
+ "your if-then-else expression will always return false"); },
_ => ()
}
}
}
fn fetch_bool_block(block: &Block) -> Option<bool> {
- if block.stmts.is_empty() { block.expr.as_ref().and_then(|e| fetch_bool_expr(e)) } else { Option::None }
+ if block.stmts.is_empty() {
+ block.expr.as_ref().map(de_p).and_then(fetch_bool_expr)
+ } else { Option::None }
}
fn fetch_bool_expr(expr: &Expr) -> Option<bool> {
use syntax::ptr::P;
use syntax::codemap::Span;
use types::match_ty_unwrap;
+use utils::span_lint;
declare_lint! {
pub PTR_ARG,
fn check_ptr_subtype(cx: &Context, span: Span, ty: &Ty) {
match_ty_unwrap(ty, &["Vec"]).map_or_else(|| match_ty_unwrap(ty,
&["String"]).map_or((), |_| {
- cx.span_lint(PTR_ARG, span,
+ span_lint(cx, PTR_ARG, span,
"Writing '&String' instead of '&str' involves a new Object \
where a slices will do. Consider changing the type to &str")
- }), |_| cx.span_lint(PTR_ARG, span, "Writing '&Vec<_>' instead of \
+ }), |_| span_lint(cx, PTR_ARG, span, "Writing '&Vec<_>' instead of \
'&[_]' involves one more reference and cannot be used with \
non-vec-based slices. Consider changing the type to &[...]")
)
--- /dev/null
+//! This LintPass catches both string addition and string addition + assignment
+//!
+//! 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::*;
+use rustc::middle::ty::TypeVariants::TyStruct;
+use syntax::ast::*;
+use syntax::codemap::{Span, Spanned};
+use eq_op::is_exp_equal;
+use misc::walk_ty;
+use types::match_ty_unwrap;
+use utils::{match_def_path, span_lint};
+
+declare_lint! {
+ pub STRING_ADD_ASSIGN,
+ Warn,
+ "Warn on `x = x + ..` where x is a `String`"
+}
+
+#[derive(Copy,Clone)]
+pub struct StringAdd;
+
+impl LintPass for StringAdd {
+ fn get_lints(&self) -> LintArray {
+ lint_array!(STRING_ADD_ASSIGN)
+ }
+
+ fn check_expr(&mut self, cx: &Context, e: &Expr) {
+ if let &ExprAssign(ref target, ref src) = &e.node {
+ if is_string(cx, target) && is_add(src, target) {
+ span_lint(cx, STRING_ADD_ASSIGN, e.span,
+ "You assign the result of adding something to this string. \
+ Consider using `String::push_str(..) instead.")
+ }
+ }
+ }
+}
+
+fn is_string(cx: &Context, e: &Expr) -> bool {
+ if let TyStruct(did, _) = walk_ty(cx.tcx.expr_ty(e)).sty {
+ match_def_path(cx, did.did, &["std", "string", "String"])
+ } else { false }
+}
+
+fn is_add(src: &Expr, target: &Expr) -> bool {
+ match &src.node {
+ &ExprBinary(Spanned{ node: BiAdd, .. }, ref left, _) =>
+ is_exp_equal(target, left),
+ &ExprBlock(ref block) => block.stmts.is_empty() &&
+ block.expr.as_ref().map_or(false, |expr| is_add(&*expr, target)),
+ &ExprParen(ref expr) => is_add(&*expr, target),
+ _ => false
+ }
+}
use rustc::lint::{Context, LintPass, LintArray, Lint, Level};
use syntax::codemap::Span;
+use utils::span_lint;
+
/// Handles all the linting of funky types
#[allow(missing_copy_implementations)]
pub struct TypePass;
// I could muck around with the maps and find the full path
// however the more efficient way is to simply reverse the iterators and zip them
// which will compare them in reverse until one of them runs out of segments
- if seg.iter().rev().zip(segments.iter().rev()).all(|(a,b)| a.identifier.as_str() == *b) {
+ if seg.iter().rev().zip(segments.iter().rev()).all(|(a,b)| a.identifier.name == b) {
match seg[..].last() {
Some(&PathSegment {parameters: AngleBracketedParameters(ref a), ..}) => {
Some(&a.types[..])
/// Lets me span a note only if the lint is shown
pub fn span_note_and_lint(cx: &Context, lint: &'static Lint, span: Span, msg: &str, note: &str) {
- cx.span_lint(lint, span, msg);
+ span_lint(cx, lint, span, msg);
if cx.current_level(lint) != Level::Allow {
cx.sess().span_note(span, note);
}
--- /dev/null
+use rustc::lint::*;
+use syntax::ast::*;
+use syntax::codemap::{BytePos, Span};
+use utils::span_lint;
+
+declare_lint!{ pub ZERO_WIDTH_SPACE, Deny, "Zero-width space is confusing" }
+
+#[derive(Copy, Clone)]
+pub struct Unicode;
+
+impl LintPass for Unicode {
+ fn get_lints(&self) -> LintArray {
+ lint_array!(ZERO_WIDTH_SPACE)
+ }
+
+ fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+ if let ExprLit(ref lit) = expr.node {
+ if let LitStr(ref string, _) = lit.node {
+ check_str(cx, string, lit.span)
+ }
+ }
+ }
+}
+
+fn check_str(cx: &Context, string: &str, span: Span) {
+ let mut start: Option<usize> = None;
+ for (i, c) in string.char_indices() {
+ if c == '\u{200B}' {
+ if start.is_none() { start = Some(i); }
+ } else {
+ lint_zero_width(cx, span, start);
+ start = None;
+ }
+ }
+ lint_zero_width(cx, span, start);
+}
+
+fn lint_zero_width(cx: &Context, span: Span, start: Option<usize>) {
+ start.map(|index| {
+ span_lint(cx, ZERO_WIDTH_SPACE, Span {
+ lo: span.lo + BytePos(index as u32),
+ hi: span.lo + BytePos(index as u32),
+ expn_id: span.expn_id,
+ }, "Zero-width space detected. Consider using \\u{200B}")
+ });
+}
--- /dev/null
+use rustc::lint::{Context, Lint, Level};
+use syntax::ast::{DefId, Expr, Name, NodeId, Path};
+use syntax::codemap::{ExpnInfo, Span};
+use syntax::ptr::P;
+use rustc::ast_map::Node::NodeExpr;
+use rustc::middle::ty;
+use std::borrow::{Cow, IntoCow};
+use std::convert::From;
+
+/// returns true if the macro that expanded the crate was outside of
+/// the current crate or was a compiler plugin
+pub fn in_macro(cx: &Context, opt_info: Option<&ExpnInfo>) -> bool {
+ // no ExpnInfo = no macro
+ opt_info.map_or(false, |info| {
+ // no span for the callee = external macro
+ info.callee.span.map_or(true, |span| {
+ // no snippet = external macro or compiler-builtin expansion
+ cx.sess().codemap().span_to_snippet(span).ok().map_or(true, |code|
+ // macro doesn't start with "macro_rules"
+ // = compiler plugin
+ !code.starts_with("macro_rules")
+ )
+ })
+ })
+}
+
+/// invokes in_macro with the expansion info of the given span
+pub fn in_external_macro(cx: &Context, span: Span) -> bool {
+ cx.sess().codemap().with_expn_info(span.expn_id,
+ |info| in_macro(cx, info))
+}
+
+/// check if a DefId's path matches the given absolute type path
+/// usage e.g. with
+/// `match_def_path(cx, id, &["core", "option", "Option"])`
+pub fn match_def_path(cx: &Context, def_id: DefId, path: &[&str]) -> bool {
+ cx.tcx.with_path(def_id, |iter| iter.map(|elem| elem.name())
+ .zip(path.iter()).all(|(nm, p)| &nm.as_str() == p))
+}
+
+/// match a Path against a slice of segment string literals, e.g.
+/// `match_path(path, &["std", "rt", "begin_unwind"])`
+pub fn match_path(path: &Path, segments: &[&str]) -> bool {
+ path.segments.iter().rev().zip(segments.iter().rev()).all(
+ |(a,b)| &a.identifier.name.as_str() == b)
+}
+
+/// convert a span to a code snippet if available, otherwise use default, e.g.
+/// `snippet(cx, expr.span, "..")`
+pub fn snippet<'a>(cx: &Context, span: Span, default: &'a str) -> Cow<'a, str> {
+ cx.sess().codemap().span_to_snippet(span).map(From::from).unwrap_or(Cow::Borrowed(default))
+}
+
+/// get a parent expr if any – this is useful to constrain a lint
+pub fn get_parent_expr<'c>(cx: &'c Context, e: &Expr) -> Option<&'c Expr> {
+ let map = &cx.tcx.map;
+ let node_id : NodeId = e.id;
+ let parent_id : NodeId = map.get_parent_node(node_id);
+ if node_id == parent_id { return None; }
+ map.find(parent_id).and_then(|node|
+ if let NodeExpr(parent) = node { Some(parent) } else { None } )
+}
+
+/// dereference a P<T> and return a ref on the result
+pub fn de_p<T>(p: &P<T>) -> &T { &*p }
+
+#[cfg(not(feature="structured_logging"))]
+pub fn span_lint(cx: &Context, lint: &'static Lint, sp: Span, msg: &str) {
+ cx.span_lint(lint, sp, msg);
+}
+
+#[cfg(feature="structured_logging")]
+pub fn span_lint(cx: &Context, lint: &'static Lint, sp: Span, msg: &str) {
+ // lint.name / lint.desc is can give details of the lint
+ // cx.sess().codemap() has all these nice functions for line/column/snippet details
+ // http://doc.rust-lang.org/syntax/codemap/struct.CodeMap.html#method.span_to_string
+ cx.span_lint(lint, sp, msg);
+}
+
+pub fn span_help_and_lint(cx: &Context, lint: &'static Lint, span: Span,
+ msg: &str, help: &str) {
+ span_lint(cx, lint, span, msg);
+ if cx.current_level(lint) != Level::Allow {
+ cx.sess().span_help(span, help);
+ }
+}
--- /dev/null
+#![feature(plugin)]
+#![plugin(clippy)]
+
+#![deny(inline_always)]
+
+#[inline(always)] //~ERROR You have declared #[inline(always)] on test_attr_lint.
+fn test_attr_lint() {
+ assert!(true)
+}
+
+#[inline(always)]
+fn false_positive_expr() {
+ unreachable!()
+}
+
+#[inline(always)]
+fn false_positive_stmt() {
+ unreachable!();
+}
+
+#[inline(always)]
+fn empty_and_false_positive_stmt() {
+ ;
+ unreachable!();
+}
+
+
+fn main() {
+ test_attr_lint();
+ if false { false_positive_expr() }
+ if false { false_positive_stmt() }
+ if false { empty_and_false_positive_stmt() }
+}
x & 0 == 0; //~ERROR &-masking with zero
x & 1 == 1; //ok, distinguishes bit 0
+ x & 1 == 0; //ok, compared with zero
x & 2 == 1; //~ERROR
x | 0 == 0; //ok, equals x == 0 (maybe warn?)
x | 1 == 3; //ok, equals x == 2 || x == 3
x != "foo".to_owned(); //~ERROR this creates an owned instance
- x != String::from_str("foo"); //~ERROR this creates an owned instance
+ #[allow(deprecated)] // for from_str
+ fn old_timey(x : &str) {
+ x != String::from_str("foo"); //~ERROR this creates an owned instance
+ }
+ old_timey(x);
+
+ x != String::from("foo"); //~ERROR this creates an owned instance
}
}
}
+ if x == "hello" {
+ print!("Hello ");
+ if y == "world" {
+ println!("world!")
+ }
+ }
}
#[deny(len_without_is_empty)]
impl One {
- fn len(self: &Self) -> isize { //~ERROR Item 'One' has a '.len()' method
+ fn len(self: &Self) -> isize { //~ERROR Item 'One' has a '.len(_: &Self)'
1
}
}
#[deny(len_without_is_empty)]
trait TraitsToo {
- fn len(self: &Self) -> isize; //~ERROR Trait 'TraitsToo' has a '.len()' method,
+ fn len(self: &Self) -> isize; //~ERROR Trait 'TraitsToo' has a '.len(_:
}
impl TraitsToo for One {
}
}
-#[allow(dead_code)]
struct HasIsEmpty;
#[deny(len_without_is_empty)]
-#[allow(dead_code)]
impl HasIsEmpty {
fn len(self: &Self) -> isize {
1
}
+
+ fn is_empty(self: &Self) -> bool {
+ false
+ }
+}
+
+struct Wither;
+
+#[deny(len_without_is_empty)]
+trait WithIsEmpty {
+ fn len(self: &Self) -> isize;
+ fn is_empty(self: &Self) -> bool;
+}
+
+impl WithIsEmpty for Wither {
+ fn len(self: &Self) -> isize {
+ 1
+ }
+
+ fn is_empty(self: &Self) -> bool {
+ false
+ }
+}
+
+struct HasWrongIsEmpty;
+
+#[deny(len_without_is_empty)]
+impl HasWrongIsEmpty {
+ fn len(self: &Self) -> isize { //~ERROR Item 'HasWrongIsEmpty' has a '.len(_: &Self)'
+ 1
+ }
- fn is_empty() -> bool {
+ #[allow(dead_code, unused)]
+ fn is_empty(self: &Self, x : u32) -> bool {
false
}
}
}
let y = One;
- // false positives here
- if y.len() == 0 { //~ERROR Consider replacing the len comparison
+ if y.len() == 0 { //no error because One does not have .is_empty()
println!("This should not happen either!");
}
let z : &TraitsToo = &y;
- if z.len() > 0 { //~ERROR Consider replacing the len comparison
+ if z.len() > 0 { //no error, because TraitsToo has no .is_empty() method
println!("Nor should this!");
}
+
+ let hie = HasIsEmpty;
+ if hie.len() == 0 { //~ERROR Consider replacing the len comparison
+ println!("Or this!");
+ }
+ assert!(!hie.is_empty());
+
+ let wie : &WithIsEmpty = &Wither;
+ if wie.len() == 0 { //~ERROR Consider replacing the len comparison
+ println!("Or this!");
+ }
+ assert!(!wie.is_empty());
+
+ let hwie = HasWrongIsEmpty;
+ if hwie.len() == 0 { //no error as HasWrongIsEmpty does not have .is_empty()
+ println!("Or this!");
+ }
}
fn main(){
let x = Some(1u8);
match x { //~ ERROR You seem to be trying to use match
- //~^ NOTE Try if let Some(y) = x { ... }
+ //~^ HELP Try if let Some(y) = x { ... }
Some(y) => println!("{:?}", y),
_ => ()
}
}
let z = (1u8,1u8);
match z { //~ ERROR You seem to be trying to use match
- //~^ NOTE Try if let (2...3, 7...9) = z { ... }
+ //~^ HELP Try if let (2...3, 7...9) = z { ... }
(2...3, 7...9) => println!("{:?}", z),
_ => {}
}
#![feature(plugin)]
#![plugin(clippy)]
+//#![plugin(regex_macros)]
+//extern crate regex;
+
#[deny(mut_mut)]
fn fun(x : &mut &mut u32) -> bool { //~ERROR
**x > 0
}
+macro_rules! mut_ptr {
+ ($p:expr) => { &mut $p }
+}
+
#[deny(mut_mut)]
#[allow(unused_mut, unused_variables)]
fn main() {
//~^^^^ ERROR
***y + **x;
}
+
+ let mut z = mut_ptr!(&mut 3u32); //~ERROR
}
--- /dev/null
+#![feature(plugin)]
+#![plugin(clippy)]
+
+#![deny(needless_return)]
+
+fn test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+ return true; //~ERROR
+}
+
+fn test_no_semicolon() -> bool {
+ return true //~ERROR
+}
+
+fn test_if_block() -> bool {
+ if true {
+ return true; //~ERROR
+ } else {
+ return false; //~ERROR
+ }
+}
+
+fn test_match(x: bool) -> bool {
+ match x {
+ true => {
+ return false; //~ERROR
+ }
+ false => {
+ return true //~ERROR
+ }
+ }
+}
+
+fn test_closure() {
+ let _ = || {
+ return true; //~ERROR
+ };
+}
+
+fn main() {
+ let _ = test_end_of_fn();
+ let _ = test_no_semicolon();
+ let _ = test_if_block();
+ let _ = test_match(true);
+ test_closure();
+}
--- /dev/null
+#![feature(plugin)]
+#![plugin(clippy)]
+
+#![deny(string_add_assign)]
+
+fn main() {
+ let x = "".to_owned();
+
+ for i in (1..3) {
+ x = x + "."; //~ERROR
+ }
+}
--- /dev/null
+#![feature(plugin)]
+#![plugin(clippy)]
+
+#[deny(zero_width_space)]
+fn zero() {
+ print!("Here >< is a ZWS, and another");
+ //~^ ERROR Zero-width space detected. Consider using \u{200B}
+ //~^^ ERROR Zero-width space detected. Consider using \u{200B}
+}
+
+//#[deny(unicode_canon)]
+fn canon() {
+ print!("̀ah?"); //not yet ~ERROR Non-canonical unicode sequence detected. Consider using à
+}
+
+//#[deny(ascii_only)]
+fn uni() {
+ println!("Üben!"); //not yet ~ERROR Unicode literal detected. Consider using \u{FC}
+}
+
+fn main() {
+ zero();
+ uni();
+ canon();
+}
extern crate compiletest_rs as compiletest;
use std::path::PathBuf;
+use std::env::var;
fn run_mode(mode: &'static str) {
let mut config = compiletest::default_config();
+
let cfg_mode = mode.parse().ok().expect("Invalid mode");
- config.target_rustcflags = Some("-L target/debug/".to_string());
+ config.target_rustcflags = Some("-L target/debug/".to_owned());
+ if let Ok(name) = var::<&str>("TESTNAME") {
+ let s : String = name.to_owned();
+ config.filter = Some(s)
+ }
config.mode = cfg_mode;
config.src_base = PathBuf::from(format!("tests/{}", mode));
--- /dev/null
+#![feature(plugin)]
+#![plugin(clippy, regex_macros)]
+
+#[macro_use]
+extern crate lazy_static;
+extern crate regex;
+
+use std::collections::HashMap;
+
+#[test]
+#[deny(mut_mut)]
+fn test_regex() {
+ let pattern = regex!(r"^(?P<level>[#]+)\s(?P<title>.+)$");
+ assert!(pattern.is_match("# headline"));
+}
+
+#[test]
+#[deny(mut_mut)]
+#[allow(unused_variables, unused_mut)]
+fn test_lazy_static() {
+ lazy_static! {
+ static ref MUT_MAP : HashMap<usize, &'static str> = {
+ let mut m = HashMap::new();
+ let mut zero = &mut &mut "zero";
+ m.insert(0, "zero");
+ m
+ };
+ static ref MUT_COUNT : usize = MUT_MAP.len();
+ }
+ assert!(*MUT_COUNT == 1);
+}