Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ control flow of a program.
- LLOC: it counts the number of logical lines (instructions) contained in a source file.
- HALSTEAD: it is a suite that provides a series of information, such as the effort required to maintain the analyzed code, the size in bits to store the program, the difficulty to understand the code, an estimate of the number of bugs present in the codebase, and an estimate of the time needed to implement the software.
- MI: it is a suite that allows to evaluate the maintainability of a software.
- NOM: it counts the number of functions and closures in a file/trait/class.
- NEXITS: it counts the number of possible exit points from a method/function.
- NARGS: it counts the number of arguments of a function/method.

Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub use crate::mi::*;
pub mod halstead;
pub use crate::halstead::*;

pub mod nom;
pub use crate::nom::*;

pub mod fn_args;
pub use crate::fn_args::*;

Expand Down
14 changes: 14 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,17 @@ macro_rules! color {
)?;
};
}

#[macro_export]
macro_rules! check_metrics {
($source: expr, $file: expr, $parser: ident, $metric: ident,
[ $( ( $func: ident, $true_value: expr $(,$type: ty)? )$(,)* )* ]) => {
{
let path = PathBuf::from($file);
let parser = $parser::new($source.to_string().into_bytes(), &path, None);
let func_space = metrics(&parser, &path).unwrap();

$( assert_eq!(func_space.metrics.$metric.$func() $(as $type)?, $true_value); )*
}
};
}
28 changes: 28 additions & 0 deletions src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::getter::Getter;
use crate::halstead::{self, Halstead};
use crate::loc::{self, Loc};
use crate::mi::{self, Mi};
use crate::nom::{self, Nom};
use crate::tools::write_file;
use crate::traits::*;

