use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use std::fmt;
use crate::checker::Checker;
use crate::*;
#[derive(Debug, Clone)]
pub struct Stats {
exit: usize,
exit_sum: usize,
total_space_functions: usize,
exit_min: usize,
exit_max: usize,
}
impl Default for Stats {
fn default() -> Self {
Self {
exit: 0,
exit_sum: 0,
total_space_functions: 1,
exit_min: usize::MAX,
exit_max: 0,
}
}
}
impl Serialize for Stats {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut st = serializer.serialize_struct("nexits", 4)?;
st.serialize_field("sum", &self.exit())?;
st.serialize_field("average", &self.exit_average())?;
st.serialize_field("min", &self.exit_min())?;
st.serialize_field("max", &self.exit_max())?;
st.end()
}
}
impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"sum: {}, average: {} min: {}, max: {}",
self.exit_sum(),
self.exit_average(),
self.exit_min(),
self.exit_max()
)
}
}
impl Stats {
pub fn merge(&mut self, other: &Stats) {
self.exit_max = self.exit_max.max(other.exit_max);
self.exit_min = self.exit_min.min(other.exit_min);
self.exit_sum += other.exit_sum;
}
pub fn exit(&self) -> f64 {
self.exit as f64
}
pub fn exit_sum(&self) -> f64 {
self.exit_sum as f64
}
pub fn exit_min(&self) -> f64 {
self.exit_min as f64
}
pub fn exit_max(&self) -> f64 {
self.exit_max as f64
}
pub fn exit_average(&self) -> f64 {
self.exit_sum() / self.total_space_functions as f64
}
#[inline(always)]
pub(crate) fn compute_sum(&mut self) {
self.exit_sum += self.exit;
}
#[inline(always)]
pub(crate) fn compute_minmax(&mut self) {
self.exit_max = self.exit_max.max(self.exit);
self.exit_min = self.exit_min.min(self.exit);
self.compute_sum();
}
pub(crate) fn finalize(&mut self, total_space_functions: usize) {
self.total_space_functions = total_space_functions;
}
}
#[doc(hidden)]
pub trait Exit
where
Self: Checker,
{
fn compute(_node: &Node, _stats: &mut Stats) {}
}
impl Exit for PythonCode {
fn compute(node: &Node, stats: &mut Stats) {
if matches!(node.object().kind_id().into(), Python::ReturnStatement) {
stats.exit += 1;
}
}
}
impl Exit for MozjsCode {
fn compute(node: &Node, stats: &mut Stats) {
if matches!(node.object().kind_id().into(), Mozjs::ReturnStatement) {
stats.exit += 1;
}
}
}
impl Exit for JavascriptCode {
fn compute(node: &Node, stats: &mut Stats) {
if matches!(node.object().kind_id().into(), Javascript::ReturnStatement) {
stats.exit += 1;
}
}
}
impl Exit for TypescriptCode {
fn compute(node: &Node, stats: &mut Stats) {
if matches!(node.object().kind_id().into(), Typescript::ReturnStatement) {
stats.exit += 1;
}
}
}
impl Exit for TsxCode {
fn compute(node: &Node, stats: &mut Stats) {
if matches!(node.object().kind_id().into(), Tsx::ReturnStatement) {
stats.exit += 1;
}
}
}
impl Exit for RustCode {
fn compute(node: &Node, stats: &mut Stats) {
if matches!(node.object().kind_id().into(), Rust::ReturnExpression)
|| Self::is_func(node) && node.object().child_by_field_name("return_type").is_some()
{
stats.exit += 1;
}
}
}
impl Exit for CppCode {
fn compute(node: &Node, stats: &mut Stats) {
if matches!(node.object().kind_id().into(), Cpp::ReturnStatement) {
stats.exit += 1;
}
}
}
impl Exit for JavaCode {
fn compute(node: &Node, stats: &mut Stats) {
if matches!(node.object().kind_id().into(), Java::ReturnStatement) {
stats.exit += 1;
}
}
}
impl Exit for PreprocCode {}
impl Exit for CcommentCode {}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
#[test]
fn python_no_exit() {
check_metrics!(
"a = 42",
"foo.py",
PythonParser,
nexits,
[
(exit_sum, 0, usize),
(exit_min, 0, usize),
(exit_max, 0, usize)
],
[(exit_average, f64::NAN)] );
}
#[test]
fn rust_no_exit() {
check_metrics!(
"let a = 42;",
"foo.rs",
RustParser,
nexits,
[
(exit_sum, 0, usize),
(exit_min, 0, usize),
(exit_max, 0, usize)
],
[(exit_average, f64::NAN)] );
}
#[test]
fn c_no_exit() {
check_metrics!(
"int a = 42;",
"foo.c",
CppParser,
nexits,
[
(exit_sum, 0, usize),
(exit_min, 0, usize),
(exit_max, 0, usize)
],
[(exit_average, f64::NAN)] );
}
#[test]
fn javascript_no_exit() {
check_metrics!(
"var a = 42;",
"foo.js",
JavascriptParser,
nexits,
[
(exit_sum, 0, usize),
(exit_min, 0, usize),
(exit_max, 0, usize)
],
[(exit_average, f64::NAN)] );
}
#[test]
fn python_simple_function() {
check_metrics!(
"def f(a, b):
if a:
return a",
"foo.py",
PythonParser,
nexits,
[
(exit_sum, 1, usize),
(exit_min, 0, usize),
(exit_max, 1, usize)
],
[(exit_average, 1.0)] );
}
#[test]
fn python_more_functions() {
check_metrics!(
"def f(a, b):
if a:
return a
def f(a, b):
if b:
return b",
"foo.py",
PythonParser,
nexits,
[
(exit_sum, 2, usize),
(exit_min, 0, usize),
(exit_max, 1, usize)
],
[(exit_average, 1.0)] );
}
#[test]
fn python_nested_functions() {
check_metrics!(
"def f(a, b):
def foo(a):
if a:
return 1
bar = lambda a: lambda b: b or True or True
return bar(foo(a))(a)",
"foo.py",
PythonParser,
nexits,
[
(exit_sum, 2, usize),
(exit_min, 0, usize),
(exit_max, 1, usize)
],
[(exit_average, 0.5)] );
}
#[test]
fn java_no_exit() {
check_metrics!(
"int a = 42;",
"foo.java",
JavaParser,
nexits,
[
(exit_sum, 0, usize),
(exit_min, 0, usize),
(exit_max, 0, usize)
],
[(exit_average, f64::NAN)] );
}
#[test]
fn java_simple_function() {
check_metrics!(
"class A {
public int sum(int x, int y) {
return x + y;
}
}",
"foo.java",
JavaParser,
nexits,
[
(exit_sum, 1, usize),
(exit_min, 0, usize),
(exit_max, 1, usize)
],
[(exit_average, 1.0)] );
}
#[test]
fn java_split_function() {
check_metrics!(
"class A {
public int multiply(int x, int y) {
if(x == 0 || y == 0){
return 0;
}
return x * y;
}
}",
"foo.java",
JavaParser,
nexits,
[
(exit_sum, 2, usize),
(exit_min, 0, usize),
(exit_max, 2, usize)
],
[(exit_average, 2.0)] );
}
}