#![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::c_declarator::innermost_declarator;
use crate::checker::Checker;
use crate::macros::implement_metric_trait;
use crate::*;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Stats {
fn_nargs: usize,
closure_nargs: usize,
fn_nargs_sum: usize,
closure_nargs_sum: usize,
fn_nargs_min: usize,
closure_nargs_min: usize,
fn_nargs_max: usize,
closure_nargs_max: usize,
total_functions: usize,
total_closures: usize,
}
impl Default for Stats {
fn default() -> Self {
Self {
fn_nargs: 0,
closure_nargs: 0,
fn_nargs_sum: 0,
closure_nargs_sum: 0,
fn_nargs_min: usize::MAX,
closure_nargs_min: usize::MAX,
fn_nargs_max: 0,
closure_nargs_max: 0,
total_functions: 0,
total_closures: 0,
}
}
}
impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"function_args: {}, closure_args: {}, function_args_average: {}, closure_args_average: {}, total: {}, average: {}, function_args_min: {}, function_args_max: {}, closure_args_min: {}, closure_args_max: {}",
self.function_args_sum(),
self.closure_args_sum(),
self.function_args_average(),
self.closure_args_average(),
self.total(),
self.average(),
self.function_args_min(),
self.function_args_max(),
self.closure_args_min(),
self.closure_args_max()
)
}
}
impl Stats {
pub fn merge(&mut self, other: &Stats) {
self.closure_nargs_min = self.closure_nargs_min.min(other.closure_nargs_min);
self.closure_nargs_max = self.closure_nargs_max.max(other.closure_nargs_max);
self.fn_nargs_min = self.fn_nargs_min.min(other.fn_nargs_min);
self.fn_nargs_max = self.fn_nargs_max.max(other.fn_nargs_max);
self.fn_nargs_sum += other.fn_nargs_sum;
self.closure_nargs_sum += other.closure_nargs_sum;
}
#[inline]
#[must_use]
pub fn function_args(&self) -> u64 {
self.fn_nargs as u64
}
#[inline]
#[must_use]
pub fn closure_args(&self) -> u64 {
self.closure_nargs as u64
}
#[inline]
#[must_use]
pub fn function_args_sum(&self) -> u64 {
self.fn_nargs_sum as u64
}
#[inline]
#[must_use]
pub fn closure_args_sum(&self) -> u64 {
self.closure_nargs_sum as u64
}
#[inline]
#[must_use]
pub fn function_args_average(&self) -> f64 {
crate::metrics::average(self.fn_nargs_sum as f64, self.total_functions)
}
#[inline]
#[must_use]
pub fn closure_args_average(&self) -> f64 {
crate::metrics::average(self.closure_nargs_sum as f64, self.total_closures)
}
#[inline]
#[must_use]
pub fn total(&self) -> u64 {
self.function_args_sum() + self.closure_args_sum()
}
#[inline]
#[must_use]
pub fn average(&self) -> f64 {
crate::metrics::average(
self.total() as f64,
self.total_functions + self.total_closures,
)
}
#[inline]
#[must_use]
pub fn function_args_min(&self) -> u64 {
if self.fn_nargs_min == usize::MAX {
0
} else {
self.fn_nargs_min as u64
}
}
#[inline]
#[must_use]
pub fn function_args_max(&self) -> u64 {
self.fn_nargs_max as u64
}
#[inline]
#[must_use]
pub fn closure_args_min(&self) -> u64 {
if self.closure_nargs_min == usize::MAX {
0
} else {
self.closure_nargs_min as u64
}
}
#[inline]
#[must_use]
pub fn closure_args_max(&self) -> u64 {
self.closure_nargs_max as u64
}
#[inline]
pub(crate) fn compute_sum(&mut self) {
self.closure_nargs_sum += self.closure_nargs;
self.fn_nargs_sum += self.fn_nargs;
}
#[inline]
pub(crate) fn compute_minmax(&mut self) {
self.closure_nargs_min = self.closure_nargs_min.min(self.closure_nargs);
self.closure_nargs_max = self.closure_nargs_max.max(self.closure_nargs);
self.fn_nargs_min = self.fn_nargs_min.min(self.fn_nargs);
self.fn_nargs_max = self.fn_nargs_max.max(self.fn_nargs);
self.compute_sum();
}
pub(crate) fn finalize(&mut self, total_functions: usize, total_closures: usize) {
self.total_functions = total_functions;
self.total_closures = total_closures;
}
}
#[inline]
fn count_args<T: Checker>(params: &Node, code: &[u8]) -> usize {
params
.children()
.filter(|child| {
!T::is_non_arg(child) && !T::is_comment(child) && !T::is_empty_param_marker(child, code)
})
.count()
}
#[inline]
fn compute_args<T: Checker>(node: &Node, code: &[u8], nargs: &mut usize) {
if let Some(params) = node.child_by_field_name("parameters") {
if T::is_bare_param(¶ms) {
*nargs += 1;
return;
}
*nargs += count_args::<T>(¶ms, code);
} else if node.child_by_field_name("parameter").is_some() {
*nargs += 1;
}
}
#[doc(hidden)]
pub(crate) trait NArgs
where
Self: Checker,
Self: std::marker::Sized,
{
fn compute<'a>(node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
if Self::is_func_with_code(node, code, ancestors) {
compute_args::<Self>(
&Self::params_owner(node, ancestors),
code,
&mut stats.fn_nargs,
);
return;
}
if Self::is_closure(node, ancestors) {
compute_args::<Self>(
&Self::params_owner(node, ancestors),
code,
&mut stats.closure_nargs,
);
}
}
fn params_owner<'tree>(node: &Node<'tree>, _ancestors: Ancestors<'tree, '_>) -> Node<'tree> {
*node
}
}
impl NArgs for CppCode {
fn params_owner<'tree>(node: &Node<'tree>, _ancestors: Ancestors<'tree, '_>) -> Node<'tree> {
innermost_declarator::<Self>(node).unwrap_or(*node)
}
}
impl NArgs for CCode {
fn params_owner<'tree>(node: &Node<'tree>, _ancestors: Ancestors<'tree, '_>) -> Node<'tree> {
innermost_declarator::<Self>(node).unwrap_or(*node)
}
}
impl NArgs for MozcppCode {
fn params_owner<'tree>(node: &Node<'tree>, _ancestors: Ancestors<'tree, '_>) -> Node<'tree> {
innermost_declarator::<Self>(node).unwrap_or(*node)
}
}
impl NArgs for ObjcCode {
fn compute<'a>(node: &Node<'a>, code: &[u8], _ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
match node.kind_id().into() {
Objc::FunctionDefinition | Objc::FunctionDefinition2 => {
if let Some(owner) = innermost_declarator::<Self>(node) {
compute_args::<Self>(&owner, code, &mut stats.fn_nargs);
}
}
Objc::MethodDefinition => {
node.act_on_child(&mut |n| {
if matches!(
n.kind_id().into(),
Objc::MethodParameter | Objc::MethodParameter2
) {
stats.fn_nargs += 1;
}
});
}
Objc::BlockLiteral => {
if let Some(params) = node.first_child(|id| Objc::ParameterList == id) {
stats.closure_nargs += count_args::<Self>(¶ms, code);
}
}
_ => {}
}
}
}
fn compute_go_args(node: &Node, nargs: &mut usize) {
let Some(params) = node.child_by_field_name("parameters") else {
return;
};
*nargs += params
.children()
.map(|child| match child.kind_id().into() {
Go::ParameterDeclaration => child
.children()
.filter(|c| c.kind_id() == Go::Identifier)
.count()
.max(1),
Go::VariadicParameterDeclaration => 1,
_ => 0,
})
.sum::<usize>();
}
impl NArgs for GoCode {
fn compute<'a>(node: &Node<'a>, _code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
if Self::is_func(node, ancestors) {
compute_go_args(node, &mut stats.fn_nargs);
return;
}
if Self::is_closure(node, ancestors) {
compute_go_args(node, &mut stats.closure_nargs);
}
}
}
fn compute_kotlin_func_args(node: &Node, nargs: &mut usize) {
if let Some(params) = node
.children()
.find(|c| c.kind_id() == Kotlin::FunctionValueParameters)
{
params.act_on_child(&mut |n| {
if n.kind_id() == Kotlin::Parameter {
*nargs += 1;
}
});
}
}
fn compute_kotlin_lambda_args(node: &Node, code: &[u8], nargs: &mut usize) {
if let Some(params) = node
.children()
.find(|c| c.kind_id() == Kotlin::LambdaParameters)
{
*nargs += count_args::<KotlinCode>(¶ms, code);
}
}
impl NArgs for KotlinCode {
fn compute<'a>(node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
if Self::is_func(node, ancestors) {
compute_kotlin_func_args(node, &mut stats.fn_nargs);
return;
}
if Self::is_closure(node, ancestors) {
if node.kind_id() == Kotlin::LambdaLiteral {
compute_kotlin_lambda_args(node, code, &mut stats.closure_nargs);
} else {
compute_kotlin_func_args(node, &mut stats.closure_nargs);
}
}
}
}
fn compute_lua_args(node: &Node, nargs: &mut usize) {
let Some(params) = node.child_by_field_name("parameters") else {
return;
};
*nargs += params
.children()
.filter(|c| matches!(c.kind_id().into(), Lua::Identifier | Lua::VarargExpression))
.count();
}
impl NArgs for LuaCode {
fn compute<'a>(node: &Node<'a>, _code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
if Self::is_func(node, ancestors) {
compute_lua_args(node, &mut stats.fn_nargs);
} else if Self::is_closure(node, ancestors) {
compute_lua_args(node, &mut stats.closure_nargs);
}
}
}
fn compute_tcl_args(node: &Node, nargs: &mut usize) {
let Some(params) = node.child_by_field_name("arguments") else {
return;
};
*nargs += params
.children()
.filter(|c| c.kind_id() == Tcl::Argument)
.count();
}
impl NArgs for TclCode {
fn compute<'a>(node: &Node<'a>, _code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
if Self::is_func(node, ancestors) {
compute_tcl_args(node, &mut stats.fn_nargs);
}
}
}
fn compute_irules_args(node: &Node, nargs: &mut usize) {
let Some(params) = node.child_by_field_name("arguments") else {
return;
};
*nargs += params
.children()
.filter(|c| c.kind_id() == Irules::Argument)
.count();
}
impl NArgs for IrulesCode {
fn compute<'a>(node: &Node<'a>, _code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
if Self::is_func(node, ancestors) {
compute_irules_args(node, &mut stats.fn_nargs);
}
}
}
fn perl_signature<'a>(node: &Node<'a>) -> Option<Node<'a>> {
fn is_signature(id: u16) -> bool {
matches!(
id.into(),
Perl::FunctionSignature | Perl::FunctionSignature2
)
}
node.children().find_map(|child| {
if is_signature(child.kind_id()) {
Some(child)
} else if child.kind_id() == Perl::FunctionAttribute {
child.first_child(is_signature)
} else {
None
}
})
}
fn compute_perl_args(node: &Node, code: &[u8], nargs: &mut usize) {
let Some(signature) = perl_signature(node) else {
return;
};
*nargs += count_args::<PerlCode>(&signature, code);
}
impl NArgs for PerlCode {
fn compute<'a>(node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
if Self::is_func(node, ancestors) {
compute_perl_args(node, code, &mut stats.fn_nargs);
return;
}
if Self::is_closure(node, ancestors) {
compute_perl_args(node, code, &mut stats.closure_nargs);
}
}
}
fn elixir_declared_args(node: &Node, code: &[u8]) -> usize {
let Some(head) = elixir_arguments(node).and_then(|a| a.children().find(Node::is_named)) else {
return 0;
};
let head = elixir_unwrap_guard(&head, code);
match head.kind_id().into() {
Elixir::Call => elixir_arguments(&head).map_or(0, |p| count_args::<ElixirCode>(&p, code)),
Elixir::BinaryOperator => 2,
Elixir::UnaryOperator => 1,
_ => 0,
}
}
fn elixir_arguments<'a>(node: &Node<'a>) -> Option<Node<'a>> {
node.first_child(|id| id == Elixir::Arguments)
}
fn elixir_unwrap_guard<'a>(node: &Node<'a>, code: &[u8]) -> Node<'a> {
let is_when = node.kind_id() == Elixir::BinaryOperator
&& node
.child_by_field_name("operator")
.and_then(|op| op.utf8_text(code))
== Some("when");
if is_when {
node.child_by_field_name("left").unwrap_or(*node)
} else {
*node
}
}
impl NArgs for ElixirCode {
fn compute<'a>(node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
if Self::is_func_with_code(node, code, ancestors) {
stats.fn_nargs += elixir_declared_args(node, code);
return;
}
if Self::is_closure(node, ancestors)
&& let Some(clause) = node.first_child(|id| id == Elixir::StabClause)
&& let Some(params) = clause.child_by_field_name("left")
{
stats.closure_nargs +=
count_args::<ElixirCode>(&elixir_unwrap_guard(¶ms, code), code);
}
}
}
implement_metric_trait!(
[NArgs],
PythonCode,
MozjsCode,
JavascriptCode,
TypescriptCode,
TsxCode,
RustCode,
PreprocCode,
CcommentCode,
BashCode,
PhpCode,
CsharpCode,
RubyCode
);
fn java_compact_constructor_record<'tree>(
node: &Node<'tree>,
ancestors: Ancestors<'tree, '_>,
) -> Option<Node<'tree>> {
if node.kind_id() != Java::CompactConstructorDeclaration {
return None;
}
let (grandparent, _) = ancestors.iter(node).nth(1)?;
(grandparent.kind_id() == Java::RecordDeclaration).then_some(grandparent)
}
impl NArgs for JavaCode {
fn params_owner<'tree>(node: &Node<'tree>, ancestors: Ancestors<'tree, '_>) -> Node<'tree> {
java_compact_constructor_record(node, ancestors).unwrap_or(*node)
}
}
impl NArgs for GroovyCode {
fn compute<'a>(node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
use crate::languages::language_groovy::Groovy;
if Self::is_func(node, ancestors) {
compute_args::<Self>(node, code, &mut stats.fn_nargs);
return;
}
if Self::is_closure(node, ancestors)
&& let Some(params) = node.first_child(|id| id == Groovy::ClosureParameters)
{
params.act_on_child(&mut |n| {
if n.kind_id() == Groovy::ClosureParameter {
stats.closure_nargs += 1;
}
});
}
}
}
#[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::test_support::check_metrics_only_shim;
use super::*;
check_metrics_only_shim!(check_metrics, Nargs);
#[test]
fn nargs_empty_file_min_is_zero() {
let stats = Stats::default();
assert_eq!(stats.function_args_min(), 0);
assert_eq!(stats.closure_args_min(), 0);
}
#[test]
fn python_no_functions_and_closures() {
check_metrics::<PythonParser>("a = 42", "foo.py", |metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
});
}
#[test]
fn rust_no_functions_and_closures() {
check_metrics::<RustParser>("let a = 42;", "foo.rs", |metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
});
}
#[test]
fn cpp_no_functions_and_closures() {
check_metrics::<CppParser>("int a = 42;", "foo.cpp", |metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
});
}
#[test]
fn javascript_no_functions_and_closures() {
check_metrics::<JavascriptParser>("var a = 42;", "foo.js", |metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
});
}
#[test]
fn python_single_function() {
check_metrics::<PythonParser>(
"def f(a, b):
if a:
return a",
"foo.py",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn rust_single_function() {
check_metrics::<RustParser>(
"fn f(a: bool, b: usize) {
if a {
return a;
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn c_single_function() {
check_metrics::<CParser>(
"int f(int a, int b) {
if (a) {
return a;
}
}",
"foo.c",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn javascript_single_function() {
check_metrics::<JavascriptParser>(
"function f(a, b) {
return a * b;
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn python_single_lambda() {
check_metrics::<PythonParser>("bar = lambda a: True", "foo.py", |metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 1,
"function_args_average": 0.0,
"closure_args_average": 1.0,
"total": 1,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 1,
"closure_args_max": 1
}
"#
);
});
}
#[test]
fn rust_single_closure() {
check_metrics::<RustParser>("let bar = |i: i32| -> i32 { i + 1 };", "foo.rs", |metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 1,
"function_args_average": 0.0,
"closure_args_average": 1.0,
"total": 1,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 1
}
"#
);
});
}
#[test]
fn cpp_single_lambda() {
check_metrics::<CppParser>(
"auto bar = [](int x, int y) -> int { return x + y; };",
"foo.cpp",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 2,
"function_args_average": 0.0,
"closure_args_average": 2.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 2,
"closure_args_max": 2
}
"#
);
},
);
}
#[test]
fn javascript_single_closure() {
check_metrics::<JavascriptParser>("function (a, b) {return a + b};", "foo.js", |metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 2,
"function_args_average": 0.0,
"closure_args_average": 2.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 2
}
"#
);
});
}
#[test]
fn python_functions() {
check_metrics::<PythonParser>(
"def f(a, b):
if a:
return a
def f(a, b):
if b:
return b",
"foo.py",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 4,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 4,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
check_metrics::<PythonParser>(
"def f(a, b):
if a:
return a
def f(a, b, c):
if b:
return b",
"foo.py",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 5,
"closure_args": 0,
"function_args_average": 2.5,
"closure_args_average": 0.0,
"total": 5,
"average": 2.5,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn rust_functions() {
check_metrics::<RustParser>(
"fn f(a: bool, b: usize) {
if a {
return a;
}
}
fn f1(a: bool, b: usize) {
if a {
return a;
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 4,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 4,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
check_metrics::<RustParser>(
"fn f(a: bool, b: usize) {
if a {
return a;
}
}
fn f1(a: bool, b: usize, c: usize) {
if a {
return a;
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 5,
"closure_args": 0,
"function_args_average": 2.5,
"closure_args_average": 0.0,
"total": 5,
"average": 2.5,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn rust_method_excludes_self_receiver() {
check_metrics::<RustParser>(
"struct S;
impl S {
fn a(self) {} // self -> 0 args
fn b(&self, x: i32) {} // &self + 1 -> 1 arg
fn c(&mut self, x: i32, y: i32) {} // &mut self + 2 -> 2 args
}",
"foo.rs",
|metric| {
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 3);
assert_eq!(s.function_args_max(), 2);
},
);
check_metrics::<RustParser>(
"use std::rc::Rc;
use std::pin::Pin;
struct S;
impl S {
fn a(self: Box<Self>, x: i32, y: i32) {} // receiver + 2 -> 2
fn b(self: Rc<Self>, x: i32) {} // receiver + 1 -> 1
fn c(self: Pin<&mut Self>) {} // receiver -> 0
}",
"foo.rs",
|metric| {
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 3);
assert_eq!(s.function_args_max(), 2);
},
);
}
#[test]
fn c_functions() {
check_metrics::<CParser>(
"int f(int a, int b) {
if (a) {
return a;
}
}
int f1(int a, int b) {
if (a) {
return a;
}
}",
"foo.c",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 4,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 4,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
check_metrics::<CppParser>(
"int f(int a, int b) {
if (a) {
return a;
}
}
int f1(int a, int b, int c) {
if (a) {
return a;
}
}",
"foo.c",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 5,
"closure_args": 0,
"function_args_average": 2.5,
"closure_args_average": 0.0,
"total": 5,
"average": 2.5,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn javascript_functions() {
check_metrics::<JavascriptParser>(
"function f(a, b) {
return a * b;
}
function f1(a, b) {
return a * b;
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 4,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 4,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
check_metrics::<JavascriptParser>(
"function f(a, b) {
return a * b;
}
function f1(a, b, c) {
return a * b;
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 5,
"closure_args": 0,
"function_args_average": 2.5,
"closure_args_average": 0.0,
"total": 5,
"average": 2.5,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn python_nested_functions() {
check_metrics::<PythonParser>(
"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",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 3,
"closure_args": 2,
"function_args_average": 1.5,
"closure_args_average": 1.0,
"total": 5,
"average": 1.25,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 2
}
"#
);
},
);
}
#[test]
fn rust_nested_functions() {
check_metrics::<RustParser>(
"fn f(a: i32, b: i32) -> i32 {
fn foo(a: i32) -> i32 {
return a;
}
let bar = |a: i32, b: i32| -> i32 { a + 1 };
let bar1 = |b: i32| -> i32 { b + 1 };
return bar(foo(a), a);
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 3,
"closure_args": 3,
"function_args_average": 1.5,
"closure_args_average": 1.5,
"total": 6,
"average": 1.5,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 2
}
"#
);
},
);
}
#[test]
fn cpp_nested_functions() {
check_metrics::<CppParser>(
"int f(int a, int b, int c) {
auto foo = [](int x) -> int { return x; };
auto bar = [](int x, int y) -> int { return x + y; };
return bar(foo(a), a);
}",
"foo.cpp",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 3,
"closure_args": 3,
"function_args_average": 3.0,
"closure_args_average": 1.5,
"total": 6,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 3
}
"#
);
},
);
}
#[test]
fn cpp_default_arguments() {
check_metrics::<CppParser>(
"int f(int a, int b, int c = 0) {
return a + b + c;
}",
"foo.cpp",
|metric| {
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 3);
assert_eq!(s.function_args_max(), 3);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 3,
"closure_args": 0,
"function_args_average": 3.0,
"closure_args_average": 0.0,
"total": 3,
"average": 3.0,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn c_variadic_function() {
check_metrics::<CParser>(
"int printf(const char* fmt, ...) {
return 0;
}",
"foo.c",
|metric| {
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 2);
assert_eq!(s.function_args_max(), 2);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn cpp_template_parameter_pack() {
check_metrics::<CppParser>(
"template<typename... Args>
int sum(int seed, Args... args) {
return seed;
}",
"foo.cpp",
|metric| {
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 2);
assert_eq!(s.function_args_max(), 2);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn cpp_lambda_capture_not_counted() {
check_metrics::<CppParser>(
"int f() {
int x = 1;
int y = 2;
auto g = [=, &x](int a, int b) -> int { return a + b + x + y; };
return g(1, 2);
}",
"foo.cpp",
|metric| {
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 0);
assert_eq!(s.closure_args_sum(), 2);
assert_eq!(s.closure_args_max(), 2);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 2,
"function_args_average": 0.0,
"closure_args_average": 2.0,
"total": 2,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 2
}
"#
);
},
);
}
#[test]
fn cpp_member_function_this_not_counted() {
check_metrics::<CppParser>(
"struct S {
int x;
int set(int a) { // implicit `this` is NOT counted
this->x = a;
return a;
}
};",
"foo.cpp",
|metric| {
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 1);
assert_eq!(s.function_args_max(), 1);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 1,
"closure_args": 0,
"function_args_average": 1.0,
"closure_args_average": 0.0,
"total": 1,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 1,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn go_zero_args() {
check_metrics::<GoParser>(
"package main
func f() {}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn go_multiple_args() {
check_metrics::<GoParser>(
"package main
func f(a int, b string, c bool) {}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 3,
"closure_args": 0,
"function_args_average": 3.0,
"closure_args_average": 0.0,
"total": 3,
"average": 3.0,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn go_method_excludes_receiver() {
check_metrics::<GoParser>(
"package main
type T struct{}
func (t *T) Greet(name string) string {
return name
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 1,
"closure_args": 0,
"function_args_average": 1.0,
"closure_args_average": 0.0,
"total": 1,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 1,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn go_variadic() {
check_metrics::<GoParser>(
"package main
func f(args ...int) {}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 1,
"closure_args": 0,
"function_args_average": 1.0,
"closure_args_average": 0.0,
"total": 1,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 1,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn go_grouped_params() {
check_metrics::<GoParser>(
"package main
func f(a, b int, c string) {}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 3,
"closure_args": 0,
"function_args_average": 3.0,
"closure_args_average": 0.0,
"total": 3,
"average": 3.0,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn go_func_literal_args() {
check_metrics::<GoParser>(
"package main
var f = func(x, y int) int { return x + y }",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 2,
"function_args_average": 0.0,
"closure_args_average": 2.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 2
}
"#
);
},
);
}
#[test]
fn javascript_nested_functions() {
check_metrics::<JavascriptParser>(
"function f(a, b) {
function foo(a, c) {
return a;
}
var bar = function (a, b) {return a + b};
function (a) {return a};
return bar(foo(a), a);
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 6,
"closure_args": 1,
"function_args_average": 2.0,
"closure_args_average": 1.0,
"total": 7,
"average": 1.75,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 1
}
"#
);
},
);
}
#[test]
fn perl_no_functions_and_closures() {
check_metrics::<PerlParser>(
"my $x = 1;
print $x;",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 0);
assert_eq!(metric.nom.closures_sum(), 0);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn perl_single_function() {
check_metrics::<PerlParser>(
"sub greet {
my ($name) = @_;
print \"hi $name\";
}",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 1);
assert_eq!(metric.nom.closures_sum(), 0);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn perl_single_closure() {
check_metrics::<PerlParser>(
"my $f = sub {
my ($x) = @_;
return $x + 1;
};",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 0);
assert_eq!(metric.nom.closures_sum(), 1);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn perl_multiple_functions() {
check_metrics::<PerlParser>(
"sub a { return 1; }
sub b {
my ($x, $y) = @_;
return $x + $y;
}",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 2);
assert_eq!(metric.nom.closures_sum(), 0);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn perl_nested_closure() {
check_metrics::<PerlParser>(
"sub outer {
my $inner = sub { return 42; };
return $inner->();
}",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 1);
assert_eq!(metric.nom.closures_sum(), 1);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn perl_signature_function() {
check_metrics::<PerlParser>(
"use feature 'signatures';
sub add($x, $y) { return $x + $y; }",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 1);
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 2);
assert_eq!(s.function_args_max(), 2);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn perl_signature_defaults_and_slurpy() {
check_metrics::<PerlParser>(
"use feature 'signatures';
sub deflt($x, $y = 5, @rest) { return $x; }",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 1);
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 3);
assert_eq!(s.function_args_max(), 3);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 3,
"closure_args": 0,
"function_args_average": 3.0,
"closure_args_average": 0.0,
"total": 3,
"average": 3.0,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn perl_signature_and_at_underscore_mixed() {
check_metrics::<PerlParser>(
"use feature 'signatures';
sub sig($x, $y, $z) { return $x; }
sub legacy { my ($a) = @_; return $a; }",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 2);
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 3);
assert_eq!(s.function_args_max(), 3);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 3,
"closure_args": 0,
"function_args_average": 1.5,
"closure_args_average": 0.0,
"total": 3,
"average": 1.5,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn perl_signature_behind_attribute() {
check_metrics::<PerlParser>(
"use feature 'signatures';
sub attrs :lvalue ($z) { return $z; }",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 1);
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 1);
assert_eq!(s.function_args_max(), 1);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 1,
"closure_args": 0,
"function_args_average": 1.0,
"closure_args_average": 0.0,
"total": 1,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 1,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn perl_signature_comments_are_not_parameters() {
check_metrics::<PerlParser>(
"use feature 'signatures';
sub documented(
$host, # hostname to connect to
$port, # TCP port
$timeout, # seconds
) { return $host; }",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 1);
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 3);
assert_eq!(s.function_args_max(), 3);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 3,
"closure_args": 0,
"function_args_average": 3.0,
"closure_args_average": 0.0,
"total": 3,
"average": 3.0,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn perl_empty_signature_and_prototype_are_zero() {
check_metrics::<PerlParser>(
"use feature 'signatures';
sub empty() { return 1; }
sub proto($$) { return 1; }",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 2);
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 0);
assert_eq!(s.function_args_max(), 0);
},
);
}
#[test]
fn perl_method_signature_function() {
check_metrics::<PerlParser>(
"use v5.38;
class Point {
method shift_by($dx, $dy) { return $dx; }
}",
"foo.pl",
|metric| {
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 2);
assert_eq!(s.function_args_max(), 2);
},
);
}
#[test]
fn perl_hidden_function_signature_is_unreachable() {
let mut hidden = false;
let mut emitted = false;
crate::test_support::for_each_node_with_chain::<PerlCode>(
b"use feature 'signatures';\nsub add($x, $y) { return $x + $y; }\n",
|node, _| {
hidden |= node.kind_id() == Perl::FunctionSignature2 as u16;
emitted |= node.kind_id() == Perl::FunctionSignature as u16;
},
);
assert!(
emitted,
"fixture must reach a real `function_signature`, else the \
hidden-rule check below is vacuous"
);
assert!(
!hidden,
"grammar now emits the hidden `_function_signature`; re-check \
the defensive arm in `perl_signature`"
);
}
#[test]
fn perl_anonymous_sub_signature_is_zero() {
check_metrics::<PerlParser>(
"use feature 'signatures';
my $mul = sub ($p, $q) { return $p * $q; };",
"foo.pl",
|metric| {
assert_eq!(metric.nom.functions_sum(), 0);
assert_eq!(metric.nom.closures_sum(), 1);
let s = &metric.nargs;
assert_eq!(s.closure_args_sum(), 0);
assert_eq!(s.closure_args_max(), 0);
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn java_no_functions() {
check_metrics::<JavaParser>(
"class Foo {
int x = 42;
String name = \"hello\";
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn java_single_method() {
check_metrics::<JavaParser>(
"class Foo {
void greet(String name, int count) {
return;
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn java_multiple_methods() {
check_metrics::<JavaParser>(
"class Foo {
void a(int x) {
return;
}
void b(int x, int y, int z) {
return;
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 4,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 4,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn java_constructor_args() {
check_metrics::<JavaParser>(
"class Foo {
Foo(String name, int age) {
return;
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn java_record_compact_constructor_counts_record_components() {
check_metrics::<JavaParser>(
"record Pair(int a, int b) {
Pair { }
record Single(int c) {
Single { }
}
}",
"foo.java",
|metric| {
let s = &metric.nargs;
assert_eq!(metric.nom.functions_sum(), 2);
assert_eq!(s.function_args_sum(), 3);
assert_eq!(s.function_args_max(), 2);
assert_eq!(s.closure_args_sum(), 0);
},
);
}
#[test]
fn java_method_excludes_explicit_receiver() {
check_metrics::<JavaParser>(
"class S {
void m(S this, int a) {} // receiver + 1 -> 1 arg
void n(int a, int b) {} // control: 2 real params
void r(S this) {} // receiver only -> 0 args
}",
"foo.java",
|metric| {
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 3);
assert_eq!(s.function_args_max(), 2);
},
);
}
#[test]
fn java_lambda_args() {
check_metrics::<JavaParser>(
"class Foo {
void run() {
Runnable r = (int a, int b) -> a + b;
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 2,
"function_args_average": 0.0,
"closure_args_average": 2.0,
"total": 2,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 2
}
"#
);
},
);
}
#[test]
fn groovy_no_functions_and_closures() {
check_metrics::<GroovyParser>("int x = 1", "foo.groovy", |metric| {
assert_eq!(metric.nargs.total(), 0);
});
}
#[test]
fn groovy_single_method() {
check_metrics::<GroovyParser>(
"class A {
void greet(String name, int times) {
println(name)
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nargs.closure_args_sum(), 0);
},
);
}
#[test]
fn groovy_multiple_methods() {
check_metrics::<GroovyParser>(
"class A {
int add(int x, int y) { x + y }
int sub(int x, int y, int z) { x - y - z }
}",
"foo.groovy",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 5);
},
);
}
#[test]
fn groovy_lambda_args() {
check_metrics::<GroovyParser>(
"class Foo {
void run() {
def f = { int a, int b -> a + b }
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.nargs.closure_args_sum(), 2);
},
);
}
#[test]
fn groovy_implicit_it_not_counted() {
check_metrics::<GroovyParser>(
"class A {
void apply() {
[1, 2, 3].each { println(it) }
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.nargs.closure_args_sum(), 0);
},
);
}
#[test]
fn csharp_no_functions() {
check_metrics::<CsharpParser>(
"class Foo {
int x = 42;
string Name = \"hello\";
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn csharp_single_method() {
check_metrics::<CsharpParser>(
"class Foo {
void Greet(string name, int count) {
return;
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn csharp_multiple_methods() {
check_metrics::<CsharpParser>(
"class Foo {
void A(int x) {
return;
}
void B(int x, int y, int z) {
return;
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 4,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 4,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn csharp_constructor_args() {
check_metrics::<CsharpParser>(
"class Foo {
public Foo(string name, int age) {
return;
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn csharp_lambda_args() {
check_metrics::<CsharpParser>(
"class Foo {
void Run() {
System.Func<int, int, int> f = (int a, int b) => a + b;
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 2,
"function_args_average": 0.0,
"closure_args_average": 2.0,
"total": 2,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 2
}
"#
);
},
);
}
#[test]
fn tsx_function_and_arrow() {
check_metrics::<TsxParser>(
"function add(a: number, b: number): number {
return a + b;
}
const multiply = (x: number, y: number) => x * y;",
"foo.tsx",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 4,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 4,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn typescript_typed_and_optional_params() {
check_metrics::<TypescriptParser>(
"function format(value: number, prefix?: string, suffix?: string): string {
return (prefix ?? '') + value.toString() + (suffix ?? '');
}
const identity = (x: number): number => x;",
"foo.ts",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 4,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 4,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn mozjs_single_function() {
check_metrics::<MozjsParser>(
"function f(a, b) {
return a * b;
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn mozjs_closure_args() {
check_metrics::<MozjsParser>("function (a, b) {return a + b};", "foo.js", |metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 2,
"function_args_average": 0.0,
"closure_args_average": 2.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 2
}
"#
);
});
}
#[test]
fn javascript_bare_arrow_function() {
check_metrics::<JavascriptParser>("const f = x => x;", "foo.js", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn javascript_async_bare_arrow_function() {
check_metrics::<JavascriptParser>("const f = async x => x;", "foo.js", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn javascript_parenthesized_arrow_function() {
check_metrics::<JavascriptParser>("const f = (x) => x;", "foo.js", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn javascript_multi_parenthesized_arrow_function() {
check_metrics::<JavascriptParser>("const f = (x, y) => x + y;", "foo.js", |metric| {
assert_eq!(metric.nargs.total(), 2);
});
}
#[test]
fn typescript_bare_arrow_function() {
check_metrics::<TypescriptParser>("const f = x => x;", "foo.ts", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn typescript_async_bare_arrow_function() {
check_metrics::<TypescriptParser>("const f = async x => x;", "foo.ts", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn typescript_parenthesized_arrow_function() {
check_metrics::<TypescriptParser>("const f = (x: number) => x;", "foo.ts", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn typescript_multi_parenthesized_arrow_function() {
check_metrics::<TypescriptParser>(
"const f = (x: number, y: number) => x + y;",
"foo.ts",
|metric| {
assert_eq!(metric.nargs.total(), 2);
},
);
}
#[test]
fn tsx_bare_arrow_function() {
check_metrics::<TsxParser>("const f = x => x;", "foo.tsx", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn tsx_async_bare_arrow_function() {
check_metrics::<TsxParser>("const f = async x => x;", "foo.tsx", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn tsx_parenthesized_arrow_function() {
check_metrics::<TsxParser>("const f = (x: number) => x;", "foo.tsx", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn tsx_multi_parenthesized_arrow_function() {
check_metrics::<TsxParser>(
"const f = (x: number, y: number) => x + y;",
"foo.tsx",
|metric| {
assert_eq!(metric.nargs.total(), 2);
},
);
}
#[test]
fn mozjs_bare_arrow_function() {
check_metrics::<MozjsParser>("const f = x => x;", "foo.js", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn mozjs_async_bare_arrow_function() {
check_metrics::<MozjsParser>("const f = async x => x;", "foo.js", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn mozjs_parenthesized_arrow_function() {
check_metrics::<MozjsParser>("const f = (x) => x;", "foo.js", |metric| {
assert_eq!(metric.nargs.total(), 1);
});
}
#[test]
fn mozjs_multi_parenthesized_arrow_function() {
check_metrics::<MozjsParser>("const f = (x, y) => x + y;", "foo.js", |metric| {
assert_eq!(metric.nargs.total(), 2);
});
}
#[test]
fn kotlin_nargs_functions_and_closures() {
check_metrics::<KotlinParser>(
"fun add(a: Int, b: Int): Int {
val transform = { x: Int, y: Int -> x + y }
return transform(a, b)
}",
"foo.kt",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 2,
"function_args_average": 2.0,
"closure_args_average": 2.0,
"total": 4,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 2
}
"#
);
},
);
}
#[test]
fn lua_no_functions_and_closures() {
check_metrics::<LuaParser>("local x = 1", "foo.lua", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
});
}
#[test]
fn lua_single_function() {
check_metrics::<LuaParser>("function f(a, b) return a + b end", "foo.lua", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
});
}
#[test]
fn lua_single_closure() {
check_metrics::<LuaParser>(
"local f = function(a, b) return a + b end",
"foo.lua",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 2);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn lua_functions() {
check_metrics::<LuaParser>(
"function f(a) return a end
function g(x, y, z) return x + y + z end",
"foo.lua",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 4);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn lua_vararg_function() {
check_metrics::<LuaParser>("function f(a, ...) return a end", "foo.lua", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
});
}
#[test]
fn lua_colon_method_nargs() {
check_metrics::<LuaParser>(
"function obj:method(a, b) return a + b end",
"foo.lua",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn tcl_no_functions() {
check_metrics::<TclParser>("set x 1", "foo.tcl", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
});
}
#[test]
fn tcl_single_function() {
check_metrics::<TclParser>("proc f {a b} { puts $a }", "foo.tcl", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
});
}
#[test]
fn tcl_single_function_no_args() {
check_metrics::<TclParser>("proc f {} { puts hello }", "foo.tcl", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
});
}
#[test]
fn tcl_functions() {
check_metrics::<TclParser>(
"proc f {a b} { puts $a }
proc g {x y z} { puts $x }",
"foo.tcl",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 5);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn tcl_nested_functions() {
check_metrics::<TclParser>(
"proc outer {a} {
proc inner {x y} { puts $x }
inner $a $a
}",
"foo.tcl",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 3);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn tcl_args_vararg() {
check_metrics::<TclParser>("proc f {a b args} { puts $a }", "foo.tcl", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 3);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
});
}
#[test]
fn tcl_default_arg() {
check_metrics::<TclParser>(
"proc greet {{name World} greeting} {
puts \"$greeting, $name!\"
}",
"foo.tcl",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn kotlin_zero_args() {
check_metrics::<KotlinParser>("fun f(): Int { return 42 }", "foo.kt", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
});
}
#[test]
fn kotlin_single_arg() {
check_metrics::<KotlinParser>(
"fun double(x: Int): Int { return x * 2 }",
"foo.kt",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 1);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn kotlin_multiple_args() {
check_metrics::<KotlinParser>(
"fun add(a: Int, b: Int, c: Int): Int { return a + b + c }",
"foo.kt",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 3);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn kotlin_default_args() {
check_metrics::<KotlinParser>(
"fun greet(name: String = \"World\", greeting: String = \"Hello\"): String {
return \"$greeting, $name!\"
}",
"foo.kt",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nargs.closure_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn kotlin_empty_lambda() {
check_metrics::<KotlinParser>(
"fun f() {
val two = { x: Int, y: Int -> x + y }
val zero = { -> 42 }
}",
"foo.kt",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 2);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn kotlin_anonymous_function() {
check_metrics::<KotlinParser>(
"val add = fun(x: Int, y: Int): Int = x + y",
"foo.kt",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 2);
insta::assert_json_snapshot!(metric.nargs);
},
);
}
#[test]
fn php_no_functions_and_closures() {
check_metrics::<PhpParser>("<?php $a = 42;", "foo.php", |metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
});
}
#[test]
fn php_single_function() {
check_metrics::<PhpParser>(
"<?php
function f(bool $a, int $b): bool {
if ($a) { return $a; }
return false;
}",
"foo.php",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn php_single_closure() {
check_metrics::<PhpParser>(
"<?php
$f = function (int $a, int $b) { return $a + $b; };
$g = fn (int $x) => $x * 2;",
"foo.php",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 0,
"closure_args": 3,
"function_args_average": 0.0,
"closure_args_average": 1.5,
"total": 3,
"average": 1.5,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 2
}
"#
);
},
);
}
#[test]
fn php_functions() {
check_metrics::<PhpParser>(
"<?php
function a(int $x): int { return $x; }
function b(int $x, int $y): int { return $x + $y; }",
"foo.php",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 3,
"closure_args": 0,
"function_args_average": 1.5,
"closure_args_average": 0.0,
"total": 3,
"average": 1.5,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#
);
},
);
}
#[test]
fn php_nested_functions() {
check_metrics::<PhpParser>(
"<?php
class A {
public function outer(int $a): int {
$f = function (int $b) use ($a) { return $a + $b; };
return $f($a);
}
}",
"foo.php",
|metric| {
insta::assert_json_snapshot!(
metric.nargs,
@r#"
{
"function_args": 1,
"closure_args": 1,
"function_args_average": 1.0,
"closure_args_average": 1.0,
"total": 2,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 1,
"closure_args_min": 0,
"closure_args_max": 1
}
"#
);
},
);
}
#[test]
fn elixir_named_function_args() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def bar(a, b, c) do\n a + b + c\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nom.functions_sum(), 1);
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 3);
assert_eq!(s.function_args_max(), 3);
},
);
}
#[test]
fn elixir_guarded_clause_args() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n defp baz(x) when is_integer(x), do: x\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nom.functions_sum(), 1);
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 1);
assert_eq!(s.function_args_max(), 1);
},
);
}
#[test]
fn elixir_zero_arg_function_has_no_parameter_list() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def noargs, do: 1\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nom.functions_sum(), 1);
assert_eq!(metric.nargs.function_args_sum(), 0);
},
);
}
#[test]
fn elixir_pattern_and_default_args() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(%{a: x}, b \\\\ 1), do: {x, b}\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nom.functions_sum(), 1);
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 2);
assert_eq!(s.function_args_max(), 2);
},
);
}
#[test]
fn elixir_multi_clause_closure_counts_one_clause() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def run do\n fn\n a, b -> a + b\n a, _ -> a\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nom.closures_sum(), 1);
let s = &metric.nargs;
assert_eq!(s.closure_args_sum(), 2);
assert_eq!(s.closure_args_max(), 2);
},
);
}
#[test]
fn elixir_guarded_closure_args() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def run do\n fn a, b, c, d when is_integer(a) -> a + b + c + d end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nom.closures_sum(), 1);
let s = &metric.nargs;
assert_eq!(s.closure_args_sum(), 4);
assert_eq!(s.closure_args_max(), 4);
},
);
}
#[test]
fn elixir_operator_definition_args() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def a + b, do: {a, b}\n def -a, do: a\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nom.functions_sum(), 2);
let s = &metric.nargs;
assert_eq!(s.function_args_sum(), 3);
assert_eq!(s.function_args_max(), 2);
},
);
}
#[test]
fn elixir_quoted_def_contributes_no_args() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n defmacro mac do\n quote do\n def generated(p, q, r), do: p + q + r\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nom.functions_sum(), 1);
},
);
}
#[test]
fn elixir_non_method_macros_count_zero() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n defdelegate log(msg), to: Logger\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 0);
},
);
}
#[test]
fn ruby_no_functions_and_closures() {
check_metrics::<RubyParser>("a = 42\n", "foo.rb", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 0);
});
}
#[test]
fn ruby_single_function() {
check_metrics::<RubyParser>("def foo(a, b, c)\n a + b + c\nend\n", "foo.rb", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 3);
assert_eq!(metric.nargs.closure_args_sum(), 0);
});
}
#[test]
fn ruby_single_closure() {
check_metrics::<RubyParser>("[1, 2, 3].each { |x| puts x }\n", "foo.rb", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 1);
});
}
#[test]
fn ruby_functions() {
check_metrics::<RubyParser>(
"def add(a, b)\n a + b\nend\n\ndef neg(x)\n -x\nend\n\nf = ->(a, b) { a * b }\n",
"foo.rb",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 3);
assert_eq!(metric.nargs.closure_args_sum(), 2);
},
);
}
#[test]
fn ruby_nested_functions() {
check_metrics::<RubyParser>(
"def outer(a)\n def inner(b, c)\n b + c\n end\n inner(a, a)\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 3);
assert_eq!(metric.nargs.closure_args_sum(), 0);
},
);
}
#[test]
fn python_both_parameter_separators() {
check_metrics::<PythonParser>(
"def f(pos_only, /, normal, *, kw_only): pass",
"foo.py",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 3);
assert_eq!(metric.nargs.closure_args_sum(), 0);
},
);
}
#[test]
fn python_positional_separator_only() {
check_metrics::<PythonParser>("def f(a, b, /): pass", "foo.py", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nargs.closure_args_sum(), 0);
});
}
#[test]
fn python_keyword_separator_only() {
check_metrics::<PythonParser>("def f(*, a, b): pass", "foo.py", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nargs.closure_args_sum(), 0);
});
}
#[test]
fn python_lambda_keyword_separator() {
check_metrics::<PythonParser>("g = lambda a, *, b: a", "foo.py", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 2);
});
}
#[test]
fn python_args_kwargs_still_counted() {
check_metrics::<PythonParser>("def f(a, *args, **kwargs): pass", "foo.py", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 3);
assert_eq!(metric.nargs.closure_args_sum(), 0);
});
}
#[test]
fn irules_no_functions_and_closures() {
check_metrics::<IrulesParser>("set x 1\nlog local0. $x\n", "foo.irule", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 0);
});
}
#[test]
fn irules_handler_zero_args() {
check_metrics::<IrulesParser>(
"when HTTP_REQUEST { log local0. \"hit\" }\n",
"foo.irule",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
assert_eq!(metric.nargs.closure_args_sum(), 0);
assert_eq!(metric.nom.functions_sum(), 1);
},
);
}
#[test]
fn irules_single_proc() {
check_metrics::<IrulesParser>("proc f { a b } { return $a }\n", "foo.irule", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nargs.closure_args_sum(), 0);
});
}
#[test]
fn irules_proc_no_args() {
check_metrics::<IrulesParser>("proc f { } { return 1 }\n", "foo.irule", |metric| {
assert_eq!(metric.nargs.function_args_sum(), 0);
});
}
#[test]
fn irules_proc_arg_defaults() {
check_metrics::<IrulesParser>(
"proc f { a {b 5} c } { return $a }\n",
"foo.irule",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 3);
},
);
}
#[test]
fn irules_multiple_functions() {
check_metrics::<IrulesParser>(
"proc add { a b } { return [expr { $a + $b }] }
when HTTP_REQUEST { log local0. \"hit\" }
",
"foo.irule",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
assert_eq!(metric.nom.functions_sum(), 2);
},
);
}
#[test]
fn objc_no_args() {
check_metrics::<ObjcParser>(
"@implementation Foo
- (void)foo {
[self doWork];
}
@end
",
"foo.m",
|metric| {
assert_eq!(metric.nargs.total(), 0);
insta::assert_json_snapshot!(metric.nargs, @r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#);
},
);
}
#[test]
fn objc_method_two_args() {
check_metrics::<ObjcParser>(
"@implementation Foo
- (void)foo:(int)a bar:(int)b {
[self use:a];
}
@end
",
"foo.m",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 2);
insta::assert_json_snapshot!(metric.nargs, @r#"
{
"function_args": 2,
"closure_args": 0,
"function_args_average": 2.0,
"closure_args_average": 0.0,
"total": 2,
"average": 2.0,
"function_args_min": 0,
"function_args_max": 2,
"closure_args_min": 0,
"closure_args_max": 0
}
"#);
},
);
}
#[test]
fn objc_function_args() {
check_metrics::<ObjcParser>(
"void f(int a, int b, int c) {
return;
}
",
"foo.m",
|metric| {
assert_eq!(metric.nargs.function_args_sum(), 3);
insta::assert_json_snapshot!(metric.nargs, @r#"
{
"function_args": 3,
"closure_args": 0,
"function_args_average": 3.0,
"closure_args_average": 0.0,
"total": 3,
"average": 3.0,
"function_args_min": 0,
"function_args_max": 3,
"closure_args_min": 0,
"closure_args_max": 0
}
"#);
},
);
}
#[test]
fn objc_block_args() {
check_metrics::<ObjcParser>(
"@implementation Foo
- (void)bar {
void (^blk)(int, int) = ^(int x, int y){
[self use:x];
};
blk(1, 2);
}
@end
",
"foo.m",
|metric| {
assert_eq!(metric.nargs.closure_args_sum(), 2);
insta::assert_json_snapshot!(metric.nargs, @r#"
{
"function_args": 0,
"closure_args": 2,
"function_args_average": 0.0,
"closure_args_average": 2.0,
"total": 2,
"average": 1.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 2
}
"#);
},
);
}
#[test]
fn objc_block_void_marker_is_not_a_parameter() {
check_metrics::<ObjcParser>(
"void host(void) {
void (^empty)(void) = ^(void){ };
empty();
}
",
"foo.m",
|metric| {
assert_eq!(metric.nargs.closure_args_sum(), 0);
assert_eq!(metric.nargs.function_args_sum(), 0);
insta::assert_json_snapshot!(metric.nargs, @r#"
{
"function_args": 0,
"closure_args": 0,
"function_args_average": 0.0,
"closure_args_average": 0.0,
"total": 0,
"average": 0.0,
"function_args_min": 0,
"function_args_max": 0,
"closure_args_min": 0,
"closure_args_max": 0
}
"#);
},
);
}
#[test]
fn objc_block_comment_is_not_a_parameter() {
check_metrics::<ObjcParser>(
"void host(void) {
void (^two)(int, int) = ^(int a /* c */, int b){ };
two(1, 2);
}
",
"foo.m",
|metric| {
assert_eq!(metric.nargs.closure_args_sum(), 2);
assert_eq!(metric.nargs.function_args_sum(), 0);
},
);
}
#[test]
fn objc_block_variadic_parameter_still_counts() {
check_metrics::<ObjcParser>(
"void host(void) {
void (^var)(int, ...) = ^(int a, ...){ };
var(1);
}
",
"foo.m",
|metric| {
assert_eq!(metric.nargs.closure_args_sum(), 2);
assert_eq!(metric.nargs.function_args_sum(), 0);
},
);
}
#[test]
fn objc_block_without_a_parameter_list_is_zero() {
check_metrics::<ObjcParser>(
"void host(void) {
void (^none)(void) = ^{ };
none();
}
",
"foo.m",
|metric| {
assert_eq!(metric.nargs.closure_args_sum(), 0);
assert_eq!(metric.nargs.function_args_sum(), 0);
},
);
}
#[test]
fn display_headline_matches_sum_for_nested_functions() {
check_metrics::<PythonParser>(
"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",
|metric| {
let stats = &metric.nargs;
assert_eq!(stats.function_args_sum(), 3);
assert_eq!(stats.closure_args_sum(), 2);
let rendered = stats.to_string();
assert!(
rendered.starts_with(&format!(
"function_args: {}, closure_args: {},",
stats.function_args_sum(),
stats.closure_args_sum()
)),
"Display headline diverged from the summed accessors: {rendered}"
);
},
);
}
}
#[cfg(test)]
mod lambda_parenthesisation_parity {
use crate::test_support::metrics_verbatim;
use crate::{LANG, MetricsOptions};
fn args(lang: LANG, source: &str) -> (u64, u64) {
let m = metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default());
(m.nargs.closure_args_sum(), m.nargs.function_args_sum())
}
fn cases(lang: LANG) -> Option<[&'static str; 4]> {
Some(match lang {
LANG::Java => [
"class K{ void f(){ Function<Integer,Integer> a = x -> x + 1; } }",
"class K{ void f(){ Function<Integer,Integer> a = (x) -> x + 1; } }",
"class K{ void f(){ BiFunction<Integer,Integer,Integer> c = (x, y) -> x + y; } }",
"class K{ void f(){ Supplier<Integer> d = () -> 1; } }",
],
LANG::Csharp => [
"class K{ void f(){ Func<int,int> a = x => x + 1; } }",
"class K{ void f(){ Func<int,int> a = (x) => x + 1; } }",
"class K{ void f(){ Func<int,int,int> c = (x, y) => x + y; } }",
"class K{ void f(){ Func<int> d = () => 1; } }",
],
LANG::Javascript | LANG::Typescript | LANG::Tsx | LANG::Mozjs => [
"function f(){ var a = x => x + 1; }",
"function f(){ var a = (x) => x + 1; }",
"function f(){ var c = (x, y) => x + y; }",
"function f(){ var d = () => 1; }",
],
_ => return None,
})
}
#[test]
fn optional_parentheses_do_not_change_the_count() {
let mut checked = 0;
for lang in LANG::into_enum_iter() {
if !lang.is_enabled() {
continue;
}
let Some([bare, paren, two, zero]) = cases(lang) else {
continue;
};
checked += 1;
let (bare_args, paren_args) = (args(lang, bare), args(lang, paren));
assert_eq!(
bare_args, paren_args,
"{lang:?}: the parentheses changed the argument count\n bare: {bare}\n paren: {paren}"
);
assert_eq!(
bare_args.0 + bare_args.1,
1,
"{lang:?}: a one-parameter lambda must report one argument"
);
assert_eq!(
args(lang, zero),
(0, 0),
"{lang:?}: `() -> …` has no arguments"
);
let two_args = args(lang, two);
assert_eq!(
two_args.0 + two_args.1,
2,
"{lang:?}: a two-parameter lambda must report two arguments"
);
}
assert!(
checked > 0,
"no lambda language enabled; this test asserted nothing"
);
}
#[test]
fn a_bare_lambda_stays_in_the_closure_channel() {
for lang in [LANG::Java, LANG::Csharp] {
if !lang.is_enabled() {
continue;
}
let [bare, ..] = cases(lang).expect("both languages have cases");
assert_eq!(
args(lang, bare),
(1, 0),
"{lang:?}: the lambda's argument must be billed to closure_args"
);
}
}
}
#[cfg(test)]
mod comments_in_parameter_lists {
use crate::test_support::metrics_verbatim;
use crate::{LANG, MetricsOptions};
fn args(lang: LANG, source: &str) -> (u64, u64) {
let m = metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default());
(m.nargs.closure_args_sum(), m.nargs.function_args_sum())
}
fn repaired_cases(lang: LANG) -> Option<&'static [(&'static str, (u64, u64))]> {
Some(match lang {
LANG::C => &[
(
"int h(int a /* one */, int b /* two */) { return a; }",
(0, 2),
),
("int h(int a, // one\n int b) { return a; }", (0, 2)),
("void f(int /*unused*/) { }", (0, 1)),
],
LANG::Cpp | LANG::Mozcpp => &[
(
"int h(int a /* one */, int b /* two */) { return a; }",
(0, 2),
),
("int h(int a, // one\n int b) { return a; }", (0, 2)),
("void f(int /*unused*/) { }", (0, 1)),
(
"int g() { auto f = [](int a, /* one */ int b){ return a + b; }; return f(1,2); }",
(2, 0),
),
],
LANG::Objc => &[("int h(int a /* one */, int b) { return a; }", (0, 2))],
LANG::Javascript | LANG::Mozjs => &[
("function h(a, /* one */ b) { return a; }", (0, 2)),
("function h(a, // one\n b) { return a; }", (0, 2)),
],
LANG::Typescript | LANG::Tsx => &[
(
"function h(a: number, /* one */ b: number) { return a; }",
(0, 2),
),
(
"function h(a: number, // one\n b: number) { return a; }",
(0, 2),
),
],
LANG::Python => &[("def h(a, # one\n b):\n return a\n", (0, 2))],
LANG::Rust => &[
("fn h(a: i32, /* one */ b: i32) -> i32 { a }", (0, 2)),
("fn h(a: i32, // one\n b: i32) -> i32 { a }", (0, 2)),
(
"fn g() { let f = |a: i32, /* one */ b: i32| a + b; }",
(2, 0),
),
],
LANG::Java | LANG::Csharp => &[
(
"class K { int h(int a, /* one */ int b) { return a; } }",
(0, 2),
),
(
"class K { int h(int a, // one\n int b) { return a; } }",
(0, 2),
),
],
LANG::Php => &[
("<?php function h($a, /* one */ $b) { return $a; }", (0, 2)),
(
"<?php function h($a, // one\n $b) { return $a; }",
(0, 2),
),
],
LANG::Ruby => &[("def h(a, # one\n b)\n a\nend", (0, 2))],
LANG::Groovy => &[
("def h(a, /* one */ b) { return a }", (0, 2)),
("def h(a, // one\n b) { return a }", (0, 2)),
],
LANG::Elixir => &[(
"defmodule M do\n def h(a, # one\n b) do\n a\n end\nend",
(0, 2),
)],
LANG::Kotlin => &[
(
"fun g() {\n val f = { a: Int, /* one */ b: Int -> a }\n println(f)\n}",
(2, 0),
),
(
"fun g() {\n val f = { a: Int, // one\n b: Int -> a }\n println(f)\n}",
(2, 0),
),
],
_ => return None,
})
}
fn already_correct_cases(lang: LANG) -> Option<&'static [(&'static str, (u64, u64))]> {
Some(match lang {
LANG::Objc => &[(
"@implementation K\n- (void)foo:(int)a /* one */ bar:(int)b { }\n@end",
(0, 2),
)],
LANG::Groovy => &[("def c = { x, /* one */ y -> x }", (2, 0))],
LANG::Kotlin => &[("fun h(a: Int, /* one */ b: Int): Int { return a }", (0, 2))],
LANG::Go => &[
(
"package m\nfunc h(a int, /* one */ b int) int { return a }",
(0, 2),
),
(
"package m\nvar f = func(x int, /* one */ y int) int { return x }",
(2, 0),
),
],
LANG::Lua => &[("local f = function(a, --[[one]] b) return a end", (2, 0))],
LANG::Perl => &[(
"use feature 'signatures';\nsub h(\n $a, # one\n $b\n) { return $a; }",
(0, 2),
)],
_ => return None,
})
}
#[test]
fn a_comment_in_a_parameter_list_is_not_a_parameter() {
let (mut repaired, mut guards) = (0, 0);
let mut failures = Vec::new();
for lang in LANG::into_enum_iter().filter(LANG::is_enabled) {
for (table, counter) in [
(repaired_cases(lang), &mut repaired),
(already_correct_cases(lang), &mut guards),
] {
for (source, expected) in table.unwrap_or_default() {
*counter += 1;
let got = args(lang, source);
if got != *expected {
failures.push((lang, source, *expected, got));
}
}
}
}
let (failed, total) = (failures.len(), repaired + guards);
assert!(
failures.is_empty(),
"{failed}/{total} fixtures counted a comment as a parameter: {failures:#?}"
);
assert!(
repaired > 0 && guards > 0,
"no fixture ran (repaired={repaired}, guards={guards}); this test asserted nothing"
);
}
#[test]
#[cfg(any(feature = "tcl", feature = "irules"))]
fn tcl_has_no_comment_inside_a_parameter_list() {
let mut ran = 0;
for lang in [LANG::Tcl, LANG::Irules]
.into_iter()
.filter(LANG::is_enabled)
{
ran += 1;
assert_eq!(
args(lang, "proc h {a\n # c\n b} { return $a }"),
(0, 4),
"{lang:?}: a `#` in an argument list is an argument named `#`, not a comment"
);
assert_eq!(args(lang, "proc h {a b} { return $a }"), (0, 2), "{lang:?}");
}
assert!(
ran > 0,
"neither tcl nor irules is enabled; this test asserted nothing"
);
}
#[test]
fn a_comment_on_a_bare_lambda_parameter_changes_nothing() {
for (lang, commented, bare) in [
(
LANG::Java,
"class K { java.util.function.Function<Integer,Integer> f = x /* c */ -> x; }",
"class K { java.util.function.Function<Integer,Integer> f = x -> x; }",
),
(
LANG::Csharp,
"class K { System.Func<int,int> f = x /* c */ => x + 1; }",
"class K { System.Func<int,int> f = x => x + 1; }",
),
]
.into_iter()
.filter(|(lang, ..)| lang.is_enabled())
{
let got = args(lang, commented);
assert_eq!(
got,
args(lang, bare),
"{lang:?}: the comment moved the count off the bare spelling's answer"
);
assert_eq!(
got,
(1, 0),
"{lang:?}: a bare lambda parameter is one closure argument"
);
}
}
}
#[cfg(test)]
mod c_family_return_type_declarators {
use crate::test_support::space_verbatim;
use crate::{LANG, MetricsOptions};
#[track_caller]
fn sole_space_args(lang: LANG, source: &str) -> (u64, u64) {
let root = space_verbatim(lang, source.as_bytes(), MetricsOptions::default());
let mut space = &root;
let mut depth = 0;
while let [only] = space.spaces.as_slice() {
space = only;
depth += 1;
}
assert!(depth > 0, "{lang:?}: fixture opened no space at all");
(
space.metrics.nargs.closure_args(),
space.metrics.nargs.function_args(),
)
}
fn c_declarator_shapes(lang: LANG) -> Option<&'static [(&'static str, (u64, u64))]> {
if !matches!(lang, LANG::C | LANG::Cpp | LANG::Mozcpp | LANG::Objc) {
return None;
}
Some(&[
("FILE *f(int a, int b, int c) { return 0; }", (0, 3)),
("int **g(int a, int b) { return 0; }", (0, 2)),
("static int *h(int a) { return 0; }", (0, 1)),
("int (*fp(int a, int b))(int c) { return 0; }", (0, 2)),
(
"int (__cdecl *w(int a, int b))(int c) { return 0; }",
(0, 2),
),
(
"int gdef(int a, int b) __attribute__((deprecated)) { return a; }",
(0, 2),
),
("int none(void) { return 0; }", (0, 0)),
("int unnamed(int) { return 0; }", (0, 1)),
("int ptr(void *p, int a) { return 0; }", (0, 2)),
("int plain(int a, int b) { return a; }", (0, 2)),
("void MACRO(name)(int a, int b) { }", (0, 2)),
("void MACRO(a, b)(int x) { }", (0, 1)),
("char *MACRO(n)(int a, int b) { return 0; }", (0, 2)),
("void A(b, c)(d)(int x, int y, int z) { }", (0, 3)),
("int (*arr(int a, int b))[4] { return 0; }", (0, 2)),
])
}
fn cpp11_attribute_shapes(lang: LANG) -> Option<&'static [(&'static str, (u64, u64))]> {
matches!(lang, LANG::C | LANG::Cpp | LANG::Mozcpp).then_some(&[(
"int attr(int a, int b) [[deprecated]] { return a; }",
(0, 2),
)])
}
fn cpp_only_shapes(lang: LANG) -> Option<&'static [(&'static str, (u64, u64))]> {
Some(match lang {
LANG::Cpp | LANG::Mozcpp => &[
("int &r(int a, int b) { static int x; return x; }", (0, 2)),
(
"Foo &&m(int a) { static Foo f; return static_cast<Foo &&>(f); }",
(0, 1),
),
("Foo &Bar::get(int a) { static Foo f; return f; }", (0, 1)),
(
"struct S { int operator()(int a, int b) const { return a; } };",
(0, 2),
),
(
"template <> void tspec<int (*)(int x, int y)>(int a) { }",
(0, 1),
),
(
"struct S { operator int (*)(int x) { return nullptr; } };",
(0, 0),
),
(
"struct S { operator int (&)(int x, int y) { static int *p; return *reinterpret_cast<int (*)(int, int)>(p); } };",
(0, 0),
),
(
"int g() { auto f = [](int a, int b){ return a + b; }; return f(1, 2); }",
(2, 0),
),
(
"int g(int a, int b) { auto f = []{ return 1; }; return f(); }",
(0, 2),
),
(
"int g(int a, int b) { auto f = []{ int q(int x, int y); }; f(); return a + b; }",
(0, 2),
),
(
"int g() { auto f = [](int a, int (*cb)(int x)){ return cb(a); }; return 0; }",
(2, 0),
),
],
_ => return None,
})
}
#[test]
fn a_wrapped_return_type_does_not_hide_the_parameter_list() {
let (mut shared, mut cpp_only, mut attributed) = (0, 0, 0);
let mut failures = Vec::new();
for lang in LANG::into_enum_iter().filter(LANG::is_enabled) {
for (table, counter) in [
(c_declarator_shapes(lang), &mut shared),
(cpp_only_shapes(lang), &mut cpp_only),
(cpp11_attribute_shapes(lang), &mut attributed),
] {
for (source, expected) in table.unwrap_or_default() {
*counter += 1;
let got = sole_space_args(lang, source);
if got != *expected {
failures.push((lang, source, *expected, got));
}
}
}
}
let (failed, checked) = (failures.len(), shared + cpp_only + attributed);
assert!(
failures.is_empty(),
"{failed}/{checked} return-type shapes lost their parameter list: {failures:#?}"
);
assert!(
shared > 0 && cpp_only > 0 && attributed > 0,
"no fixture ran (shared={shared}, cpp_only={cpp_only}, \
attributed={attributed}); this test asserted nothing"
);
}
}