use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use std::fmt;
use crate::checker::Checker;
use crate::*;
#[derive(Debug, Clone)]
pub struct Stats {
cyclomatic: f64,
n: usize,
}
impl Default for Stats {
fn default() -> Self {
Self {
cyclomatic: 1.,
n: 1,
}
}
}
impl Serialize for Stats {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut st = serializer.serialize_struct("cyclomatic", 2)?;
st.serialize_field("sum", &self.cyclomatic())?;
st.serialize_field("average", &self.cyclomatic_average())?;
st.end()
}
}
impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"sum: {}, average: {}",
self.cyclomatic(),
self.cyclomatic_average()
)
}
}
impl Stats {
pub fn merge(&mut self, other: &Stats) {
self.cyclomatic += other.cyclomatic;
self.n += other.n;
}
pub fn cyclomatic(&self) -> f64 {
self.cyclomatic
}
pub fn cyclomatic_average(&self) -> f64 {
self.cyclomatic() / self.n as f64
}
}
#[doc(hidden)]
pub trait Cyclomatic
where
Self: Checker,
{
fn compute(_node: &Node, _stats: &mut Stats) {}
}
impl Cyclomatic for PythonCode {
fn compute(node: &Node, stats: &mut Stats) {
use Python::*;
match node.object().kind_id().into() {
If | Elif | For | While | Except | With | Assert | And | Or => {
stats.cyclomatic += 1.;
}
Else => {
if has_ancestors!(node, ForStatement | WhileStatement, ElseClause) {
stats.cyclomatic += 1.;
}
}
_ => {}
}
}
}
impl Cyclomatic for MozjsCode {
fn compute(node: &Node, stats: &mut Stats) {
use Mozjs::*;
match node.object().kind_id().into() {
If | For | While | Case | Catch | TernaryExpression | AMPAMP | PIPEPIPE => {
stats.cyclomatic += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for JavascriptCode {
fn compute(node: &Node, stats: &mut Stats) {
use Javascript::*;
match node.object().kind_id().into() {
If | For | While | Case | Catch | TernaryExpression | AMPAMP | PIPEPIPE => {
stats.cyclomatic += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for TypescriptCode {
fn compute(node: &Node, stats: &mut Stats) {
use Typescript::*;
match node.object().kind_id().into() {
If | For | While | Case | Catch | TernaryExpression | AMPAMP | PIPEPIPE => {
stats.cyclomatic += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for TsxCode {
fn compute(node: &Node, stats: &mut Stats) {
use Tsx::*;
match node.object().kind_id().into() {
If | For | While | Case | Catch | TernaryExpression | AMPAMP | PIPEPIPE => {
stats.cyclomatic += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for RustCode {
fn compute(node: &Node, stats: &mut Stats) {
use Rust::*;
match node.object().kind_id().into() {
If | For | While | Loop | MatchArm | MatchArm2 | QMARK | AMPAMP | PIPEPIPE => {
stats.cyclomatic += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for CppCode {
fn compute(node: &Node, stats: &mut Stats) {
use Cpp::*;
match node.object().kind_id().into() {
If | For | While | Case | Catch | ConditionalExpression | AMPAMP | PIPEPIPE => {
stats.cyclomatic += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for PreprocCode {}
impl Cyclomatic for CcommentCode {}
impl Cyclomatic for JavaCode {}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
#[test]
fn python_simple_function() {
check_metrics!(
"def f(a, b): # +2 (+1 unit space)
if a and b: # +2 (+1 and)
return 1
if c and d: # +2 (+1 and)
return 1",
"foo.py",
PythonParser,
cyclomatic,
[(cyclomatic, 6, usize)],
[
(cyclomatic_average, 3.0) ]
);
}
#[test]
fn python_1_level_nesting() {
check_metrics!(
"def f(a, b): # +2 (+1 unit space)
if a: # +1
for i in range(b): # +1
return 1",
"foo.py",
PythonParser,
cyclomatic,
[(cyclomatic, 4, usize)],
[
(cyclomatic_average, 2.0) ]
);
}
#[test]
fn rust_1_level_nesting() {
check_metrics!(
"fn f() { // +2 (+1 unit space)
if true { // +1
match true {
true => println!(\"test\"), // +1
false => println!(\"test\"), // +1
}
}
}",
"foo.rs",
RustParser,
cyclomatic,
[(cyclomatic, 5, usize)],
[
(cyclomatic_average, 2.5) ]
);
}
#[test]
fn c_switch() {
check_metrics!(
"void f() { // +2 (+1 unit space)
switch (1) {
case 1: // +1
printf(\"one\");
break;
case 2: // +1
printf(\"two\");
break;
case 3: // +1
printf(\"three\");
break;
default:
printf(\"all\");
break;
}
}",
"foo.c",
CppParser,
cyclomatic,
[(cyclomatic, 5, usize)],
[
(cyclomatic_average, 2.5) ]
);
}
#[test]
fn c_real_function() {
check_metrics!(
"int sumOfPrimes(int max) { // +2 (+1 unit space)
int total = 0;
OUT: for (int i = 1; i <= max; ++i) { // +1
for (int j = 2; j < i; ++j) { // +1
if (i % j == 0) { // +1
continue OUT;
}
}
total += i;
}
return total;
}",
"foo.c",
CppParser,
cyclomatic,
[(cyclomatic, 5, usize)],
[
(cyclomatic_average, 2.5) ]
);
}
}