From e6cd3f46c12b32072e40d348d53a61d887da3523 Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Tue, 31 Mar 2020 14:21:42 +0200 Subject: [PATCH 1/5] Implement Nom metric --- src/lib.rs | 3 + src/metrics.rs | 28 +++++++ src/nom.rs | 195 +++++++++++++++++++++++++++++++++++++++++++++++ src/traits.rs | 2 + src/ts_parser.rs | 15 +++- 5 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 src/nom.rs diff --git a/src/lib.rs b/src/lib.rs index 9a8220402..9c2fdcf4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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::*; diff --git a/src/metrics.rs b/src/metrics.rs index acff96a54..f8cc2ad3a 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -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::*; @@ -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, @@ -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)?; @@ -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(), @@ -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) @@ -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); @@ -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) } @@ -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, @@ -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, diff --git a/src/nom.rs b/src/nom.rs new file mode 100644 index 000000000..c6dc09784 --- /dev/null +++ b/src/nom.rs @@ -0,0 +1,195 @@ +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(&self, serializer: S) -> Result + 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 | Lambda2 | Lambda3 => { + 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 {} diff --git a/src/traits.rs b/src/traits.rs index 15a5ef4b0..9b6ecb3ca 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -11,6 +11,7 @@ use crate::halstead::Halstead; use crate::languages::*; use crate::loc::Loc; use crate::mi::Mi; +use crate::nom::Nom; use crate::preproc::PreprocResults; use crate::ts_parser::Filter; use crate::web::alterator::Alterator; @@ -29,6 +30,7 @@ pub trait TSParserTrait { type Cyclomatic: Cyclomatic; type Halstead: Halstead; type Loc: Loc; + type Nom: Nom; type Mi: Mi; type NArgs: NArgs; type Exit: Exit; diff --git a/src/ts_parser.rs b/src/ts_parser.rs index 116328c3d..5d23b5eaf 100644 --- a/src/ts_parser.rs +++ b/src/ts_parser.rs @@ -13,12 +13,23 @@ use crate::halstead::Halstead; use crate::languages::*; use crate::loc::Loc; use crate::mi::Mi; +use crate::nom::Nom; use crate::preproc::{get_macros, PreprocResults}; use crate::traits::*; use crate::web::alterator::Alterator; pub struct TSParser< - T: TSLanguage + Checker + Getter + Alterator + Cyclomatic + Exit + Halstead + NArgs + Loc + Mi, + T: TSLanguage + + Checker + + Getter + + Alterator + + Cyclomatic + + Exit + + Halstead + + NArgs + + Loc + + Nom + + Mi, > { code: Vec, tree: Tree, @@ -80,6 +91,7 @@ impl< + Exit + Halstead + Loc + + Nom + Mi + NArgs, > TSParserTrait for TSParser @@ -89,6 +101,7 @@ impl< type Cyclomatic = T; type Halstead = T; type Loc = T; + type Nom = T; type Mi = T; type NArgs = T; type Exit = T; From 8efaf2ffa7d3a104006ee3d2f0faf73783590c5a Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Thu, 2 Apr 2020 15:32:26 +0200 Subject: [PATCH 2/5] Update tests --- src/nom.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++ src/web/server.rs | 5 ++++ 2 files changed, 65 insertions(+) diff --git a/src/nom.rs b/src/nom.rs index c6dc09784..f609f63ad 100644 --- a/src/nom.rs +++ b/src/nom.rs @@ -193,3 +193,63 @@ 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() { + let sample = + String::from("def a():\n pass\n\ndef b():\n pass\n\ndef c():\n pass\n"); + let path = PathBuf::from("foo.py"); + let b_sample = sample.as_bytes().to_vec(); + let parser = PythonParser::new(b_sample.clone(), &path, None); + let func_space = metrics(&parser, &path).unwrap(); + + assert_eq!(func_space.metrics.nom.functions() as usize, 3); + assert_eq!(func_space.metrics.nom.closures() as usize, 0); + assert_eq!(func_space.metrics.nom.total() as usize, 3); + } + + #[test] + fn test_nom_rust() { + let sample = String::from("mod A { fn foo() {}}\n mod B { fn foo() {}}\n"); + let path = PathBuf::from("foo.rs"); + let b_sample = sample.as_bytes().to_vec(); + let parser = RustParser::new(b_sample.clone(), &path, None); + let func_space = metrics(&parser, &path).unwrap(); + + assert_eq!(func_space.metrics.nom.functions() as usize, 2); + assert_eq!(func_space.metrics.nom.closures() as usize, 0); + assert_eq!(func_space.metrics.nom.total() as usize, 2); + } + + #[test] + fn test_nom_cpp() { + let sample = String::from("struct A {\n void foo(int) {}\n void foo(double) {}\n};\n"); + let path = PathBuf::from("foo.cpp"); + let b_sample = sample.as_bytes().to_vec(); + let parser = CppParser::new(b_sample.clone(), &path, None); + let func_space = metrics(&parser, &path).unwrap(); + + assert_eq!(func_space.metrics.nom.functions() as usize, 2); + assert_eq!(func_space.metrics.nom.closures() as usize, 0); + assert_eq!(func_space.metrics.nom.total() as usize, 2); + } + + #[test] + fn test_nom_c() { + let sample = String::from("int foo();\n\nint foo() {\n return 0;\n}\n"); + let path = PathBuf::from("foo.c"); + let b_sample = sample.as_bytes().to_vec(); + let parser = CppParser::new(b_sample.clone(), &path, None); + let func_space = metrics(&parser, &path).unwrap(); + + assert_eq!(func_space.metrics.nom.functions() as usize, 1); + assert_eq!(func_space.metrics.nom.closures() as usize, 0); + assert_eq!(func_space.metrics.nom.total() as usize, 1); + } +} diff --git a/src/web/server.rs b/src/web/server.rs index 112a00fc2..ccd3d1e16 100644 --- a/src/web/server.rs +++ b/src/web/server.rs @@ -651,6 +651,7 @@ mod tests { "n1": 2.0, "volume": 4.754_887_502_163_468}, "loc": {"cloc": 1.0, "lloc": 2.0, "sloc": 4.0}, + "nom": {"functions": 1.0, "closures": 0.0, "total": 1.0}, "mi": {"mi_original": 140.204_331_558_152_12, "mi_sei": 161.644_455_240_662_24, "mi_visual_studio": 81.990_837_168_510_01}}, @@ -676,6 +677,7 @@ mod tests { "n1": 2.0, "volume": 4.754_887_502_163_468}, "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}, + "nom": {"functions": 1.0, "closures": 0.0, "total": 1.0}, "mi": {"mi_original": 151.433_315_883_223_23, "mi_sei": 142.873_061_717_489_78, "mi_visual_studio": 88.557_494_668_551_6}}, @@ -727,6 +729,7 @@ mod tests { "n1": 2.0, "volume": 4.754_887_502_163_468}, "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}, + "nom": {"functions": 1.0, "closures": 0.0, "total": 1.0}, "mi": {"mi_original": 151.433_315_883_223_23, "mi_sei": 142.873_061_717_489_78, "mi_visual_studio": 88.557_494_668_551_6}}, @@ -774,6 +777,7 @@ mod tests { "n1": 2.0, "volume": 4.754_887_502_163_468}, "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}, + "nom": {"functions": 1.0, "closures": 0.0, "total": 1.0}, "mi": {"mi_original": 151.433_315_883_223_23, "mi_sei": 142.873_061_717_489_78, "mi_visual_studio": 88.557_494_668_551_6}}, @@ -799,6 +803,7 @@ mod tests { "n1": 2.0, "volume": 4.754_887_502_163_468}, "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}, + "nom": {"functions": 1.0, "closures": 0.0, "total": 1.0}, "mi": {"mi_original": 151.433_315_883_223_23, "mi_sei": 142.873_061_717_489_78, "mi_visual_studio": 88.557_494_668_551_6}}, From f1152dbea76605c99185c1f7c9b5dfc455831019 Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Thu, 2 Apr 2020 16:22:27 +0200 Subject: [PATCH 3/5] Add Nom to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d71ea8890..f140a7f66 100644 --- a/README.md +++ b/README.md @@ -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. From ad27bb77a196264ceebac8503c5181d4eaab3411 Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Mon, 6 Apr 2020 11:51:07 +0200 Subject: [PATCH 4/5] Reduce code duplication in Nom unit tests --- src/macros.rs | 14 +++++++++ src/nom.rs | 81 ++++++++++++++++++++++++++++----------------------- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index d0c0156af..381221b2b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -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); )* + } + }; +} diff --git a/src/nom.rs b/src/nom.rs index f609f63ad..c3d3ba420 100644 --- a/src/nom.rs +++ b/src/nom.rs @@ -202,54 +202,61 @@ mod tests { #[test] fn test_nom_python() { - let sample = - String::from("def a():\n pass\n\ndef b():\n pass\n\ndef c():\n pass\n"); - let path = PathBuf::from("foo.py"); - let b_sample = sample.as_bytes().to_vec(); - let parser = PythonParser::new(b_sample.clone(), &path, None); - let func_space = metrics(&parser, &path).unwrap(); - - assert_eq!(func_space.metrics.nom.functions() as usize, 3); - assert_eq!(func_space.metrics.nom.closures() as usize, 0); - assert_eq!(func_space.metrics.nom.total() as usize, 3); + check_metrics!( + "def a():\n pass\n\ndef b():\n pass\n\ndef c():\n pass\n", + "foo.py", + PythonParser, + nom, + [ + (functions, 3, usize), + (closures, 0, usize), + (total, 3, usize) + ] + ); } #[test] fn test_nom_rust() { - let sample = String::from("mod A { fn foo() {}}\n mod B { fn foo() {}}\n"); - let path = PathBuf::from("foo.rs"); - let b_sample = sample.as_bytes().to_vec(); - let parser = RustParser::new(b_sample.clone(), &path, None); - let func_space = metrics(&parser, &path).unwrap(); - - assert_eq!(func_space.metrics.nom.functions() as usize, 2); - assert_eq!(func_space.metrics.nom.closures() as usize, 0); - assert_eq!(func_space.metrics.nom.total() as usize, 2); + check_metrics!( + "mod A { fn foo() {}}\n mod B { fn foo() {}}\n", + "foo.rs", + RustParser, + nom, + [ + (functions, 2, usize), + (closures, 0, usize), + (total, 2, usize) + ] + ); } #[test] fn test_nom_cpp() { - let sample = String::from("struct A {\n void foo(int) {}\n void foo(double) {}\n};\n"); - let path = PathBuf::from("foo.cpp"); - let b_sample = sample.as_bytes().to_vec(); - let parser = CppParser::new(b_sample.clone(), &path, None); - let func_space = metrics(&parser, &path).unwrap(); - - assert_eq!(func_space.metrics.nom.functions() as usize, 2); - assert_eq!(func_space.metrics.nom.closures() as usize, 0); - assert_eq!(func_space.metrics.nom.total() as usize, 2); + check_metrics!( + "struct A {\n void foo(int) {}\n void foo(double) {}\n};\n", + "foo.cpp", + CppParser, + nom, + [ + (functions, 2, usize), + (closures, 0, usize), + (total, 2, usize) + ] + ); } #[test] fn test_nom_c() { - let sample = String::from("int foo();\n\nint foo() {\n return 0;\n}\n"); - let path = PathBuf::from("foo.c"); - let b_sample = sample.as_bytes().to_vec(); - let parser = CppParser::new(b_sample.clone(), &path, None); - let func_space = metrics(&parser, &path).unwrap(); - - assert_eq!(func_space.metrics.nom.functions() as usize, 1); - assert_eq!(func_space.metrics.nom.closures() as usize, 0); - assert_eq!(func_space.metrics.nom.total() as usize, 1); + 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) + ] + ); } } From 3e31167f41624ffaadbb9942f6a63d809a4b04f0 Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Tue, 7 Apr 2020 14:59:53 +0200 Subject: [PATCH 5/5] Add lambda examples to cloc tests --- src/nom.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/nom.rs b/src/nom.rs index c3d3ba420..e05838c92 100644 --- a/src/nom.rs +++ b/src/nom.rs @@ -78,7 +78,7 @@ impl Nom for PythonCode { FunctionDefinition => { stats.functions += 1; } - Lambda | Lambda2 | Lambda3 => { + Lambda => { stats.closures += 1; } _ => {} @@ -203,14 +203,17 @@ mod tests { #[test] fn test_nom_python() { check_metrics!( - "def a():\n pass\n\ndef b():\n pass\n\ndef c():\n pass\n", + "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, 0, usize), - (total, 3, usize) + (closures, 1, usize), + (total, 4, usize) ] ); } @@ -218,14 +221,15 @@ mod tests { #[test] fn test_nom_rust() { check_metrics!( - "mod A { fn foo() {}}\n mod B { fn foo() {}}\n", + "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, 0, usize), - (total, 2, usize) + (closures, 1, usize), + (total, 3, usize) ] ); } @@ -233,14 +237,15 @@ mod tests { #[test] fn test_nom_cpp() { check_metrics!( - "struct A {\n void foo(int) {}\n void foo(double) {}\n};\n", + "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, 0, usize), - (total, 2, usize) + (closures, 1, usize), + (total, 3, usize) ] ); }