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,16 +126,17 @@ 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> {
|
||||||
let mut allowed_lts = HashSet::new();
|
let mut allowed_lts = HashSet::new();
|
||||||
|
|
210
src/loops.rs
210
src/loops.rs
|
@ -56,6 +56,113 @@ 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_loop(cx, pat, arg, body, expr);
|
||||||
|
}
|
||||||
|
// check for `loop { if let {} else break }` that could be `while let`
|
||||||
|
// (also matches an explicit "match" instead of "if let")
|
||||||
|
// (even if the "match" or "if let" is used for declaration)
|
||||||
|
if let ExprLoop(ref block, _) = expr.node {
|
||||||
|
// also check for empty `loop {}` statements
|
||||||
|
if block.stmts.is_empty() && block.expr.is_none() {
|
||||||
|
span_lint(cx, EMPTY_LOOP, expr.span,
|
||||||
|
"empty `loop {}` detected. You may want to either \
|
||||||
|
use `panic!()` or add `std::thread::sleep(..);` to \
|
||||||
|
the loop body.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract the expression from the first statement (if any) in a block
|
||||||
|
let inner_stmt_expr = extract_expr_from_first_stmt(block);
|
||||||
|
// extract the first expression (if any) from the block
|
||||||
|
let inner_expr = extract_first_expr(block);
|
||||||
|
let (extracted, collect_expr) = match inner_stmt_expr {
|
||||||
|
Some(_) => (inner_stmt_expr, true), // check if an expression exists in the first statement
|
||||||
|
None => (inner_expr, false), // if not, let's go for the first expression in the block
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(inner) = extracted {
|
||||||
|
if let ExprMatch(ref matchexpr, ref arms, ref source) = inner.node {
|
||||||
|
// collect the remaining statements below the match
|
||||||
|
let mut other_stuff = block.stmts
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|stmt| {
|
||||||
|
format!("{}", snippet(cx, stmt.span, ".."))
|
||||||
|
}).collect::<Vec<String>>();
|
||||||
|
if collect_expr { // if we have a statement which has a match,
|
||||||
|
match block.expr { // then collect the expression (without semicolon) below it
|
||||||
|
Some(ref expr) => other_stuff.push(format!("{}", snippet(cx, expr.span, ".."))),
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure "if let" compatible match structure
|
||||||
|
match *source {
|
||||||
|
MatchSource::Normal | MatchSource::IfLetDesugar{..} => if
|
||||||
|
arms.len() == 2 &&
|
||||||
|
arms[0].pats.len() == 1 && arms[0].guard.is_none() &&
|
||||||
|
arms[1].pats.len() == 1 && arms[1].guard.is_none() &&
|
||||||
|
// finally, check for "break" in the second clause
|
||||||
|
is_break_expr(&arms[1].body)
|
||||||
|
{
|
||||||
|
if in_external_macro(cx, expr.span) { return; }
|
||||||
|
let loop_body = match inner_stmt_expr {
|
||||||
|
// FIXME: should probably be an ellipsis
|
||||||
|
// tabbing and newline is probably a bad idea, especially for large blocks
|
||||||
|
Some(_) => Cow::Owned(format!("{{\n {}\n}}", other_stuff.join("\n "))),
|
||||||
|
None => expr_block(cx, &arms[0].body,
|
||||||
|
Some(other_stuff.join("\n ")), ".."),
|
||||||
|
};
|
||||||
|
span_help_and_lint(cx, WHILE_LET_LOOP, expr.span,
|
||||||
|
"this loop could be written as a `while let` loop",
|
||||||
|
&format!("try\nwhile let {} = {} {}",
|
||||||
|
snippet(cx, arms[0].pats[0].span, ".."),
|
||||||
|
snippet(cx, matchexpr.span, ".."),
|
||||||
|
loop_body));
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let ExprMatch(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.node {
|
||||||
|
let pat = &arms[0].pats[0].node;
|
||||||
|
if let (&PatEnum(ref path, Some(ref pat_args)),
|
||||||
|
&ExprMethodCall(method_name, _, ref method_args)) =
|
||||||
|
(pat, &match_expr.node) {
|
||||||
|
let iter_expr = &method_args[0];
|
||||||
|
if let Some(lhs_constructor) = path.segments.last() {
|
||||||
|
if method_name.node.as_str() == "next" &&
|
||||||
|
match_trait_method(cx, match_expr, &["core", "iter", "Iterator"]) &&
|
||||||
|
lhs_constructor.identifier.name.as_str() == "Some" &&
|
||||||
|
!is_iterator_used_after_while_let(cx, iter_expr) {
|
||||||
|
let iterator = snippet(cx, method_args[0].span, "_");
|
||||||
|
let loop_var = snippet(cx, pat_args[0].span, "_");
|
||||||
|
span_help_and_lint(cx, WHILE_LET_ON_ITERATOR, expr.span,
|
||||||
|
"this loop could be written as a `for` loop",
|
||||||
|
&format!("try\nfor {} in {} {{...}}",
|
||||||
|
loop_var,
|
||||||
|
iterator));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_stmt(&mut self, cx: &LateContext, stmt: &Stmt) {
|
||||||
|
if let StmtSemi(ref expr, _) = stmt.node {
|
||||||
|
if let ExprMethodCall(ref method, _, ref args) = expr.node {
|
||||||
|
if args.len() == 1 && method.node.as_str() == "collect" &&
|
||||||
|
match_trait_method(cx, expr, &["core", "iter", "Iterator"]) {
|
||||||
|
span_lint(cx, UNUSED_COLLECT, expr.span, &format!(
|
||||||
|
"you are collect()ing an iterator and throwing away the result. \
|
||||||
|
Consider using an explicit for loop to exhaust the iterator"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// check for looping over a range and then indexing a sequence with it
|
||||||
// -> the iteratee must be a range literal
|
// -> the iteratee must be a range literal
|
||||||
if let ExprRange(Some(ref l), _) = arg.node {
|
if let ExprRange(Some(ref l), _) = arg.node {
|
||||||
|
@ -167,109 +274,6 @@ impl LateLintPass for LoopsPass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check for `loop { if let {} else break }` that could be `while let`
|
|
||||||
// (also matches an explicit "match" instead of "if let")
|
|
||||||
// (even if the "match" or "if let" is used for declaration)
|
|
||||||
if let ExprLoop(ref block, _) = expr.node {
|
|
||||||
// also check for empty `loop {}` statements
|
|
||||||
if block.stmts.is_empty() && block.expr.is_none() {
|
|
||||||
span_lint(cx, EMPTY_LOOP, expr.span,
|
|
||||||
"empty `loop {}` detected. You may want to either \
|
|
||||||
use `panic!()` or add `std::thread::sleep(..);` to \
|
|
||||||
the loop body.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract the expression from the first statement (if any) in a block
|
|
||||||
let inner_stmt_expr = extract_expr_from_first_stmt(block);
|
|
||||||
// extract the first expression (if any) from the block
|
|
||||||
let inner_expr = extract_first_expr(block);
|
|
||||||
let (extracted, collect_expr) = match inner_stmt_expr {
|
|
||||||
Some(_) => (inner_stmt_expr, true), // check if an expression exists in the first statement
|
|
||||||
None => (inner_expr, false), // if not, let's go for the first expression in the block
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(inner) = extracted {
|
|
||||||
if let ExprMatch(ref matchexpr, ref arms, ref source) = inner.node {
|
|
||||||
// collect the remaining statements below the match
|
|
||||||
let mut other_stuff = block.stmts
|
|
||||||
.iter()
|
|
||||||
.skip(1)
|
|
||||||
.map(|stmt| {
|
|
||||||
format!("{}", snippet(cx, stmt.span, ".."))
|
|
||||||
}).collect::<Vec<String>>();
|
|
||||||
if collect_expr { // if we have a statement which has a match,
|
|
||||||
match block.expr { // then collect the expression (without semicolon) below it
|
|
||||||
Some(ref expr) => other_stuff.push(format!("{}", snippet(cx, expr.span, ".."))),
|
|
||||||
None => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure "if let" compatible match structure
|
|
||||||
match *source {
|
|
||||||
MatchSource::Normal | MatchSource::IfLetDesugar{..} => if
|
|
||||||
arms.len() == 2 &&
|
|
||||||
arms[0].pats.len() == 1 && arms[0].guard.is_none() &&
|
|
||||||
arms[1].pats.len() == 1 && arms[1].guard.is_none() &&
|
|
||||||
// finally, check for "break" in the second clause
|
|
||||||
is_break_expr(&arms[1].body)
|
|
||||||
{
|
|
||||||
if in_external_macro(cx, expr.span) { return; }
|
|
||||||
let loop_body = match inner_stmt_expr {
|
|
||||||
// FIXME: should probably be an ellipsis
|
|
||||||
// tabbing and newline is probably a bad idea, especially for large blocks
|
|
||||||
Some(_) => Cow::Owned(format!("{{\n {}\n}}", other_stuff.join("\n "))),
|
|
||||||
None => expr_block(cx, &arms[0].body,
|
|
||||||
Some(other_stuff.join("\n ")), ".."),
|
|
||||||
};
|
|
||||||
span_help_and_lint(cx, WHILE_LET_LOOP, expr.span,
|
|
||||||
"this loop could be written as a `while let` loop",
|
|
||||||
&format!("try\nwhile let {} = {} {}",
|
|
||||||
snippet(cx, arms[0].pats[0].span, ".."),
|
|
||||||
snippet(cx, matchexpr.span, ".."),
|
|
||||||
loop_body));
|
|
||||||
},
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let ExprMatch(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.node {
|
|
||||||
let pat = &arms[0].pats[0].node;
|
|
||||||
if let (&PatEnum(ref path, Some(ref pat_args)),
|
|
||||||
&ExprMethodCall(method_name, _, ref method_args)) =
|
|
||||||
(pat, &match_expr.node) {
|
|
||||||
let iter_expr = &method_args[0];
|
|
||||||
if let Some(lhs_constructor) = path.segments.last() {
|
|
||||||
if method_name.node.as_str() == "next" &&
|
|
||||||
match_trait_method(cx, match_expr, &["core", "iter", "Iterator"]) &&
|
|
||||||
lhs_constructor.identifier.name.as_str() == "Some" &&
|
|
||||||
!is_iterator_used_after_while_let(cx, iter_expr) {
|
|
||||||
let iterator = snippet(cx, method_args[0].span, "_");
|
|
||||||
let loop_var = snippet(cx, pat_args[0].span, "_");
|
|
||||||
span_help_and_lint(cx, WHILE_LET_ON_ITERATOR, expr.span,
|
|
||||||
"this loop could be written as a `for` loop",
|
|
||||||
&format!("try\nfor {} in {} {{...}}",
|
|
||||||
loop_var,
|
|
||||||
iterator));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_stmt(&mut self, cx: &LateContext, stmt: &Stmt) {
|
|
||||||
if let StmtSemi(ref expr, _) = stmt.node {
|
|
||||||
if let ExprMethodCall(ref method, _, ref args) = expr.node {
|
|
||||||
if args.len() == 1 && method.node.as_str() == "collect" &&
|
|
||||||
match_trait_method(cx, expr, &["core", "iter", "Iterator"]) {
|
|
||||||
span_lint(cx, UNUSED_COLLECT, expr.span, &format!(
|
|
||||||
"you are collect()ing an iterator and throwing away the result. \
|
|
||||||
Consider using an explicit for loop to exhaust the iterator"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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)`.
|
||||||
|
|
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