src/metrics/nom.rs (671 lines of code) (raw):

use serde::Serialize; use serde::ser::{SerializeStruct, Serializer}; use std::fmt; use crate::checker::Checker; use crate::macros::implement_metric_trait; use crate::*; /// The `Nom` metric suite. #[derive(Clone, Debug)] pub struct Stats { functions: usize, closures: usize, functions_sum: usize, closures_sum: usize, functions_min: usize, functions_max: usize, closures_min: usize, closures_max: usize, space_count: usize, } impl Default for Stats { fn default() -> Self { Self { functions: 0, closures: 0, functions_sum: 0, closures_sum: 0, functions_min: usize::MAX, functions_max: 0, closures_min: usize::MAX, closures_max: 0, space_count: 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("nom", 10)?; st.serialize_field("functions", &self.functions_sum())?; st.serialize_field("closures", &self.closures_sum())?; st.serialize_field("functions_average", &self.functions_average())?; st.serialize_field("closures_average", &self.closures_average())?; st.serialize_field("total", &self.total())?; st.serialize_field("average", &self.average())?; st.serialize_field("functions_min", &self.functions_min())?; st.serialize_field("functions_max", &self.functions_max())?; st.serialize_field("closures_min", &self.closures_min())?; st.serialize_field("closures_max", &self.closures_max())?; st.end() } } impl fmt::Display for Stats { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "functions: {}, \ closures: {}, \ functions_average: {}, \ closures_average: {}, \ total: {} \ average: {} \ functions_min: {} \ functions_max: {} \ closures_min: {} \ closures_max: {}", self.functions_sum(), self.closures_sum(), self.functions_average(), self.closures_average(), self.total(), self.average(), self.functions_min(), self.functions_max(), self.closures_min(), self.closures_max(), ) } } impl Stats { /// Merges a second `Nom` metric suite into the first one pub fn merge(&mut self, other: &Stats) { self.functions_min = self.functions_min.min(other.functions_min); self.functions_max = self.functions_max.max(other.functions_max); self.closures_min = self.closures_min.min(other.closures_min); self.closures_max = self.closures_max.max(other.closures_max); self.functions_sum += other.functions_sum; self.closures_sum += other.closures_sum; self.space_count += other.space_count; } /// Counts the number of function definitions in a scope #[inline(always)] pub fn functions(&self) -> f64 { // Only function definitions are considered, not general declarations self.functions as f64 } /// Counts the number of closures in a scope #[inline(always)] pub fn closures(&self) -> f64 { self.closures as f64 } /// Return the sum metric for functions #[inline(always)] pub fn functions_sum(&self) -> f64 { // Only function definitions are considered, not general declarations self.functions_sum as f64 } /// Return the sum metric for closures #[inline(always)] pub fn closures_sum(&self) -> f64 { self.closures_sum as f64 } /// Returns the average number of function definitions over all spaces #[inline(always)] pub fn functions_average(&self) -> f64 { self.functions_sum() / self.space_count as f64 } /// Returns the average number of closures over all spaces #[inline(always)] pub fn closures_average(&self) -> f64 { self.closures_sum() / self.space_count as f64 } /// Returns the average number of function definitions and closures over all spaces #[inline(always)] pub fn average(&self) -> f64 { self.total() / self.space_count as f64 } /// Counts the number of function definitions in a scope #[inline(always)] pub fn functions_min(&self) -> f64 { // Only function definitions are considered, not general declarations self.functions_min as f64 } /// Counts the number of closures in a scope #[inline(always)] pub fn closures_min(&self) -> f64 { self.closures_min as f64 } /// Counts the number of function definitions in a scope #[inline(always)] pub fn functions_max(&self) -> f64 { // Only function definitions are considered, not general declarations self.functions_max as f64 } /// Counts the number of closures in a scope #[inline(always)] pub fn closures_max(&self) -> f64 { self.closures_max as f64 } /// Returns the total number of function definitions and /// closures in a scope #[inline(always)] pub fn total(&self) -> f64 { self.functions_sum() + self.closures_sum() } #[inline(always)] pub(crate) fn compute_sum(&mut self) { self.functions_sum += self.functions; self.closures_sum += self.closures; } #[inline(always)] pub(crate) fn compute_minmax(&mut self) { self.functions_min = self.functions_min.min(self.functions); self.functions_max = self.functions_max.max(self.functions); self.closures_min = self.closures_min.min(self.closures); self.closures_max = self.closures_max.max(self.closures); self.compute_sum(); } } pub trait Nom where Self: Checker, { fn compute(node: &Node, stats: &mut Stats) { if Self::is_func(node) { stats.functions += 1; return; } if Self::is_closure(node) { stats.closures += 1; } } } implement_metric_trait!( [Nom], PythonCode, MozjsCode, JavascriptCode, TypescriptCode, TsxCode, CppCode, RustCode, PreprocCode, CcommentCode, JavaCode, KotlinCode ); #[cfg(test)] mod tests { use crate::tools::check_metrics; use super::*; #[test] fn python_nom() { check_metrics::<PythonParser>( "def a(): pass def b(): pass def c(): pass x = lambda a : a + 42", "foo.py", |metric| { // Number of spaces = 4 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 3.0, "closures": 1.0, "functions_average": 0.75, "closures_average": 0.25, "total": 4.0, "average": 1.0, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 1.0 }"### ); }, ); } #[test] fn rust_nom() { check_metrics::<RustParser>( "mod A { fn foo() {}} mod B { fn foo() {}} let closure = |i: i32| -> i32 { i + 42 };", "foo.rs", |metric| { // Number of spaces = 4 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 2.0, "closures": 1.0, "functions_average": 0.5, "closures_average": 0.25, "total": 3.0, "average": 0.75, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 1.0 }"### ); }, ); } #[test] fn c_nom() { check_metrics::<CppParser>( "int foo(); int foo() { return 0; }", "foo.c", |metric| { // Number of spaces = 2 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 1.0, "closures": 0.0, "functions_average": 0.5, "closures_average": 0.0, "total": 1.0, "average": 0.5, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 0.0 }"### ); }, ); } #[test] fn cpp_nom() { check_metrics::<CppParser>( "struct A { void foo(int) {} void foo(double) {} }; int b = [](int x) -> int { return x + 42; };", "foo.cpp", |metric| { // Number of spaces = 4 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 2.0, "closures": 1.0, "functions_average": 0.5, "closures_average": 0.25, "total": 3.0, "average": 0.75, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 1.0 }"### ); }, ); } #[test] fn javascript_nom() { check_metrics::<JavascriptParser>( "function f(a, b) { function foo(a) { return a; } var bar = (function () { var counter = 0; return function () { counter += 1; return counter } })(); return bar(foo(a), a); }", "foo.js", |metric| { // Number of spaces = 5 // functions: f, foo, bar // closures: return function () insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 3.0, "closures": 1.0, "functions_average": 0.6, "closures_average": 0.2, "total": 4.0, "average": 0.8, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 1.0 }"### ); }, ); } #[test] fn javascript_call_nom() { check_metrics::<JavascriptParser>( "add_task(async function test_safe_mode() { gAppInfo.inSafeMode = true; });", "foo.js", |metric| { // Number of spaces = 2 // functions: test_safe_mode insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 1.0, "closures": 0.0, "functions_average": 0.5, "closures_average": 0.0, "total": 1.0, "average": 0.5, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 0.0 }"### ); }, ); } #[test] fn javascript_assignment_nom() { check_metrics::<JavascriptParser>( "AnimationTest.prototype.enableDisplay = function(element) {};", "foo.js", |metric| { // Number of spaces = 2 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 1.0, "closures": 0.0, "functions_average": 0.5, "closures_average": 0.0, "total": 1.0, "average": 0.5, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 0.0 }"### ); }, ); } #[test] fn javascript_labeled_nom() { check_metrics::<JavascriptParser>( "toJSON: function() { return this.inspect(true); }", "foo.js", |metric| { // Number of spaces = 2 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 1.0, "closures": 0.0, "functions_average": 0.5, "closures_average": 0.0, "total": 1.0, "average": 0.5, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 0.0 }"### ); }, ); } #[test] fn javascript_labeled_arrow_nom() { check_metrics::<JavascriptParser>( "const dimConverters = { pt: x => x, };", "foo.js", |metric| { // Number of spaces = 2 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 1.0, "closures": 0.0, "functions_average": 0.5, "closures_average": 0.0, "total": 1.0, "average": 0.5, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 0.0 }"### ); }, ); } #[test] fn javascript_pair_nom() { check_metrics::<JavascriptParser>( "return { initialize: function(object) { this._object = object.toObject(); }, }", "foo.js", |metric| { // Number of spaces = 2 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 1.0, "closures": 0.0, "functions_average": 0.5, "closures_average": 0.0, "total": 1.0, "average": 0.5, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 0.0 }"### ); }, ); } #[test] fn javascript_unnamed_nom() { check_metrics::<JavascriptParser>( "Ajax.getTransport = Try.these( function() { return function(){ return new XMLHttpRequest()} } );", "foo.js", |metric| { // Number of spaces = 3 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 0.0, "closures": 2.0, "functions_average": 0.0, "closures_average": 0.6666666666666666, "total": 2.0, "average": 0.6666666666666666, "functions_min": 0.0, "functions_max": 0.0, "closures_min": 0.0, "closures_max": 1.0 }"### ); }, ); } #[test] fn javascript_arrow_nom() { check_metrics::<JavascriptParser>( "var materials = [\"Hydrogen\"]; materials.map(material => material.length); let add = (a, b) => a + b;", "foo.js", |metric| { // Number of spaces = 3 // Functions: add // Closures: material.map insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 1.0, "closures": 1.0, "functions_average": 0.3333333333333333, "closures_average": 0.3333333333333333, "total": 2.0, "average": 0.6666666666666666, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 1.0 }"### ); }, ); } #[test] fn javascript_arrow_assignment_nom() { check_metrics::<JavascriptParser>("sink.onPull = () => { };", "foo.js", |metric| { // Number of spaces = 2 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 1.0, "closures": 0.0, "functions_average": 0.5, "closures_average": 0.0, "total": 1.0, "average": 0.5, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 0.0 }"### ); }); } #[test] fn javascript_arrow_new_nom() { check_metrics::<JavascriptParser>( "const response = new Promise(resolve => channel.port1.onmessage = resolve);", "foo.js", |metric| { // Number of spaces = 2 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 0.0, "closures": 1.0, "functions_average": 0.0, "closures_average": 0.5, "total": 1.0, "average": 0.5, "functions_min": 0.0, "functions_max": 0.0, "closures_min": 0.0, "closures_max": 1.0 }"### ); }, ); } #[test] fn javascript_arrow_call_nom() { check_metrics::<JavascriptParser>( "let notDisabled = TestUtils.waitForCondition( () => !backbutton.hasAttribute(\"disabled\") );", "foo.js", |metric| { // Number of spaces = 2 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 0.0, "closures": 1.0, "functions_average": 0.0, "closures_average": 0.5, "total": 1.0, "average": 0.5, "functions_min": 0.0, "functions_max": 0.0, "closures_min": 0.0, "closures_max": 1.0 }"### ); }, ); } #[test] fn java_nom() { check_metrics::<JavaParser>( "class A { public void foo(){ return; } public void bar(){ return; } }", "foo.java", |metric| { // Number of spaces = 4 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 2.0, "closures": 0.0, "functions_average": 0.5, "closures_average": 0.0, "total": 2.0, "average": 0.5, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 0.0 }"### ); }, ); } #[test] fn java_closure_nom() { check_metrics::<JavaParser>( "interface printable{ void print(); } interface IntFunc { int func(int n); } class Printer implements printable{ public void print(){System.out.println(\"Hello\");} public static void main(String args[]){ Printer obj = new Printer(); obj.print(); IntFunc meaning = (i) -> i + 42; int i = meaning.func(1); } }", "foo.java", |metric| { // Number of spaces = 8 insta::assert_json_snapshot!( metric.nom, @r###" { "functions": 4.0, "closures": 1.0, "functions_average": 0.5, "closures_average": 0.125, "total": 5.0, "average": 0.625, "functions_min": 0.0, "functions_max": 1.0, "closures_min": 0.0, "closures_max": 1.0 }"### ); }, ); } }