mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-10 07:04:18 +00:00
compute cyclomatic complexity (adjusted to not punish Rust's match
)
This commit is contained in:
parent
b45745e905
commit
617c820e6b
11 changed files with 485 additions and 127 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -12,3 +12,6 @@
|
||||||
|
|
||||||
# We don't pin yet
|
# We don't pin yet
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
|
||||||
|
# Generated by dogfood
|
||||||
|
/target_recur/
|
||||||
|
|
|
@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
|
||||||
[Jump to usage instructions](#usage)
|
[Jump to usage instructions](#usage)
|
||||||
|
|
||||||
##Lints
|
##Lints
|
||||||
There are 79 lints included in this crate:
|
There are 80 lints included in this crate:
|
||||||
|
|
||||||
name | default | meaning
|
name | default | meaning
|
||||||
---------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -22,6 +22,7 @@ name
|
||||||
[cmp_nan](https://github.com/Manishearth/rust-clippy/wiki#cmp_nan) | deny | comparisons to NAN (which will always return false, which is probably not intended)
|
[cmp_nan](https://github.com/Manishearth/rust-clippy/wiki#cmp_nan) | deny | comparisons to NAN (which will always return false, which is probably not intended)
|
||||||
[cmp_owned](https://github.com/Manishearth/rust-clippy/wiki#cmp_owned) | warn | creating owned instances for comparing with others, e.g. `x == "foo".to_string()`
|
[cmp_owned](https://github.com/Manishearth/rust-clippy/wiki#cmp_owned) | warn | creating owned instances for comparing with others, e.g. `x == "foo".to_string()`
|
||||||
[collapsible_if](https://github.com/Manishearth/rust-clippy/wiki#collapsible_if) | warn | two nested `if`-expressions can be collapsed into one, e.g. `if x { if y { foo() } }` can be written as `if x && y { foo() }`
|
[collapsible_if](https://github.com/Manishearth/rust-clippy/wiki#collapsible_if) | warn | two nested `if`-expressions can be collapsed into one, e.g. `if x { if y { foo() } }` can be written as `if x && y { foo() }`
|
||||||
|
[cyclomatic_complexity](https://github.com/Manishearth/rust-clippy/wiki#cyclomatic_complexity) | warn | finds functions that should be split up into multiple functions
|
||||||
[empty_loop](https://github.com/Manishearth/rust-clippy/wiki#empty_loop) | warn | empty `loop {}` detected
|
[empty_loop](https://github.com/Manishearth/rust-clippy/wiki#empty_loop) | warn | empty `loop {}` detected
|
||||||
[eq_op](https://github.com/Manishearth/rust-clippy/wiki#eq_op) | warn | equal operands on both sides of a comparison or bitwise combination (e.g. `x == x`)
|
[eq_op](https://github.com/Manishearth/rust-clippy/wiki#eq_op) | warn | equal operands on both sides of a comparison or bitwise combination (e.g. `x == x`)
|
||||||
[explicit_counter_loop](https://github.com/Manishearth/rust-clippy/wiki#explicit_counter_loop) | warn | for-looping with an explicit counter when `_.enumerate()` would do
|
[explicit_counter_loop](https://github.com/Manishearth/rust-clippy/wiki#explicit_counter_loop) | warn | for-looping with an explicit counter when `_.enumerate()` would do
|
||||||
|
|
105
src/cyclomatic_complexity.rs
Normal file
105
src/cyclomatic_complexity.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
//! calculate cyclomatic complexity and warn about overly complex functions
|
||||||
|
|
||||||
|
use rustc::lint::*;
|
||||||
|
use rustc_front::hir::*;
|
||||||
|
use rustc::middle::cfg::CFG;
|
||||||
|
use syntax::codemap::Span;
|
||||||
|
use syntax::attr::*;
|
||||||
|
use syntax::ast::Attribute;
|
||||||
|
use rustc_front::intravisit::{Visitor, walk_expr};
|
||||||
|
|
||||||
|
use utils::{in_macro, LimitStack};
|
||||||
|
|
||||||
|
declare_lint! { pub CYCLOMATIC_COMPLEXITY, Warn,
|
||||||
|
"finds functions that should be split up into multiple functions" }
|
||||||
|
|
||||||
|
pub struct CyclomaticComplexity {
|
||||||
|
limit: LimitStack,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CyclomaticComplexity {
|
||||||
|
pub fn new(limit: u64) -> Self {
|
||||||
|
CyclomaticComplexity {
|
||||||
|
limit: LimitStack::new(limit),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LintPass for CyclomaticComplexity {
|
||||||
|
fn get_lints(&self) -> LintArray {
|
||||||
|
lint_array!(CYCLOMATIC_COMPLEXITY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CyclomaticComplexity {
|
||||||
|
fn check(&mut self, cx: &LateContext, block: &Block, span: Span) {
|
||||||
|
if in_macro(cx, span) { return; }
|
||||||
|
let cfg = CFG::new(cx.tcx, block);
|
||||||
|
let n = cfg.graph.len_nodes() as u64;
|
||||||
|
let e = cfg.graph.len_edges() as u64;
|
||||||
|
let cc = e + 2 - n;
|
||||||
|
let mut arm_counter = MatchArmCounter(0);
|
||||||
|
arm_counter.visit_block(block);
|
||||||
|
let mut narms = arm_counter.0;
|
||||||
|
if narms > 0 {
|
||||||
|
narms = narms - 1;
|
||||||
|
}
|
||||||
|
if cc < narms {
|
||||||
|
println!("cc = {}, arms = {}", cc, narms);
|
||||||
|
println!("{:?}", block);
|
||||||
|
println!("{:?}", span);
|
||||||
|
panic!("cc = {}, arms = {}", cc, narms);
|
||||||
|
}
|
||||||
|
let rust_cc = cc - narms;
|
||||||
|
if rust_cc > self.limit.limit() {
|
||||||
|
cx.span_lint_help(CYCLOMATIC_COMPLEXITY, span,
|
||||||
|
&format!("The function has a cyclomatic complexity of {}.", rust_cc),
|
||||||
|
"You could split it up into multiple smaller functions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LateLintPass for CyclomaticComplexity {
|
||||||
|
fn check_item(&mut self, cx: &LateContext, item: &Item) {
|
||||||
|
if let ItemFn(_, _, _, _, _, ref block) = item.node {
|
||||||
|
self.check(cx, block, item.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_impl_item(&mut self, cx: &LateContext, item: &ImplItem) {
|
||||||
|
if let ImplItemKind::Method(_, ref block) = item.node {
|
||||||
|
self.check(cx, block, item.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_trait_item(&mut self, cx: &LateContext, item: &TraitItem) {
|
||||||
|
if let MethodTraitItem(_, Some(ref block)) = item.node {
|
||||||
|
self.check(cx, block, item.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_lint_attrs(&mut self, cx: &LateContext, attrs: &[Attribute]) {
|
||||||
|
self.limit.push_attrs(cx.sess(), attrs, "cyclomatic_complexity");
|
||||||
|
}
|
||||||
|
fn exit_lint_attrs(&mut self, cx: &LateContext, attrs: &[Attribute]) {
|
||||||
|
self.limit.pop_attrs(cx.sess(), attrs, "cyclomatic_complexity");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MatchArmCounter(u64);
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for MatchArmCounter {
|
||||||
|
fn visit_expr(&mut self, e: &'a Expr) {
|
||||||
|
match e.node {
|
||||||
|
ExprMatch(_, ref arms, _) => {
|
||||||
|
walk_expr(self, e);
|
||||||
|
let arms_n: u64 = arms.iter().map(|arm| arm.pats.len() as u64).sum();
|
||||||
|
if arms_n > 0 {
|
||||||
|
self.0 += arms_n - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ExprClosure(..) => {},
|
||||||
|
_ => walk_expr(self, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
#![feature(plugin_registrar, box_syntax)]
|
#![feature(plugin_registrar, box_syntax)]
|
||||||
#![feature(rustc_private, core, collections)]
|
#![feature(rustc_private, core, collections)]
|
||||||
#![feature(num_bits_bytes)]
|
#![feature(num_bits_bytes, iter_arith)]
|
||||||
#![allow(unknown_lints)]
|
#![allow(unknown_lints)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -59,6 +59,7 @@ pub mod needless_update;
|
||||||
pub mod no_effect;
|
pub mod no_effect;
|
||||||
pub mod temporary_assignment;
|
pub mod temporary_assignment;
|
||||||
pub mod transmute;
|
pub mod transmute;
|
||||||
|
pub mod cyclomatic_complexity;
|
||||||
|
|
||||||
mod reexport {
|
mod reexport {
|
||||||
pub use syntax::ast::{Name, Ident, NodeId};
|
pub use syntax::ast::{Name, Ident, NodeId};
|
||||||
|
@ -110,6 +111,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
|
||||||
reg.register_late_lint_pass(box map_clone::MapClonePass);
|
reg.register_late_lint_pass(box map_clone::MapClonePass);
|
||||||
reg.register_late_lint_pass(box temporary_assignment::TemporaryAssignmentPass);
|
reg.register_late_lint_pass(box temporary_assignment::TemporaryAssignmentPass);
|
||||||
reg.register_late_lint_pass(box transmute::UselessTransmute);
|
reg.register_late_lint_pass(box transmute::UselessTransmute);
|
||||||
|
reg.register_late_lint_pass(box cyclomatic_complexity::CyclomaticComplexity::new(25));
|
||||||
|
|
||||||
reg.register_lint_group("clippy_pedantic", vec![
|
reg.register_lint_group("clippy_pedantic", vec![
|
||||||
methods::OPTION_UNWRAP_USED,
|
methods::OPTION_UNWRAP_USED,
|
||||||
|
@ -138,6 +140,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
|
||||||
block_in_if_condition::BLOCK_IN_IF_CONDITION_EXPR,
|
block_in_if_condition::BLOCK_IN_IF_CONDITION_EXPR,
|
||||||
block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT,
|
block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT,
|
||||||
collapsible_if::COLLAPSIBLE_IF,
|
collapsible_if::COLLAPSIBLE_IF,
|
||||||
|
cyclomatic_complexity::CYCLOMATIC_COMPLEXITY,
|
||||||
eq_op::EQ_OP,
|
eq_op::EQ_OP,
|
||||||
eta_reduction::REDUNDANT_CLOSURE,
|
eta_reduction::REDUNDANT_CLOSURE,
|
||||||
identity_op::IDENTITY_OP,
|
identity_op::IDENTITY_OP,
|
||||||
|
|
|
@ -108,7 +108,7 @@ fn could_use_elision(cx: &LateContext, func: &FnDecl, slf: Option<&ExplicitSelf>
|
||||||
|
|
||||||
// no input lifetimes? easy case!
|
// no input lifetimes? easy case!
|
||||||
if input_lts.is_empty() {
|
if input_lts.is_empty() {
|
||||||
return false;
|
false
|
||||||
} else if output_lts.is_empty() {
|
} else if output_lts.is_empty() {
|
||||||
// no output lifetimes, check distinctness of input lifetimes
|
// no output lifetimes, check distinctness of input lifetimes
|
||||||
|
|
||||||
|
@ -117,9 +117,7 @@ fn could_use_elision(cx: &LateContext, func: &FnDecl, slf: Option<&ExplicitSelf>
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// we have no output reference, so we only need all distinct lifetimes
|
// we have no output reference, so we only need all distinct lifetimes
|
||||||
if input_lts.len() == unique_lifetimes(&input_lts) {
|
input_lts.len() == unique_lifetimes(&input_lts)
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// we have output references, so we need one input reference,
|
// we have output references, so we need one input reference,
|
||||||
// and all output lifetimes must be the same
|
// and all output lifetimes must be the same
|
||||||
|
@ -128,15 +126,16 @@ fn could_use_elision(cx: &LateContext, func: &FnDecl, slf: Option<&ExplicitSelf>
|
||||||
}
|
}
|
||||||
if input_lts.len() == 1 {
|
if input_lts.len() == 1 {
|
||||||
match (&input_lts[0], &output_lts[0]) {
|
match (&input_lts[0], &output_lts[0]) {
|
||||||
(&Named(n1), &Named(n2)) if n1 == n2 => { return true; }
|
(&Named(n1), &Named(n2)) if n1 == n2 => true,
|
||||||
(&Named(_), &Unnamed) => { return true; }
|
(&Named(_), &Unnamed) => true,
|
||||||
(&Unnamed, &Named(_)) => { return true; }
|
(&Unnamed, &Named(_)) => true,
|
||||||
_ => { } // already elided, different named lifetimes
|
_ => false // already elided, different named lifetimes
|
||||||
// or something static going on
|
// or something static going on
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allowed_lts_from(named_lts: &[LifetimeDef]) -> HashSet<RefLt> {
|
fn allowed_lts_from(named_lts: &[LifetimeDef]) -> HashSet<RefLt> {
|
||||||
|
|
224
src/loops.rs
224
src/loops.rs
|
@ -56,116 +56,7 @@ impl LintPass for LoopsPass {
|
||||||
impl LateLintPass for LoopsPass {
|
impl LateLintPass for LoopsPass {
|
||||||
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
|
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
|
||||||
if let Some((pat, arg, body)) = recover_for_loop(expr) {
|
if let Some((pat, arg, body)) = recover_for_loop(expr) {
|
||||||
// check for looping over a range and then indexing a sequence with it
|
check_for_loop(cx, pat, arg, body, expr);
|
||||||
// -> the iteratee must be a range literal
|
|
||||||
if let ExprRange(Some(ref l), _) = arg.node {
|
|
||||||
// Range should start with `0`
|
|
||||||
if let ExprLit(ref lit) = l.node {
|
|
||||||
if let LitInt(0, _) = lit.node {
|
|
||||||
|
|
||||||
// the var must be a single name
|
|
||||||
if let PatIdent(_, ref ident, _) = pat.node {
|
|
||||||
let mut visitor = VarVisitor { cx: cx, var: ident.node.name,
|
|
||||||
indexed: HashSet::new(), nonindex: false };
|
|
||||||
walk_expr(&mut visitor, body);
|
|
||||||
// linting condition: we only indexed one variable
|
|
||||||
if visitor.indexed.len() == 1 {
|
|
||||||
let indexed = visitor.indexed.into_iter().next().expect(
|
|
||||||
"Len was nonzero, but no contents found");
|
|
||||||
if visitor.nonindex {
|
|
||||||
span_lint(cx, NEEDLESS_RANGE_LOOP, expr.span, &format!(
|
|
||||||
"the loop variable `{}` is used to index `{}`. Consider using \
|
|
||||||
`for ({}, item) in {}.iter().enumerate()` or similar iterators",
|
|
||||||
ident.node.name, indexed, ident.node.name, indexed));
|
|
||||||
} else {
|
|
||||||
span_lint(cx, NEEDLESS_RANGE_LOOP, expr.span, &format!(
|
|
||||||
"the loop variable `{}` is only used to index `{}`. \
|
|
||||||
Consider using `for item in &{}` or similar iterators",
|
|
||||||
ident.node.name, indexed, indexed));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this for loop is iterating over a two-sided range...
|
|
||||||
if let ExprRange(Some(ref start_expr), Some(ref stop_expr)) = arg.node {
|
|
||||||
// ...and both sides are compile-time constant integers...
|
|
||||||
if let Some(start_idx @ Constant::ConstantInt(..)) = constant_simple(start_expr) {
|
|
||||||
if let Some(stop_idx @ Constant::ConstantInt(..)) = constant_simple(stop_expr) {
|
|
||||||
// ...and the start index is greater than the stop index,
|
|
||||||
// this loop will never run. This is often confusing for developers
|
|
||||||
// who think that this will iterate from the larger value to the
|
|
||||||
// smaller value.
|
|
||||||
if start_idx > stop_idx {
|
|
||||||
span_help_and_lint(cx, REVERSE_RANGE_LOOP, expr.span,
|
|
||||||
"this range is empty so this for loop will never run",
|
|
||||||
&format!("Consider using `({}..{}).rev()` if you are attempting to \
|
|
||||||
iterate over this range in reverse", stop_idx, start_idx));
|
|
||||||
} else if start_idx == stop_idx {
|
|
||||||
// if they are equal, it's also problematic - this loop
|
|
||||||
// will never run.
|
|
||||||
span_lint(cx, REVERSE_RANGE_LOOP, expr.span,
|
|
||||||
"this range is empty so this for loop will never run");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let ExprMethodCall(ref method, _, ref args) = arg.node {
|
|
||||||
// just the receiver, no arguments
|
|
||||||
if args.len() == 1 {
|
|
||||||
let method_name = method.node;
|
|
||||||
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
|
|
||||||
if method_name.as_str() == "iter" || method_name.as_str() == "iter_mut" {
|
|
||||||
if is_ref_iterable_type(cx, &args[0]) {
|
|
||||||
let object = snippet(cx, args[0].span, "_");
|
|
||||||
span_lint(cx, EXPLICIT_ITER_LOOP, expr.span, &format!(
|
|
||||||
"it is more idiomatic to loop over `&{}{}` instead of `{}.{}()`",
|
|
||||||
if method_name.as_str() == "iter_mut" { "mut " } else { "" },
|
|
||||||
object, object, method_name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check for looping over Iterator::next() which is not what you want
|
|
||||||
else if method_name.as_str() == "next" &&
|
|
||||||
match_trait_method(cx, arg, &["core", "iter", "Iterator"]) {
|
|
||||||
span_lint(cx, ITER_NEXT_LOOP, expr.span,
|
|
||||||
"you are iterating over `Iterator::next()` which is an Option; \
|
|
||||||
this will compile but is probably not what you want");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for variables that are incremented once per loop iteration.
|
|
||||||
let mut visitor = IncrementVisitor { cx: cx, states: HashMap::new(), depth: 0, done: false };
|
|
||||||
walk_expr(&mut visitor, body);
|
|
||||||
|
|
||||||
// For each candidate, check the parent block to see if
|
|
||||||
// it's initialized to zero at the start of the loop.
|
|
||||||
let map = &cx.tcx.map;
|
|
||||||
let parent_scope = map.get_enclosing_scope(expr.id).and_then(|id| map.get_enclosing_scope(id) );
|
|
||||||
if let Some(parent_id) = parent_scope {
|
|
||||||
if let NodeBlock(block) = map.get(parent_id) {
|
|
||||||
for (id, _) in visitor.states.iter().filter( |&(_,v)| *v == VarState::IncrOnce) {
|
|
||||||
let mut visitor2 = InitializeVisitor { cx: cx, end_expr: expr, var_id: id.clone(),
|
|
||||||
state: VarState::IncrOnce, name: None,
|
|
||||||
depth: 0,
|
|
||||||
past_loop: false };
|
|
||||||
walk_block(&mut visitor2, block);
|
|
||||||
|
|
||||||
if visitor2.state == VarState::Warn {
|
|
||||||
if let Some(name) = visitor2.name {
|
|
||||||
span_lint(cx, EXPLICIT_COUNTER_LOOP, expr.span,
|
|
||||||
&format!("the variable `{0}` is used as a loop counter. Consider \
|
|
||||||
using `for ({0}, item) in {1}.enumerate()` \
|
|
||||||
or similar iterators",
|
|
||||||
name, snippet(cx, arg.span, "_")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// check for `loop { if let {} else break }` that could be `while let`
|
// check for `loop { if let {} else break }` that could be `while let`
|
||||||
// (also matches an explicit "match" instead of "if let")
|
// (also matches an explicit "match" instead of "if let")
|
||||||
|
@ -271,6 +162,119 @@ impl LateLintPass for LoopsPass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_for_loop(cx: &LateContext, pat: &Pat, arg: &Expr, body: &Expr, expr: &Expr) {
|
||||||
|
// check for looping over a range and then indexing a sequence with it
|
||||||
|
// -> the iteratee must be a range literal
|
||||||
|
if let ExprRange(Some(ref l), _) = arg.node {
|
||||||
|
// Range should start with `0`
|
||||||
|
if let ExprLit(ref lit) = l.node {
|
||||||
|
if let LitInt(0, _) = lit.node {
|
||||||
|
|
||||||
|
// the var must be a single name
|
||||||
|
if let PatIdent(_, ref ident, _) = pat.node {
|
||||||
|
let mut visitor = VarVisitor { cx: cx, var: ident.node.name,
|
||||||
|
indexed: HashSet::new(), nonindex: false };
|
||||||
|
walk_expr(&mut visitor, body);
|
||||||
|
// linting condition: we only indexed one variable
|
||||||
|
if visitor.indexed.len() == 1 {
|
||||||
|
let indexed = visitor.indexed.into_iter().next().expect(
|
||||||
|
"Len was nonzero, but no contents found");
|
||||||
|
if visitor.nonindex {
|
||||||
|
span_lint(cx, NEEDLESS_RANGE_LOOP, expr.span, &format!(
|
||||||
|
"the loop variable `{}` is used to index `{}`. Consider using \
|
||||||
|
`for ({}, item) in {}.iter().enumerate()` or similar iterators",
|
||||||
|
ident.node.name, indexed, ident.node.name, indexed));
|
||||||
|
} else {
|
||||||
|
span_lint(cx, NEEDLESS_RANGE_LOOP, expr.span, &format!(
|
||||||
|
"the loop variable `{}` is only used to index `{}`. \
|
||||||
|
Consider using `for item in &{}` or similar iterators",
|
||||||
|
ident.node.name, indexed, indexed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this for loop is iterating over a two-sided range...
|
||||||
|
if let ExprRange(Some(ref start_expr), Some(ref stop_expr)) = arg.node {
|
||||||
|
// ...and both sides are compile-time constant integers...
|
||||||
|
if let Some(start_idx @ Constant::ConstantInt(..)) = constant_simple(start_expr) {
|
||||||
|
if let Some(stop_idx @ Constant::ConstantInt(..)) = constant_simple(stop_expr) {
|
||||||
|
// ...and the start index is greater than the stop index,
|
||||||
|
// this loop will never run. This is often confusing for developers
|
||||||
|
// who think that this will iterate from the larger value to the
|
||||||
|
// smaller value.
|
||||||
|
if start_idx > stop_idx {
|
||||||
|
span_help_and_lint(cx, REVERSE_RANGE_LOOP, expr.span,
|
||||||
|
"this range is empty so this for loop will never run",
|
||||||
|
&format!("Consider using `({}..{}).rev()` if you are attempting to \
|
||||||
|
iterate over this range in reverse", stop_idx, start_idx));
|
||||||
|
} else if start_idx == stop_idx {
|
||||||
|
// if they are equal, it's also problematic - this loop
|
||||||
|
// will never run.
|
||||||
|
span_lint(cx, REVERSE_RANGE_LOOP, expr.span,
|
||||||
|
"this range is empty so this for loop will never run");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ExprMethodCall(ref method, _, ref args) = arg.node {
|
||||||
|
// just the receiver, no arguments
|
||||||
|
if args.len() == 1 {
|
||||||
|
let method_name = method.node;
|
||||||
|
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
|
||||||
|
if method_name.as_str() == "iter" || method_name.as_str() == "iter_mut" {
|
||||||
|
if is_ref_iterable_type(cx, &args[0]) {
|
||||||
|
let object = snippet(cx, args[0].span, "_");
|
||||||
|
span_lint(cx, EXPLICIT_ITER_LOOP, expr.span, &format!(
|
||||||
|
"it is more idiomatic to loop over `&{}{}` instead of `{}.{}()`",
|
||||||
|
if method_name.as_str() == "iter_mut" { "mut " } else { "" },
|
||||||
|
object, object, method_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check for looping over Iterator::next() which is not what you want
|
||||||
|
else if method_name.as_str() == "next" &&
|
||||||
|
match_trait_method(cx, arg, &["core", "iter", "Iterator"]) {
|
||||||
|
span_lint(cx, ITER_NEXT_LOOP, expr.span,
|
||||||
|
"you are iterating over `Iterator::next()` which is an Option; \
|
||||||
|
this will compile but is probably not what you want");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for variables that are incremented once per loop iteration.
|
||||||
|
let mut visitor = IncrementVisitor { cx: cx, states: HashMap::new(), depth: 0, done: false };
|
||||||
|
walk_expr(&mut visitor, body);
|
||||||
|
|
||||||
|
// For each candidate, check the parent block to see if
|
||||||
|
// it's initialized to zero at the start of the loop.
|
||||||
|
let map = &cx.tcx.map;
|
||||||
|
let parent_scope = map.get_enclosing_scope(expr.id).and_then(|id| map.get_enclosing_scope(id) );
|
||||||
|
if let Some(parent_id) = parent_scope {
|
||||||
|
if let NodeBlock(block) = map.get(parent_id) {
|
||||||
|
for (id, _) in visitor.states.iter().filter( |&(_,v)| *v == VarState::IncrOnce) {
|
||||||
|
let mut visitor2 = InitializeVisitor { cx: cx, end_expr: expr, var_id: id.clone(),
|
||||||
|
state: VarState::IncrOnce, name: None,
|
||||||
|
depth: 0,
|
||||||
|
past_loop: false };
|
||||||
|
walk_block(&mut visitor2, block);
|
||||||
|
|
||||||
|
if visitor2.state == VarState::Warn {
|
||||||
|
if let Some(name) = visitor2.name {
|
||||||
|
span_lint(cx, EXPLICIT_COUNTER_LOOP, expr.span,
|
||||||
|
&format!("the variable `{0}` is used as a loop counter. Consider \
|
||||||
|
using `for ({0}, item) in {1}.enumerate()` \
|
||||||
|
or similar iterators",
|
||||||
|
name, snippet(cx, arg.span, "_")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Recover the essential nodes of a desugared for loop:
|
/// Recover the essential nodes of a desugared for loop:
|
||||||
/// `for pat in arg { body }` becomes `(pat, arg, body)`.
|
/// `for pat in arg { body }` becomes `(pat, arg, body)`.
|
||||||
fn recover_for_loop(expr: &Expr) -> Option<(&Pat, &Expr, &Expr)> {
|
fn recover_for_loop(expr: &Expr) -> Option<(&Pat, &Expr, &Expr)> {
|
||||||
|
|
66
src/utils.rs
66
src/utils.rs
|
@ -9,6 +9,9 @@ use std::borrow::Cow;
|
||||||
use syntax::ast::Lit_::*;
|
use syntax::ast::Lit_::*;
|
||||||
use syntax::ast;
|
use syntax::ast;
|
||||||
|
|
||||||
|
use rustc::session::Session;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
// module DefPaths for certain structs/enums we check for
|
// module DefPaths for certain structs/enums we check for
|
||||||
pub const OPTION_PATH: [&'static str; 3] = ["core", "option", "Option"];
|
pub const OPTION_PATH: [&'static str; 3] = ["core", "option", "Option"];
|
||||||
pub const RESULT_PATH: [&'static str; 3] = ["core", "result", "Result"];
|
pub const RESULT_PATH: [&'static str; 3] = ["core", "result", "Result"];
|
||||||
|
@ -65,8 +68,8 @@ macro_rules! if_let_chain {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns true this expn_info was expanded by any macro
|
/// returns true if this expn_info was expanded by any macro
|
||||||
pub fn in_macro(cx: &LateContext, span: Span) -> bool {
|
pub fn in_macro<T: LintContext>(cx: &T, span: Span) -> bool {
|
||||||
cx.sess().codemap().with_expn_info(span.expn_id,
|
cx.sess().codemap().with_expn_info(span.expn_id,
|
||||||
|info| info.is_some())
|
|info| info.is_some())
|
||||||
}
|
}
|
||||||
|
@ -397,3 +400,62 @@ macro_rules! if_let_chain {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct LimitStack {
|
||||||
|
stack: Vec<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for LimitStack {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
assert_eq!(self.stack.len(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LimitStack {
|
||||||
|
pub fn new(limit: u64) -> LimitStack {
|
||||||
|
LimitStack {
|
||||||
|
stack: vec![limit],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn limit(&self) -> u64 {
|
||||||
|
*self.stack.last().expect("there should always be a value in the stack")
|
||||||
|
}
|
||||||
|
pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
|
||||||
|
let stack = &mut self.stack;
|
||||||
|
parse_attrs(
|
||||||
|
sess,
|
||||||
|
attrs,
|
||||||
|
name,
|
||||||
|
|val| stack.push(val),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
|
||||||
|
let stack = &mut self.stack;
|
||||||
|
parse_attrs(
|
||||||
|
sess,
|
||||||
|
attrs,
|
||||||
|
name,
|
||||||
|
|val| assert_eq!(stack.pop(), Some(val)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
|
||||||
|
for attr in attrs {
|
||||||
|
let attr = &attr.node;
|
||||||
|
if attr.is_sugared_doc { continue; }
|
||||||
|
if let ast::MetaNameValue(ref key, ref value) = attr.value.node {
|
||||||
|
if *key == name {
|
||||||
|
if let LitStr(ref s, _) = value.node {
|
||||||
|
if let Ok(value) = FromStr::from_str(s) {
|
||||||
|
f(value)
|
||||||
|
} else {
|
||||||
|
sess.span_err(value.span, "not a number");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
181
tests/compile-fail/cyclomatic_complexity.rs
Normal file
181
tests/compile-fail/cyclomatic_complexity.rs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
#![feature(plugin, custom_attribute)]
|
||||||
|
#![plugin(clippy)]
|
||||||
|
#![deny(clippy)]
|
||||||
|
#![deny(cyclomatic_complexity)]
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
fn main() { //~ ERROR: The function has a cyclomatic complexity of 28.
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
println!("a");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cyclomatic_complexity = "0"]
|
||||||
|
fn kaboom() { //~ ERROR: The function has a cyclomatic complexity of 6
|
||||||
|
let n = 0;
|
||||||
|
'a: for i in 0..20 {
|
||||||
|
'b: for j in i..20 {
|
||||||
|
for k in j..20 {
|
||||||
|
if k == 5 {
|
||||||
|
break 'b;
|
||||||
|
}
|
||||||
|
if j == 3 && k == 6 {
|
||||||
|
continue 'a;
|
||||||
|
}
|
||||||
|
if k == j {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
println!("bake");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("cake");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bloo() {
|
||||||
|
match 42 {
|
||||||
|
0 => println!("hi"),
|
||||||
|
1 => println!("hai"),
|
||||||
|
2 => println!("hey"),
|
||||||
|
3 => println!("hallo"),
|
||||||
|
4 => println!("hello"),
|
||||||
|
5 => println!("salut"),
|
||||||
|
6 => println!("good morning"),
|
||||||
|
7 => println!("good evening"),
|
||||||
|
8 => println!("good afternoon"),
|
||||||
|
9 => println!("good night"),
|
||||||
|
10 => println!("bonjour"),
|
||||||
|
11 => println!("hej"),
|
||||||
|
12 => println!("hej hej"),
|
||||||
|
13 => println!("greetings earthling"),
|
||||||
|
14 => println!("take us to you leader"),
|
||||||
|
15 | 17 | 19 | 21 | 23 | 25 | 27 | 29 | 31 | 33 => println!("take us to you leader"),
|
||||||
|
35 | 37 | 39 | 41 | 43 | 45 | 47 | 49 | 51 | 53 => println!("there is no undefined behavior"),
|
||||||
|
55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 73 => println!("I know borrow-fu"),
|
||||||
|
_ => println!("bye"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cyclomatic_complexity = "0"]
|
||||||
|
fn baa() { //~ ERROR: The function has a cyclomatic complexity of 2
|
||||||
|
let x = || match 99 {
|
||||||
|
0 => true,
|
||||||
|
1 => false,
|
||||||
|
2 => true,
|
||||||
|
4 => true,
|
||||||
|
6 => true,
|
||||||
|
9 => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if x() {
|
||||||
|
println!("x");
|
||||||
|
} else {
|
||||||
|
println!("not x");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cyclomatic_complexity = "0"]
|
||||||
|
fn bar() { //~ ERROR: The function has a cyclomatic complexity of 2
|
||||||
|
match 99 {
|
||||||
|
0 => println!("hi"),
|
||||||
|
_ => println!("bye"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cyclomatic_complexity = "0"]
|
||||||
|
fn barr() { //~ ERROR: The function has a cyclomatic complexity of 2
|
||||||
|
match 99 {
|
||||||
|
0 => println!("hi"),
|
||||||
|
1 => println!("bla"),
|
||||||
|
2 | 3 => println!("blub"),
|
||||||
|
_ => println!("bye"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Void {}
|
||||||
|
|
||||||
|
#[cyclomatic_complexity = "0"]
|
||||||
|
fn void(void: Void) { //~ ERROR: The function has a cyclomatic complexity of 1
|
||||||
|
if true {
|
||||||
|
match void {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ impl Unrelated {
|
||||||
|
|
||||||
#[deny(needless_range_loop, explicit_iter_loop, iter_next_loop, reverse_range_loop, explicit_counter_loop)]
|
#[deny(needless_range_loop, explicit_iter_loop, iter_next_loop, reverse_range_loop, explicit_counter_loop)]
|
||||||
#[deny(unused_collect)]
|
#[deny(unused_collect)]
|
||||||
#[allow(linkedlist,shadow_unrelated,unnecessary_mut_passed)]
|
#[allow(linkedlist,shadow_unrelated,unnecessary_mut_passed, cyclomatic_complexity)]
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut vec = vec![1, 2, 3, 4];
|
let mut vec = vec![1, 2, 3, 4];
|
||||||
let vec2 = vec![1, 2, 3, 4];
|
let vec2 = vec![1, 2, 3, 4];
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#![plugin(clippy)]
|
#![plugin(clippy)]
|
||||||
|
|
||||||
#![deny(while_let_loop, empty_loop, while_let_on_iterator)]
|
#![deny(while_let_loop, empty_loop, while_let_on_iterator)]
|
||||||
#![allow(dead_code, unused)]
|
#![allow(dead_code, unused, cyclomatic_complexity)]
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let y = Some(true);
|
let y = Some(true);
|
||||||
|
|
Loading…
Reference in a new issue