Expand All @@ -24,6 +25,7 @@ pub struct CodeMetrics<'a> {
pub cyclomatic: cyclomatic::Stats,
pub halstead: halstead::Stats<'a>,
pub loc: loc::Stats,
pub nom: nom::Stats,
pub mi: mi::Stats,
pub nargs: fn_args::Stats,
pub nexits: exit::Stats,
Expand All @@ -38,6 +40,7 @@ impl<'a> Serialize for CodeMetrics<'a> {
st.serialize_field("cyclomatic", &self.cyclomatic)?;
st.serialize_field("halstead", &self.halstead)?;
st.serialize_field("loc", &self.loc)?;
st.serialize_field("nom", &self.nom)?;
st.serialize_field("mi", &self.mi)?;
st.serialize_field("nargs", &self.nargs)?;
st.serialize_field("nexits", &self.nexits)?;
Expand All @@ -51,6 +54,7 @@ impl<'a> Default for CodeMetrics<'a> {
cyclomatic: cyclomatic::Stats::default(),
halstead: halstead::Stats::default(),
loc: loc::Stats::default(),
nom: nom::Stats::default(),
mi: mi::Stats::default(),
nargs: fn_args::Stats::default(),
nexits: exit::Stats::default(),
Expand All @@ -63,6 +67,7 @@ impl<'a> fmt::Display for CodeMetrics<'a> {
writeln!(f, "{}", self.cyclomatic)?;
writeln!(f, "{}", self.halstead)?;
writeln!(f, "{}", self.loc)?;
writeln!(f, "{}", self.nom)?;
writeln!(f, "{}", self.mi)?;
writeln!(f, "{}", self.nargs)?;
write!(f, "{}", self.nexits)
Expand All @@ -74,6 +79,7 @@ impl<'a> CodeMetrics<'a> {
self.cyclomatic.merge(&other.cyclomatic);
self.halstead.merge(&other.halstead);
self.loc.merge(&other.loc);
self.nom.merge(&other.nom);
self.mi.merge(&other.mi);
self.nargs.merge(&other.nargs);
self.nexits.merge(&other.nexits);
Expand Down Expand Up @@ -190,6 +196,7 @@ impl<'a> FuncSpace<'a> {
Self::dump_nexits(&metrics.nexits, &prefix, false, stdout)?;
Self::dump_halstead(&metrics.halstead, &prefix, false, stdout)?;
Self::dump_loc(&metrics.loc, &prefix, false, stdout)?;
Self::dump_nom(&metrics.nom, &prefix, false, stdout)?;
Self::dump_mi(&metrics.mi, &prefix, true, stdout)
}

Expand Down Expand Up @@ -270,6 +277,26 @@ impl<'a> FuncSpace<'a> {
Self::dump_value("cloc", stats.cloc(), &prefix, true, stdout)
}

fn dump_nom(
stats: &nom::Stats,
prefix: &str,
last: bool,
stdout: &mut StandardStreamLock,
) -> std::io::Result<()> {
let (pref_child, pref) = if last { (" ", "`- ") } else { ("| ", "|- ") };

color!(stdout, Blue);
write!(stdout, "{}{}", prefix, pref)?;

color!(stdout, Green, true);
writeln!(stdout, "nom")?;

let prefix = format!("{}{}", prefix, pref_child);
Self::dump_value("functions", stats.functions(), &prefix, false, stdout)?;
Self::dump_value("closures", stats.closures(), &prefix, false, stdout)?;
Self::dump_value("total", stats.total(), &prefix, true, stdout)
}

fn dump_mi(
stats: &mi::Stats,
prefix: &str,
Expand Down Expand Up @@ -427,6 +454,7 @@ pub fn metrics<'a, T: TSParserTrait>(parser: &'a T, path: &'a PathBuf) -> Option
T::Cyclomatic::compute(&node, &mut last.metrics.cyclomatic);
T::Halstead::compute(&node, code, &mut last.metrics.halstead);
T::Loc::compute(&node, code, &mut last.metrics.loc, func_space, unit);
T::Nom::compute(&node, &mut last.metrics.nom);
T::Mi::compute(
&node,
&last.metrics.loc,
Expand Down
267 changes: 267 additions & 0 deletions src/nom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use std::fmt;
use tree_sitter::Node;

use crate::checker::Checker;

use crate::*;

#[derive(Default, Debug)]
pub struct Stats {
functions: usize,
closures: usize,
}

impl Serialize for Stats {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut st = serializer.serialize_struct("nom", 3)?;
st.serialize_field("functions", &self.functions())?;
st.serialize_field("closures", &self.closures())?;
st.serialize_field("total", &self.total())?;
st.end()
}
}

impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"functions: {}, \
closures: {}, \
total: {}",
self.functions(),
self.closures(),
self.total(),
)
}
}

impl Stats {
pub fn merge(&mut self, other: &Stats) {
self.functions += other.functions;
self.closures += other.closures;
}

#[inline(always)]
pub fn functions(&self) -> f64 {
// Only function definitions are considered, not general declarations
self.functions as f64
}

#[inline(always)]
pub fn closures(&self) -> f64 {
self.closures as f64
}

#[inline(always)]
pub fn total(&self) -> f64 {
self.functions() + self.closures()
}
}

pub trait Nom
where
Self: Checker,
{
fn compute(_node: &Node, _stats: &mut Stats) {}
}

impl Nom for PythonCode {
fn compute(node: &Node, stats: &mut Stats) {
use Python::*;

match node.kind_id().into() {
FunctionDefinition => {
stats.functions += 1;
}
Lambda => {
stats.closures += 1;
}
_ => {}
}
}
}

impl Nom for MozjsCode {
fn compute(node: &Node, stats: &mut Stats) {
use Mozjs::*;

match node.kind_id().into() {
Function | FunctionDeclaration | MethodDefinition => {
stats.functions += 1;
}
GeneratorFunction | GeneratorFunctionDeclaration | ArrowFunction => {
stats.closures += 1;
}
_ => {}
}
}
}

impl Nom for JavascriptCode {
fn compute(node: &Node, stats: &mut Stats) {
use Javascript::*;

match node.kind_id().into() {
Function | FunctionDeclaration | MethodDefinition => {
stats.functions += 1;
}
GeneratorFunction | GeneratorFunctionDeclaration | ArrowFunction => {
stats.closures += 1;
}
_ => {}
}
}
}

impl Nom for TypescriptCode {
fn compute(node: &Node, stats: &mut Stats) {
use Typescript::*;

match node.kind_id().into() {
Function | FunctionDeclaration | MethodDefinition => {
stats.functions += 1;
}
GeneratorFunction | GeneratorFunctionDeclaration | ArrowFunction => {
stats.closures += 1;
}
_ => {}
}
}
}

impl Nom for TsxCode {
fn compute(node: &Node, stats: &mut Stats) {
use Tsx::*;

match node.kind_id().into() {
Function | FunctionDeclaration | MethodDefinition => {
stats.functions += 1;
}
GeneratorFunction | GeneratorFunctionDeclaration | ArrowFunction => {
stats.closures += 1;
}
_ => {}
}
}
}

impl Nom for RustCode {
fn compute(node: &Node, stats: &mut Stats) {
use Rust::*;

match node.kind_id().into() {
FunctionItem => {
stats.functions += 1;
}
ClosureExpression => {
stats.closures += 1;
}
_ => {}
}
}
}

impl Nom for CppCode {
fn compute(node: &Node, stats: &mut Stats) {
use Cpp::*;

match node.kind_id().into() {
FunctionDefinition
| FunctionDefinition2
| FunctionDefinition3
| FunctionDefinition4
| FunctionDefinitionRepeat1 => {
stats.functions += 1;
}
LambdaExpression => {
stats.closures += 1;
}
_ => {}
}
}
}

impl Nom for PreprocCode {}
impl Nom for CcommentCode {}
impl Nom for CSharpCode {}
impl Nom for JavaCode {}
impl Nom for GoCode {}
impl Nom for CssCode {}
impl Nom for HtmlCode {}

#[cfg(test)]
mod tests {
use std::path::PathBuf;

use super::*;

#[test]
fn test_nom_python() {
check_metrics!(
"def a():\n pass\n\n
def b():\n pass\n\n
def c():\n pass\n\n
x = lambda a : a + 42\n",
"foo.py",
PythonParser,
nom,
[
(functions, 3, usize),
(closures, 1, usize),
(total, 4, usize)
]
);
}

#[test]
fn test_nom_rust() {
check_metrics!(
"mod A { fn foo() {}}\n mod B { fn foo() {}}\n
let closure = |i: i32| -> i32 { i + 42 };\n",
"foo.rs",
RustParser,
nom,
[
(functions, 2, usize),
(closures, 1, usize),
(total, 3, usize)
]
);
}

#[test]
fn test_nom_cpp() {
check_metrics!(
"struct A {\n void foo(int) {}\n void foo(double) {}\n};\n
int b = [](int x) -> int { return x + 42; };\n",
"foo.cpp",
CppParser,
nom,
[
(functions, 2, usize),
(closures, 1, usize),
(total, 3, usize)
]
);
}

#[test]
fn test_nom_c() {
check_metrics!(
"int foo();\n\nint foo() {\n return 0;\n}\n",
"foo.c",
CppParser,
nom,
[
(functions, 1, usize),
(closures, 0, usize),
(total, 1, usize)
]
);
}
}
Loading