#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use std::fmt;
use crate::checker::Checker;
use crate::macros::implement_metric_trait;
use crate::*;
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct Stats {
tokens: usize,
tokens_sum: usize,
tokens_min: usize,
tokens_max: usize,
space_count: usize,
}
impl Default for Stats {
fn default() -> Self {
Self {
tokens: 0,
tokens_sum: 0,
tokens_min: usize::MAX,
tokens_max: 0,
space_count: 1,
}
}
}
impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"tokens: {}, \
tokens_average: {}, \
tokens_min: {}, \
tokens_max: {}",
self.tokens_sum(),
self.tokens_average(),
self.tokens_min(),
self.tokens_max(),
)
}
}
impl Stats {
pub fn merge(&mut self, other: &Stats) {
self.tokens_min = self.tokens_min.min(other.tokens_min);
self.tokens_max = self.tokens_max.max(other.tokens_max);
self.tokens_sum += other.tokens_sum;
self.space_count += other.space_count;
}
#[inline]
#[must_use]
pub fn tokens_sum(&self) -> u64 {
self.tokens_sum as u64
}
#[inline]
#[must_use]
pub fn tokens_average(&self) -> f64 {
crate::metrics::average(self.tokens_sum() as f64, self.space_count)
}
#[inline]
#[must_use]
pub fn tokens_min(&self) -> u64 {
if self.tokens_min == usize::MAX {
0
} else {
self.tokens_min as u64
}
}
#[inline]
#[must_use]
pub fn tokens_max(&self) -> u64 {
self.tokens_max as u64
}
#[inline]
pub(crate) fn compute_sum(&mut self) {
self.tokens_sum += self.tokens;
}
#[inline]
pub(crate) fn compute_minmax(&mut self) {
self.tokens_min = self.tokens_min.min(self.tokens);
self.tokens_max = self.tokens_max.max(self.tokens);
self.compute_sum();
}
}
#[doc(hidden)]
pub(crate) trait Tokens
where
Self: Checker,
{
fn compute(node: &Node, stats: &mut Stats) {
if node.child_count() != 0 {
return;
}
let in_comment =
std::iter::successors(Some(*node), Node::parent).any(|n| Self::is_comment(&n));
if !in_comment {
stats.tokens += 1;
}
}
}
implement_metric_trait!(
[Tokens],
PythonCode,
MozjsCode,
JavascriptCode,
TypescriptCode,
TsxCode,
CppCode,
MozcppCode,
CCode,
ObjcCode,
RustCode,
PreprocCode,
CcommentCode,
JavaCode,
KotlinCode,
GoCode,
PerlCode,
BashCode,
LuaCode,
TclCode,
PhpCode,
CsharpCode,
ElixirCode,
RubyCode,
GroovyCode,
IrulesCode
);
#[cfg(test)]
#[allow(
clippy::float_cmp,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::doc_markdown,
clippy::needless_raw_string_hashes,
clippy::too_many_lines
)]
mod tests {
use crate::tools::check_metrics;
use super::*;
#[test]
fn python_tokens_exact_count() {
check_metrics::<PythonParser>("def foo(x): return x", "foo.py", |metric| {
assert_eq!(metric.tokens.tokens_sum(), 8);
assert!(metric.tokens.tokens_max() >= 7);
});
}
#[test]
fn python_tokens_comments_excluded() {
check_metrics::<PythonParser>(
"def foo(x): return x # explanation\n# header\n",
"foo.py",
|metric| {
assert_eq!(metric.tokens.tokens_sum(), 8);
},
);
}
#[test]
fn python_tokens_whitespace_excluded() {
check_metrics::<PythonParser>(
"\n\n def foo(x):\n return x\n\n",
"foo.py",
|metric| {
assert_eq!(metric.tokens.tokens_sum(), 8);
},
);
}
#[test]
fn python_tokens_distinct_from_halstead() {
check_metrics::<PythonParser>("def foo(x): return (x + 1)", "foo.py", |metric| {
let halstead_total =
metric.halstead.total_operators() + metric.halstead.total_operands();
assert!(
metric.tokens.tokens_sum() > halstead_total,
"expected tokens ({}) > halstead N1+N2 ({}); punctuation \
like `(`, `)`, `:` should contribute to tokens but not Halstead",
metric.tokens.tokens_sum(),
halstead_total,
);
});
}
#[test]
fn python_tokens_nested_attribution() {
check_metrics::<PythonParser>(
"def outer():\n def inner():\n return 1\n",
"foo.py",
|metric| {
assert_eq!(metric.tokens.tokens_sum(), 12);
assert_eq!(metric.tokens.tokens_max(), 7);
assert_eq!(metric.tokens.tokens_min(), 0);
},
);
}
#[test]
fn cpp_tokens_block_comments_excluded() {
check_metrics::<CppParser>(
"int foo(int x) { /* multi\n line */ return x; }",
"foo.cpp",
|m| {
assert_eq!(m.tokens.tokens_sum(), 11);
},
);
check_metrics::<CppParser>("int foo(int x) { return x; }", "foo.cpp", |m| {
assert_eq!(m.tokens.tokens_sum(), 11);
});
}
#[test]
fn cpp_tokens_line_comments_excluded() {
check_metrics::<CppParser>("int x = 1; // a one-line comment\n", "foo.cpp", |m| {
assert_eq!(m.tokens.tokens_sum(), 5);
});
check_metrics::<CppParser>("int x = 1;\n", "foo.cpp", |m| {
assert_eq!(m.tokens.tokens_sum(), 5);
});
}
#[test]
fn cpp_tokens_whitespace_excluded() {
check_metrics::<CppParser>("\n\nint foo(int x) {\n return x;\n}\n", "foo.cpp", |m| {
assert_eq!(m.tokens.tokens_sum(), 11);
});
}
#[test]
fn cpp_tokens_distinct_from_halstead() {
check_metrics::<CppParser>("int foo(int x) { return (x + 1); }", "foo.cpp", |m| {
let halstead_total = m.halstead.total_operators() + m.halstead.total_operands();
assert!(
m.tokens.tokens_sum() > halstead_total,
"expected tokens ({}) > halstead N1+N2 ({}); punctuation like \
`(`, `)`, `{{`, `}}` and `;` should contribute to tokens but not Halstead",
m.tokens.tokens_sum(),
halstead_total,
);
});
}
#[test]
fn cpp_tokens_nested_attribution() {
check_metrics::<CppParser>(
"struct S {\n int method(int a) { return a + 1; }\n};\nint outer() { return 2; }\n",
"foo.cpp",
|m| {
assert_eq!(m.tokens.tokens_sum(), 27);
assert_eq!(m.tokens.tokens_max(), 13);
assert_eq!(m.tokens.tokens_min(), 1);
},
);
}
#[test]
fn java_tokens_line_comments_excluded() {
check_metrics::<JavaParser>(
"class A { void foo() { // hi\n return; } }",
"A.java",
|m| {
assert_eq!(m.tokens.tokens_sum(), 12);
},
);
check_metrics::<JavaParser>("class A { void foo() { return; } }", "A.java", |m| {
assert_eq!(m.tokens.tokens_sum(), 12);
});
}
#[test]
fn groovy_tokens_line_comments_excluded() {
check_metrics::<GroovyParser>(
"class A { void foo() { // hi\n return\n } }",
"A.groovy",
|m| {
assert_eq!(m.tokens.tokens_sum(), 11);
},
);
}
#[test]
fn javascript_tokens_html_comment_excluded() {
check_metrics::<JavascriptParser>("<!-- hi -->\nlet x = 1;\n", "foo.js", |m| {
assert_eq!(m.tokens.tokens_sum(), 5);
});
check_metrics::<JavascriptParser>("let x = 1;\n", "foo.js", |m| {
assert_eq!(m.tokens.tokens_sum(), 5);
});
}
#[test]
fn groovy_tokens_groovydoc_excluded() {
check_metrics::<GroovyParser>(
"/** doc */\nclass A { void f() { return\n } }\n",
"A.groovy",
|m| {
assert_eq!(m.tokens.tokens_sum(), 11);
},
);
check_metrics::<GroovyParser>("class A { void f() { return\n } }\n", "A.groovy", |m| {
assert_eq!(m.tokens.tokens_sum(), 11);
});
}
#[test]
fn rust_tokens_doc_comments_excluded() {
check_metrics::<RustParser>(
"/// outer doc\n/// more doc\nfn f() { let x = 1; }",
"foo.rs",
|m| {
assert_eq!(m.tokens.tokens_sum(), 11);
},
);
check_metrics::<RustParser>("fn f() { let x = 1; }", "foo.rs", |m| {
assert_eq!(m.tokens.tokens_sum(), 11);
});
}
#[test]
fn smoke_python() {
check_metrics::<PythonParser>("x = 1\n", "foo.py", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_rust() {
check_metrics::<RustParser>("fn f() { let x = 1; }", "foo.rs", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_cpp() {
check_metrics::<CppParser>("int x = 1;", "foo.cpp", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_java() {
check_metrics::<JavaParser>("class A { int x = 1; }", "A.java", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_csharp() {
check_metrics::<CsharpParser>("class A { int X = 1; }", "A.cs", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_javascript() {
check_metrics::<JavascriptParser>("let x = 1;", "foo.js", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_mozjs() {
check_metrics::<MozjsParser>("let x = 1;", "foo.js", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_typescript() {
check_metrics::<TypescriptParser>("const x: number = 1;", "foo.ts", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_tsx() {
check_metrics::<TsxParser>("const x: number = 1;", "foo.tsx", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_go() {
check_metrics::<GoParser>("package main\nfunc f() {}", "foo.go", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_kotlin() {
check_metrics::<KotlinParser>("fun f(): Int = 1", "foo.kt", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_lua() {
check_metrics::<LuaParser>("local x = 1", "foo.lua", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_bash() {
check_metrics::<BashParser>("x=1", "foo.sh", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_tcl() {
check_metrics::<TclParser>("set x 1", "foo.tcl", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_perl() {
check_metrics::<PerlParser>("my $x = 1;", "foo.pl", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_php() {
check_metrics::<PhpParser>("<?php $x = 1;", "foo.php", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_preproc() {
check_metrics::<PreprocParser>("#define FOO 1\n", "foo.h", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_ccomment() {
check_metrics::<CcommentParser>("int x = 1;", "foo.c", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_c() {
check_metrics::<CParser>("int x = 1;\n", "foo.c", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_objc() {
check_metrics::<ObjcParser>("int x = 1;\n", "foo.m", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_elixir() {
check_metrics::<ElixirParser>("defmodule Foo do\n :ok\nend\n", "foo.ex", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_ruby() {
check_metrics::<RubyParser>("def foo\n a = 1\nend\n", "foo.rb", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
#[test]
fn smoke_irules() {
check_metrics::<IrulesParser>("when X {\n set x 1\n}\n", "foo.irule", |m| {
assert!(m.tokens.tokens_sum() > 0);
});
}
}