big_code_analysis/metrics/nargs.rs
1// Per-language metric and AST modules deliberately consume the macro-
2// generated tree-sitter token enums via `use crate::*` and `use Foo::*`
3// inside match expressions — explicit imports would list dozens of
4// variants per arm and obscure the per-language token sets that are the
5// point of these files. Allowed at the module level rather than per
6// function so the per-language impl blocks stay readable.
7#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
8// Metric counts (token, function, branch, argument, etc.) are stored as
9// `usize` and crossed with `f64` averages, ratios, and Halstead scores
10// across the cyclomatic / MI / Halstead computations. The `usize as f64`
11// and `f64 as usize` casts are intentional and snapshot-anchored — every
12// site is bounded by the count it came from. Allowing the lints at the
13// module level keeps the metric arithmetic legible.
14#![allow(
15 clippy::cast_precision_loss,
16 clippy::cast_possible_truncation,
17 clippy::cast_sign_loss
18)]
19
20use std::fmt;
21
22use crate::c_declarator::innermost_declarator;
23use crate::checker::Checker;
24use crate::macros::implement_metric_trait;
25use crate::*;
26
27/// The `NArgs` metric.
28///
29/// This metric counts the number of arguments
30/// of functions/closures.
31#[derive(Debug, Clone, PartialEq)]
32#[non_exhaustive]
33pub struct Stats {
34 fn_nargs: usize,
35 closure_nargs: usize,
36 fn_nargs_sum: usize,
37 closure_nargs_sum: usize,
38 fn_nargs_min: usize,
39 closure_nargs_min: usize,
40 fn_nargs_max: usize,
41 closure_nargs_max: usize,
42 total_functions: usize,
43 total_closures: usize,
44}
45
46impl Default for Stats {
47 fn default() -> Self {
48 Self {
49 fn_nargs: 0,
50 closure_nargs: 0,
51 fn_nargs_sum: 0,
52 closure_nargs_sum: 0,
53 fn_nargs_min: usize::MAX,
54 closure_nargs_min: usize::MAX,
55 fn_nargs_max: 0,
56 closure_nargs_max: 0,
57 total_functions: 0,
58 total_closures: 0,
59 }
60 }
61}
62
63impl fmt::Display for Stats {
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 write!(
66 f,
67 "function_args: {}, closure_args: {}, function_args_average: {}, closure_args_average: {}, total: {}, average: {}, function_args_min: {}, function_args_max: {}, closure_args_min: {}, closure_args_max: {}",
68 self.function_args_sum(),
69 self.closure_args_sum(),
70 self.function_args_average(),
71 self.closure_args_average(),
72 self.total(),
73 self.average(),
74 self.function_args_min(),
75 self.function_args_max(),
76 self.closure_args_min(),
77 self.closure_args_max()
78 )
79 }
80}
81
82impl Stats {
83 /// Merges a second `NArgs` metric into the first one
84 pub fn merge(&mut self, other: &Stats) {
85 self.closure_nargs_min = self.closure_nargs_min.min(other.closure_nargs_min);
86 self.closure_nargs_max = self.closure_nargs_max.max(other.closure_nargs_max);
87 self.fn_nargs_min = self.fn_nargs_min.min(other.fn_nargs_min);
88 self.fn_nargs_max = self.fn_nargs_max.max(other.fn_nargs_max);
89 self.fn_nargs_sum += other.fn_nargs_sum;
90 self.closure_nargs_sum += other.closure_nargs_sum;
91 }
92
93 /// Returns the number of function arguments in a space.
94 #[inline]
95 #[must_use]
96 pub fn function_args(&self) -> u64 {
97 self.fn_nargs as u64
98 }
99
100 /// Returns the number of closure arguments in a space.
101 #[inline]
102 #[must_use]
103 pub fn closure_args(&self) -> u64 {
104 self.closure_nargs as u64
105 }
106
107 /// Returns the number of function arguments sum in a space.
108 #[inline]
109 #[must_use]
110 pub fn function_args_sum(&self) -> u64 {
111 self.fn_nargs_sum as u64
112 }
113
114 /// Returns the number of closure arguments sum in a space.
115 #[inline]
116 #[must_use]
117 pub fn closure_args_sum(&self) -> u64 {
118 self.closure_nargs_sum as u64
119 }
120
121 /// Returns the average number of functions arguments in a space.
122 #[inline]
123 #[must_use]
124 pub fn function_args_average(&self) -> f64 {
125 crate::metrics::average(self.fn_nargs_sum as f64, self.total_functions)
126 }
127
128 /// Returns the average number of closures arguments in a space.
129 #[inline]
130 #[must_use]
131 pub fn closure_args_average(&self) -> f64 {
132 crate::metrics::average(self.closure_nargs_sum as f64, self.total_closures)
133 }
134
135 /// Returns the total number of arguments of each function and
136 /// closure in a space.
137 #[inline]
138 #[must_use]
139 pub fn total(&self) -> u64 {
140 self.function_args_sum() + self.closure_args_sum()
141 }
142
143 /// Returns the `NArgs` metric average value
144 ///
145 /// This value is computed dividing the `NArgs` value
146 /// for the total number of functions/closures in a space.
147 #[inline]
148 #[must_use]
149 pub fn average(&self) -> f64 {
150 crate::metrics::average(
151 self.total() as f64,
152 self.total_functions + self.total_closures,
153 )
154 }
155 /// Returns the minimum number of function arguments in a space.
156 ///
157 /// Collapses the `usize::MAX` sentinel that `Stats::default()` plants
158 /// into `fn_nargs_min` to `0.0`, so a never-observed space
159 /// serializes to a meaningful number rather than `1.8446744e19`.
160 #[inline]
161 #[must_use]
162 pub fn function_args_min(&self) -> u64 {
163 if self.fn_nargs_min == usize::MAX {
164 0
165 } else {
166 self.fn_nargs_min as u64
167 }
168 }
169 /// Returns the maximum number of function arguments in a space.
170 #[inline]
171 #[must_use]
172 pub fn function_args_max(&self) -> u64 {
173 self.fn_nargs_max as u64
174 }
175 /// Returns the minimum number of closure arguments in a space.
176 ///
177 /// Same `usize::MAX` sentinel collapse as `function_args_min`.
178 #[inline]
179 #[must_use]
180 pub fn closure_args_min(&self) -> u64 {
181 if self.closure_nargs_min == usize::MAX {
182 0
183 } else {
184 self.closure_nargs_min as u64
185 }
186 }
187 /// Returns the maximum number of closure arguments in a space.
188 #[inline]
189 #[must_use]
190 pub fn closure_args_max(&self) -> u64 {
191 self.closure_nargs_max as u64
192 }
193 #[inline]
194 pub(crate) fn compute_sum(&mut self) {
195 self.closure_nargs_sum += self.closure_nargs;
196 self.fn_nargs_sum += self.fn_nargs;
197 }
198 #[inline]
199 pub(crate) fn compute_minmax(&mut self) {
200 self.closure_nargs_min = self.closure_nargs_min.min(self.closure_nargs);
201 self.closure_nargs_max = self.closure_nargs_max.max(self.closure_nargs);
202 self.fn_nargs_min = self.fn_nargs_min.min(self.fn_nargs);
203 self.fn_nargs_max = self.fn_nargs_max.max(self.fn_nargs);
204 self.compute_sum();
205 }
206 pub(crate) fn finalize(&mut self, total_functions: usize, total_closures: usize) {
207 self.total_functions = total_functions;
208 self.total_closures = total_closures;
209 }
210}
211
212/// How many of `params`' children are formal parameters.
213///
214/// The one place that says what a parameter is, and the only caller of
215/// [`Checker::is_non_arg`]. No language's `is_non_arg` lists a comment —
216/// they carry punctuation plus, in a few cases, a non-parameter the
217/// grammar puts in the list anyway (Rust's `self` receivers, Python's
218/// PEP 570 `/` marker, PHP's `...`) — so the purely negative filter
219/// this replaces counted a comment sitting between two parameters —
220/// `int h(int a /* one */, int b)` reported 3 — and counted the comment
221/// that stands in for an unnamed parameter, so `void f(int /*unused*/)`
222/// reported 2 (#1201). tree-sitter attaches a comment as a direct child
223/// of the parameter list, not inside the parameter it documents, which
224/// is why no `is_non_arg` list could have caught it.
225///
226/// Excluding comments here rather than in twenty `is_non_arg` impls is
227/// what makes it one rule: the next language added inherits it. Perl,
228/// Elixir and Kotlin lambdas reach their parameter list by three routes
229/// `compute_args` cannot express, so they call this directly.
230#[inline]
231fn count_args<T: Checker>(params: &Node, code: &[u8]) -> usize {
232 params
233 .children()
234 .filter(|child| {
235 !T::is_non_arg(child) && !T::is_comment(child) && !T::is_empty_param_marker(child, code)
236 })
237 .count()
238}
239
240#[inline]
241fn compute_args<T: Checker>(node: &Node, code: &[u8], nargs: &mut usize) {
242 if let Some(params) = node.child_by_field_name("parameters") {
243 // The field can hold a lone parameter rather than a list, in
244 // which case there are no children to walk and `count_args`
245 // yields zero — see `Checker::is_bare_param` (#1185).
246 if T::is_bare_param(¶ms) {
247 *nargs += 1;
248 return;
249 }
250 *nargs += count_args::<T>(¶ms, code);
251 } else if node.child_by_field_name("parameter").is_some() {
252 // JS/TS/TSX/MozJS arrow functions with a bare identifier parameter
253 // (`x => …`) use the singular `parameter` field instead of the plural
254 // `parameters` field. The grammar guarantees this is exactly one
255 // identifier, so count it as one argument.
256 *nargs += 1;
257 }
258}
259
260#[doc(hidden)]
261/// Per-language counting of function arguments.
262pub(crate) trait NArgs
263where
264 Self: Checker,
265 Self: std::marker::Sized,
266{
267 /// Walk `node` and update `stats` with this metric for the language
268 /// implementing the trait.
269 ///
270 /// Uses the source-aware [`Checker::is_func_with_code`] rather than the
271 /// byte-less `is_func`, exactly as [`crate::nom::Nom::compute`] does.
272 /// For every grammar with a syntactic function-definition node the two
273 /// are the same predicate, so no count moves; the point is that a
274 /// language whose declarations are only recognisable from the source
275 /// text — Elixir's `def` is an ordinary `Call` (#275) — does not
276 /// silently report 0 here (#1142).
277 fn compute<'a>(node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
278 if Self::is_func_with_code(node, code, ancestors) {
279 compute_args::<Self>(
280 &Self::params_owner(node, ancestors),
281 code,
282 &mut stats.fn_nargs,
283 );
284 return;
285 }
286
287 if Self::is_closure(node, ancestors) {
288 compute_args::<Self>(
289 &Self::params_owner(node, ancestors),
290 code,
291 &mut stats.closure_nargs,
292 );
293 }
294 }
295
296 /// The node whose `parameters` field holds this callable's formal
297 /// arguments. Defaults to the callable's own node.
298 ///
299 /// Exists so a language that spells its parameters somewhere other
300 /// than on the function node can say *that* and inherit everything
301 /// else. Overriding [`Self::compute`] to change this one expression
302 /// means re-stating the `is_func_with_code`-not-`is_func` rule and
303 /// the closure fallback, and a language that copied them does not
304 /// pick up a later correction — which is the drift #1142 and #1162
305 /// were both filed about.
306 ///
307 /// It answers for both channels — a closure reaches its parameters
308 /// through this too, which is what lets the C family express a C++
309 /// lambda and a pointer-returning function as one rule (#1200).
310 ///
311 /// The two are mutually exclusive: only the default `compute` calls
312 /// this, so a language that overrides `compute` (Objc, Go, Kotlin,
313 /// Lua, Tcl, iRules, Perl, Elixir, Groovy) would define a
314 /// `params_owner` that is never consulted. Override one or the
315 /// other, not both.
316 fn params_owner<'tree>(node: &Node<'tree>, _ancestors: Ancestors<'tree, '_>) -> Node<'tree> {
317 *node
318 }
319}
320
321// The C family spells a function's parameters on the
322// `function_declarator` buried under whatever the *return type*
323// contributed, so all three point `params_owner` at the innermost
324// declarator carrying a `parameters` field. Falling back to the node
325// keeps the pre-#1200 answer for a shape with no declarator at all — a
326// parameterless C++ lambda, `[]{ … }`, which has none.
327//
328// C++ and Mozcpp reach this for their lambdas as well as their
329// functions; C has no closure form at all (`CCode::is_closure` is a
330// constant `false`), so its closure channel is unreachable rather than
331// merely unused.
332impl NArgs for CppCode {
333 fn params_owner<'tree>(node: &Node<'tree>, _ancestors: Ancestors<'tree, '_>) -> Node<'tree> {
334 innermost_declarator::<Self>(node).unwrap_or(*node)
335 }
336}
337
338impl NArgs for CCode {
339 fn params_owner<'tree>(node: &Node<'tree>, _ancestors: Ancestors<'tree, '_>) -> Node<'tree> {
340 innermost_declarator::<Self>(node).unwrap_or(*node)
341 }
342}
343
344impl NArgs for MozcppCode {
345 fn params_owner<'tree>(node: &Node<'tree>, _ancestors: Ancestors<'tree, '_>) -> Node<'tree> {
346 innermost_declarator::<Self>(node).unwrap_or(*node)
347 }
348}
349
350// Objective-C carries parameters in three different shapes, so it cannot
351// share the single-`declarator`-field C/C++ impl:
352// * free `function_definition`s use the C declarator → `parameters`
353// field, counted exactly as in C;
354// * a `method_definition` lists one `method_parameter` per labelled
355// argument (`- (void)foo:(int)a bar:(int)b` → 2; the unary
356// `- (void)foo` → 0);
357// * a block `^(int x){ … }` holds its params in a `parameter_list`
358// child rather than under a `parameters` field.
359impl NArgs for ObjcCode {
360 fn compute<'a>(node: &Node<'a>, code: &[u8], _ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
361 match node.kind_id().into() {
362 Objc::FunctionDefinition | Objc::FunctionDefinition2 => {
363 // The same declarator walk C and C++ use: Objective-C
364 // inherits C's declarator syntax, so `FILE *f(int a)`
365 // buries the parameter list one level down (#1200).
366 if let Some(owner) = innermost_declarator::<Self>(node) {
367 compute_args::<Self>(&owner, code, &mut stats.fn_nargs);
368 }
369 }
370 Objc::MethodDefinition => {
371 // Both `method_parameter` aliases are accepted per the
372 // #285 / lesson-2 defensive convention: real parse trees
373 // emit `MethodParameter` (475), but the grammar also
374 // declares the `MethodParameter2` (476) alias, so list it
375 // too rather than risk a future bump emitting it.
376 node.act_on_child(&mut |n| {
377 if matches!(
378 n.kind_id().into(),
379 Objc::MethodParameter | Objc::MethodParameter2
380 ) {
381 stats.fn_nargs += 1;
382 }
383 });
384 }
385 Objc::BlockLiteral => {
386 // Through `count_args`, so the block channel gets the same
387 // three exclusions `compute_args` gives the function
388 // channel. Counting `ParameterDeclaration |
389 // VariadicParameter` positively could not consult
390 // `Checker::is_empty_param_marker`, so `^(void){ … }` —
391 // whose `parameter_list` holds a real
392 // `parameter_declaration` for the `void`, exactly as
393 // `int f(void)` does — reported one parameter (#1218).
394 //
395 // It inherits the shared rule's *inclusions* too, which the
396 // narrower positive match had excluded by construction: on
397 // invalid source an `ERROR` child (`^(int a,,)`) or a
398 // `compound_statement` one (`^({ int x; })`) now counts.
399 // That is the point rather than a regression — those are
400 // the numbers `int f(int a,,)` already reported through
401 // `count_args`, so the block arm stopped being the one
402 // caller that answered differently.
403 //
404 // `ParameterList2` is deliberately not matched: it is the
405 // alias for the hidden `_old_style_parameter_list`, and
406 // `block_literal` cannot produce it — even a K&R function
407 // definition emits `ParameterList`. Marked rather than
408 // silently omitted per `grammar-dispatch.md` §1/§2.
409 if let Some(params) = node.first_child(|id| Objc::ParameterList == id) {
410 stats.closure_nargs += count_args::<Self>(¶ms, code);
411 }
412 }
413 _ => {}
414 }
415 }
416}
417
418// Go's `parameter_declaration` allows multiple names to share one type
419// (`func f(a, b int)` is one parameter_declaration with two `name` children
420// but two formal parameters). Count names rather than declarations so the
421// reported nargs matches Go's parameter count.
422fn compute_go_args(node: &Node, nargs: &mut usize) {
423 let Some(params) = node.child_by_field_name("parameters") else {
424 return;
425 };
426 *nargs += params
427 .children()
428 .map(|child| match child.kind_id().into() {
429 Go::ParameterDeclaration => child
430 .children()
431 .filter(|c| c.kind_id() == Go::Identifier)
432 .count()
433 .max(1),
434 Go::VariadicParameterDeclaration => 1,
435 _ => 0,
436 })
437 .sum::<usize>();
438}
439
440impl NArgs for GoCode {
441 fn compute<'a>(node: &Node<'a>, _code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
442 if Self::is_func(node, ancestors) {
443 compute_go_args(node, &mut stats.fn_nargs);
444 return;
445 }
446
447 if Self::is_closure(node, ancestors) {
448 compute_go_args(node, &mut stats.closure_nargs);
449 }
450 }
451}
452
453fn compute_kotlin_func_args(node: &Node, nargs: &mut usize) {
454 if let Some(params) = node
455 .children()
456 .find(|c| c.kind_id() == Kotlin::FunctionValueParameters)
457 {
458 params.act_on_child(&mut |n| {
459 if n.kind_id() == Kotlin::Parameter {
460 *nargs += 1;
461 }
462 });
463 }
464}
465
466fn compute_kotlin_lambda_args(node: &Node, code: &[u8], nargs: &mut usize) {
467 // Lambda parameters are plain identifiers or destructuring patterns separated
468 // by commas; there is no typed `Parameter` wrapper node (unlike function
469 // value parameters), so a negative filter is the correct predicate here — the
470 // shared one, which also drops the comment `{ a, /* one */ b -> }` puts
471 // between them (#1201). `KotlinCode::is_non_arg` adds the parens to the
472 // comma, which costs nothing: a destructuring pattern nests its own parens
473 // inside a `multi_variable_declaration`, so `lambda_parameters` never has a
474 // paren as a direct child.
475 if let Some(params) = node
476 .children()
477 .find(|c| c.kind_id() == Kotlin::LambdaParameters)
478 {
479 *nargs += count_args::<KotlinCode>(¶ms, code);
480 }
481}
482
483impl NArgs for KotlinCode {
484 fn compute<'a>(node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
485 if Self::is_func(node, ancestors) {
486 compute_kotlin_func_args(node, &mut stats.fn_nargs);
487 return;
488 }
489
490 if Self::is_closure(node, ancestors) {
491 if node.kind_id() == Kotlin::LambdaLiteral {
492 compute_kotlin_lambda_args(node, code, &mut stats.closure_nargs);
493 } else {
494 compute_kotlin_func_args(node, &mut stats.closure_nargs);
495 }
496 }
497 }
498}
499
500fn compute_lua_args(node: &Node, nargs: &mut usize) {
501 let Some(params) = node.child_by_field_name("parameters") else {
502 return;
503 };
504 *nargs += params
505 .children()
506 .filter(|c| matches!(c.kind_id().into(), Lua::Identifier | Lua::VarargExpression))
507 .count();
508}
509
510impl NArgs for LuaCode {
511 fn compute<'a>(node: &Node<'a>, _code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
512 if Self::is_func(node, ancestors) {
513 compute_lua_args(node, &mut stats.fn_nargs);
514 } else if Self::is_closure(node, ancestors) {
515 compute_lua_args(node, &mut stats.closure_nargs);
516 }
517 }
518}
519
520fn compute_tcl_args(node: &Node, nargs: &mut usize) {
521 let Some(params) = node.child_by_field_name("arguments") else {
522 return;
523 };
524 *nargs += params
525 .children()
526 .filter(|c| c.kind_id() == Tcl::Argument)
527 .count();
528}
529
530impl NArgs for TclCode {
531 fn compute<'a>(node: &Node<'a>, _code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
532 if Self::is_func(node, ancestors) {
533 compute_tcl_args(node, &mut stats.fn_nargs);
534 }
535 }
536}
537
538// iRules counterpart of `compute_tcl_args`. Only `procedure` carries an
539// `arguments` *field*; `when_event` handlers have no formal parameters
540// (the event context is implicit), so they correctly count zero. `{a 5}`
541// default-valued parameters parse as a single `argument`, so each formal
542// parameter contributes one regardless of its default.
543fn compute_irules_args(node: &Node, nargs: &mut usize) {
544 let Some(params) = node.child_by_field_name("arguments") else {
545 return;
546 };
547 *nargs += params
548 .children()
549 .filter(|c| c.kind_id() == Irules::Argument)
550 .count();
551}
552
553impl NArgs for IrulesCode {
554 fn compute<'a>(node: &Node<'a>, _code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
555 if Self::is_func(node, ancestors) {
556 compute_irules_args(node, &mut stats.fn_nargs);
557 }
558 }
559}
560
561// tree-sitter-perl emits a subroutine signature as an unnamed
562// `function_signature` child rather than under a `parameters` field, so
563// the shared `compute_args` helper never sees it. `FunctionSignature2`
564// is the hidden `_function_signature` supertype, listed defensively per
565// the lesson-2 convention.
566//
567// A bare attribute swallows the signature — `sub f :lvalue ($z)` parses
568// as `function_attribute → function_signature` — while an attribute
569// carrying its own parens (`sub f :prototype($$) ($a, $b)`) leaves the
570// signature a direct child. Look one level into `function_attribute` so
571// both spellings count; a `:prototype($$)` argument list is a
572// `function_prototype`, a different kind, so it cannot be mistaken for a
573// signature.
574fn perl_signature<'a>(node: &Node<'a>) -> Option<Node<'a>> {
575 fn is_signature(id: u16) -> bool {
576 matches!(
577 id.into(),
578 Perl::FunctionSignature | Perl::FunctionSignature2
579 )
580 }
581 node.children().find_map(|child| {
582 if is_signature(child.kind_id()) {
583 Some(child)
584 } else if child.kind_id() == Perl::FunctionAttribute {
585 child.first_child(is_signature)
586 } else {
587 None
588 }
589 })
590}
591
592// Count every signature child `count_args` accepts: a defaulted parameter
593// (`$y = 5`) is a `binary_expression`, not a bare `scalar_variable`, so a
594// positive variant list would undercount it. The negative filter also
595// survives signature forms the grammar may add — at the price of needing
596// the comment exclusion, since a multi-line signature documents its
597// parameters with `comments` children sitting directly under
598// `function_signature`. Perl carried that exclusion inline for years
599// before #1201 found the same hole in every other language and moved it
600// into the shared predicate.
601fn compute_perl_args(node: &Node, code: &[u8], nargs: &mut usize) {
602 let Some(signature) = perl_signature(node) else {
603 return;
604 };
605 *nargs += count_args::<PerlCode>(&signature, code);
606}
607
608impl NArgs for PerlCode {
609 fn compute<'a>(node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
610 if Self::is_func(node, ancestors) {
611 compute_perl_args(node, code, &mut stats.fn_nargs);
612 return;
613 }
614
615 // Every anonymous sub reads 0 today: tree-sitter-perl 1.1.2 parses
616 // its signature inside an `ERROR` node
617 // (`anonymous_function → sub → ERROR → function_signature`), so
618 // `perl_signature` finds nothing among the direct children.
619 // Deliberately not recovered — descending through `ERROR` would
620 // pin us to a parse shape upstream will change. The call stays
621 // here so a grammar fix starts counting (and fails
622 // `perl_anonymous_sub_signature_is_zero`) rather than passing
623 // silently; revisit at the next `recreate-grammars.sh` bump.
624 if Self::is_closure(node, ancestors) {
625 compute_perl_args(node, code, &mut stats.closure_nargs);
626 }
627 }
628}
629
630// Elixir has no function-definition node. `def bar(a, b, c)` is a `Call`
631// whose `arguments` holds a *second* `Call` carrying the real parameter
632// list, which is why the `parameters`-field heuristic in `compute_args`
633// finds nothing:
634//
635// call (def)
636// ├─ identifier def <- the `target` field
637// ╰─ arguments <- NOT a field; tree-sitter-elixir
638// ╰─ call bar(a, b, c) gives `call` only a `target` field,
639// ├─ identifier bar so both `arguments` levels have to
640// ╰─ arguments (a, b, c) be found by kind.
641//
642// A guarded head interposes a `when` `binary_operator` whose `left` is
643// that `Call` — without unwrapping it every guarded clause counts 0, and
644// guards are a large fraction of real Elixir. A head that is a bare
645// `identifier` (`def noargs, do: 1`) has no parameter list and counts 0.
646//
647// `def a + b` and `def -a` define the operator functions `+/2` and `-/1`.
648// Their head is the operator node itself, with the parameters as its
649// operands and no `arguments` container to walk, so the arity comes from
650// the operator's shape.
651fn elixir_declared_args(node: &Node, code: &[u8]) -> usize {
652 let Some(head) = elixir_arguments(node).and_then(|a| a.children().find(Node::is_named)) else {
653 return 0;
654 };
655 let head = elixir_unwrap_guard(&head, code);
656 match head.kind_id().into() {
657 Elixir::Call => elixir_arguments(&head).map_or(0, |p| count_args::<ElixirCode>(&p, code)),
658 Elixir::BinaryOperator => 2,
659 Elixir::UnaryOperator => 1,
660 _ => 0,
661 }
662}
663
664fn elixir_arguments<'a>(node: &Node<'a>) -> Option<Node<'a>> {
665 node.first_child(|id| id == Elixir::Arguments)
666}
667
668// Returns the guarded expression when `node` is a `when` guard, and
669// `node` itself otherwise. Matching `BinaryOperator` alone would also
670// unwrap an operator definition, whose operands are the parameters.
671fn elixir_unwrap_guard<'a>(node: &Node<'a>, code: &[u8]) -> Node<'a> {
672 let is_when = node.kind_id() == Elixir::BinaryOperator
673 && node
674 .child_by_field_name("operator")
675 .and_then(|op| op.utf8_text(code))
676 == Some("when");
677 if is_when {
678 node.child_by_field_name("left").unwrap_or(*node)
679 } else {
680 *node
681 }
682}
683
684impl NArgs for ElixirCode {
685 fn compute<'a>(node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
686 // `is_func` is byte-less and constant `false` for Elixir, because a
687 // `def` is textually indistinguishable from any other `Call`. The
688 // code-aware predicate (#275) is the only one that identifies one,
689 // and it also excludes a `def` inside `quote do … end`, which
690 // declares nothing until the macro expands (#310).
691 if Self::is_func_with_code(node, code, ancestors) {
692 stats.fn_nargs += elixir_declared_args(node, code);
693 return;
694 }
695
696 // Every clause of one `fn` must have the same arity, so the first
697 // `stab_clause` gives the closure's argument count. Summing the
698 // clauses would report `2n` for an n-clause function — do not
699 // "fix" this into a sum.
700 //
701 // A guarded clause (`fn x when is_integer(x) -> …`) aliases its
702 // `left` to a `binary_operator`, exactly as a guarded `def` head
703 // does, so it needs the same unwrap — otherwise every guarded
704 // closure counts the fixed 3 children of the guard expression.
705 if Self::is_closure(node, ancestors)
706 && let Some(clause) = node.first_child(|id| id == Elixir::StabClause)
707 && let Some(params) = clause.child_by_field_name("left")
708 {
709 stats.closure_nargs +=
710 count_args::<ElixirCode>(&elixir_unwrap_guard(¶ms, code), code);
711 }
712 }
713}
714
715implement_metric_trait!(
716 [NArgs],
717 PythonCode,
718 MozjsCode,
719 JavascriptCode,
720 TypescriptCode,
721 TsxCode,
722 RustCode,
723 PreprocCode,
724 CcommentCode,
725 BashCode,
726 PhpCode,
727 CsharpCode,
728 RubyCode
729);
730
731// A record's compact constructor (`record R(int a, int b) { R { … } }`,
732// JLS 8.10.4) declares no formal parameter list of its own: the grammar
733// gives `compact_constructor_declaration` only `name` and `body` fields,
734// and hangs the parameters — the record's components — off the enclosing
735// `record_declaration`. Resolve that declaration so the two spellings of
736// one constructor agree: the canonical `R(int a, int b) { … }` reports 2,
737// and so should the compact form (#1160).
738//
739// The nesting is fixed by the grammar — the constructor is a direct child
740// of the record's `class_body` — so the record sits exactly two steps up.
741// The kind check states what that positional step is allowed to land on;
742// no kind other than `record_declaration` carries a `parameters` field
743// here, so it changes no count today, but it keeps the step from silently
744// starting to mean something else if one ever does.
745//
746// A record that declares *no* constructor still reports 0: its canonical
747// constructor is implicit, so there is no node to open a space for and
748// nothing to attribute the components to. Adding an empty `R { }` to such
749// a record therefore moves its `nargs` from 0 to the component count
750// without changing the API — the same asymmetry an explicit
751// `R(int a, int b) { }` already produces, and the price of measuring
752// declared code rather than generated code.
753fn java_compact_constructor_record<'tree>(
754 node: &Node<'tree>,
755 ancestors: Ancestors<'tree, '_>,
756) -> Option<Node<'tree>> {
757 if node.kind_id() != Java::CompactConstructorDeclaration {
758 return None;
759 }
760 let (grandparent, _) = ancestors.iter(node).nth(1)?;
761 (grandparent.kind_id() == Java::RecordDeclaration).then_some(grandparent)
762}
763
764impl NArgs for JavaCode {
765 fn params_owner<'tree>(node: &Node<'tree>, ancestors: Ancestors<'tree, '_>) -> Node<'tree> {
766 java_compact_constructor_record(node, ancestors).unwrap_or(*node)
767 }
768}
769
770// Groovy closures use `closure_parameters` as an unnamed child rather
771// than a `parameters` field, so the default NArgs walker (which looks
772// for a `parameters` field) misses them. Match the closure_parameters
773// child directly and count its `closure_parameter` grand-children.
774impl NArgs for GroovyCode {
775 fn compute<'a>(node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, stats: &mut Stats) {
776 use crate::languages::language_groovy::Groovy;
777
778 if Self::is_func(node, ancestors) {
779 compute_args::<Self>(node, code, &mut stats.fn_nargs);
780 return;
781 }
782
783 if Self::is_closure(node, ancestors)
784 && let Some(params) = node.first_child(|id| id == Groovy::ClosureParameters)
785 {
786 params.act_on_child(&mut |n| {
787 if n.kind_id() == Groovy::ClosureParameter {
788 stats.closure_nargs += 1;
789 }
790 });
791 }
792 }
793}
794
795#[cfg(test)]
796#[allow(
797 clippy::float_cmp,
798 clippy::cast_precision_loss,
799 clippy::cast_possible_truncation,
800 clippy::cast_sign_loss,
801 clippy::similar_names,
802 clippy::doc_markdown,
803 clippy::needless_raw_string_hashes,
804 clippy::too_many_lines
805)]
806mod tests {
807 use crate::test_support::check_metrics_only_shim;
808
809 use super::*;
810
811 // Nargs pulls Nom for its per-function average divisor, which is also
812 // what this module's `metric.nom.functions_sum()` /
813 // `closures_sum()` assertions read.
814 check_metrics_only_shim!(check_metrics, Nargs);
815
816 /// Regression for #227: a `Stats::default()` that never sees an
817 /// observation must not leak the `usize::MAX` sentinel for
818 /// `fn_args_min` or `closure_args_min`. Both getters collapse
819 /// the sentinel to `0.0` so JSON never emits `1.8446744e19`.
820 #[test]
821 fn nargs_empty_file_min_is_zero() {
822 let stats = Stats::default();
823 assert_eq!(stats.function_args_min(), 0);
824 assert_eq!(stats.closure_args_min(), 0);
825 }
826
827 #[test]
828 fn python_no_functions_and_closures() {
829 check_metrics::<PythonParser>("a = 42", "foo.py", |metric| {
830 // 0 functions + 0 closures
831 insta::assert_json_snapshot!(
832 metric.nargs,
833 @r#"
834 {
835 "function_args": 0,
836 "closure_args": 0,
837 "function_args_average": 0.0,
838 "closure_args_average": 0.0,
839 "total": 0,
840 "average": 0.0,
841 "function_args_min": 0,
842 "function_args_max": 0,
843 "closure_args_min": 0,
844 "closure_args_max": 0
845 }
846 "#
847 );
848 });
849 }
850
851 #[test]
852 fn rust_no_functions_and_closures() {
853 check_metrics::<RustParser>("let a = 42;", "foo.rs", |metric| {
854 // 0 functions + 0 closures
855 insta::assert_json_snapshot!(
856 metric.nargs,
857 @r#"
858 {
859 "function_args": 0,
860 "closure_args": 0,
861 "function_args_average": 0.0,
862 "closure_args_average": 0.0,
863 "total": 0,
864 "average": 0.0,
865 "function_args_min": 0,
866 "function_args_max": 0,
867 "closure_args_min": 0,
868 "closure_args_max": 0
869 }
870 "#
871 );
872 });
873 }
874
875 #[test]
876 fn cpp_no_functions_and_closures() {
877 check_metrics::<CppParser>("int a = 42;", "foo.cpp", |metric| {
878 // 0 functions + 0 closures
879 insta::assert_json_snapshot!(
880 metric.nargs,
881 @r#"
882 {
883 "function_args": 0,
884 "closure_args": 0,
885 "function_args_average": 0.0,
886 "closure_args_average": 0.0,
887 "total": 0,
888 "average": 0.0,
889 "function_args_min": 0,
890 "function_args_max": 0,
891 "closure_args_min": 0,
892 "closure_args_max": 0
893 }
894 "#
895 );
896 });
897 }
898
899 #[test]
900 fn javascript_no_functions_and_closures() {
901 check_metrics::<JavascriptParser>("var a = 42;", "foo.js", |metric| {
902 // 0 functions + 0 closures
903 insta::assert_json_snapshot!(
904 metric.nargs,
905 @r#"
906 {
907 "function_args": 0,
908 "closure_args": 0,
909 "function_args_average": 0.0,
910 "closure_args_average": 0.0,
911 "total": 0,
912 "average": 0.0,
913 "function_args_min": 0,
914 "function_args_max": 0,
915 "closure_args_min": 0,
916 "closure_args_max": 0
917 }
918 "#
919 );
920 });
921 }
922
923 #[test]
924 fn python_single_function() {
925 check_metrics::<PythonParser>(
926 "def f(a, b):
927 if a:
928 return a",
929 "foo.py",
930 |metric| {
931 // 1 function
932 insta::assert_json_snapshot!(
933 metric.nargs,
934 @r#"
935 {
936 "function_args": 2,
937 "closure_args": 0,
938 "function_args_average": 2.0,
939 "closure_args_average": 0.0,
940 "total": 2,
941 "average": 2.0,
942 "function_args_min": 0,
943 "function_args_max": 2,
944 "closure_args_min": 0,
945 "closure_args_max": 0
946 }
947 "#
948 );
949 },
950 );
951 }
952
953 #[test]
954 fn rust_single_function() {
955 check_metrics::<RustParser>(
956 "fn f(a: bool, b: usize) {
957 if a {
958 return a;
959 }
960 }",
961 "foo.rs",
962 |metric| {
963 // 1 function
964 insta::assert_json_snapshot!(
965 metric.nargs,
966 @r#"
967 {
968 "function_args": 2,
969 "closure_args": 0,
970 "function_args_average": 2.0,
971 "closure_args_average": 0.0,
972 "total": 2,
973 "average": 2.0,
974 "function_args_min": 0,
975 "function_args_max": 2,
976 "closure_args_min": 0,
977 "closure_args_max": 0
978 }
979 "#
980 );
981 },
982 );
983 }
984
985 #[test]
986 fn c_single_function() {
987 check_metrics::<CParser>(
988 "int f(int a, int b) {
989 if (a) {
990 return a;
991 }
992 }",
993 "foo.c",
994 |metric| {
995 // 1 function
996 insta::assert_json_snapshot!(
997 metric.nargs,
998 @r#"
999 {
1000 "function_args": 2,
1001 "closure_args": 0,
1002 "function_args_average": 2.0,
1003 "closure_args_average": 0.0,
1004 "total": 2,
1005 "average": 2.0,
1006 "function_args_min": 0,
1007 "function_args_max": 2,
1008 "closure_args_min": 0,
1009 "closure_args_max": 0
1010 }
1011 "#
1012 );
1013 },
1014 );
1015 }
1016
1017 #[test]
1018 fn javascript_single_function() {
1019 check_metrics::<JavascriptParser>(
1020 "function f(a, b) {
1021 return a * b;
1022 }",
1023 "foo.js",
1024 |metric| {
1025 // 1 function
1026 insta::assert_json_snapshot!(
1027 metric.nargs,
1028 @r#"
1029 {
1030 "function_args": 2,
1031 "closure_args": 0,
1032 "function_args_average": 2.0,
1033 "closure_args_average": 0.0,
1034 "total": 2,
1035 "average": 2.0,
1036 "function_args_min": 0,
1037 "function_args_max": 2,
1038 "closure_args_min": 0,
1039 "closure_args_max": 0
1040 }
1041 "#
1042 );
1043 },
1044 );
1045 }
1046
1047 #[test]
1048 fn python_single_lambda() {
1049 check_metrics::<PythonParser>("bar = lambda a: True", "foo.py", |metric| {
1050 // 1 lambda
1051 insta::assert_json_snapshot!(
1052 metric.nargs,
1053 @r#"
1054 {
1055 "function_args": 0,
1056 "closure_args": 1,
1057 "function_args_average": 0.0,
1058 "closure_args_average": 1.0,
1059 "total": 1,
1060 "average": 1.0,
1061 "function_args_min": 0,
1062 "function_args_max": 0,
1063 "closure_args_min": 1,
1064 "closure_args_max": 1
1065 }
1066 "#
1067 );
1068 });
1069 }
1070
1071 #[test]
1072 fn rust_single_closure() {
1073 check_metrics::<RustParser>("let bar = |i: i32| -> i32 { i + 1 };", "foo.rs", |metric| {
1074 // 1 lambda
1075 insta::assert_json_snapshot!(
1076 metric.nargs,
1077 @r#"
1078 {
1079 "function_args": 0,
1080 "closure_args": 1,
1081 "function_args_average": 0.0,
1082 "closure_args_average": 1.0,
1083 "total": 1,
1084 "average": 1.0,
1085 "function_args_min": 0,
1086 "function_args_max": 0,
1087 "closure_args_min": 0,
1088 "closure_args_max": 1
1089 }
1090 "#
1091 );
1092 });
1093 }
1094
1095 #[test]
1096 fn cpp_single_lambda() {
1097 check_metrics::<CppParser>(
1098 "auto bar = [](int x, int y) -> int { return x + y; };",
1099 "foo.cpp",
1100 |metric| {
1101 // 1 lambda
1102 insta::assert_json_snapshot!(
1103 metric.nargs,
1104 @r#"
1105 {
1106 "function_args": 0,
1107 "closure_args": 2,
1108 "function_args_average": 0.0,
1109 "closure_args_average": 2.0,
1110 "total": 2,
1111 "average": 2.0,
1112 "function_args_min": 0,
1113 "function_args_max": 0,
1114 "closure_args_min": 2,
1115 "closure_args_max": 2
1116 }
1117 "#
1118 );
1119 },
1120 );
1121 }
1122
1123 #[test]
1124 fn javascript_single_closure() {
1125 check_metrics::<JavascriptParser>("function (a, b) {return a + b};", "foo.js", |metric| {
1126 // 1 lambda
1127 insta::assert_json_snapshot!(
1128 metric.nargs,
1129 @r#"
1130 {
1131 "function_args": 0,
1132 "closure_args": 2,
1133 "function_args_average": 0.0,
1134 "closure_args_average": 2.0,
1135 "total": 2,
1136 "average": 2.0,
1137 "function_args_min": 0,
1138 "function_args_max": 0,
1139 "closure_args_min": 0,
1140 "closure_args_max": 2
1141 }
1142 "#
1143 );
1144 });
1145 }
1146
1147 #[test]
1148 fn python_functions() {
1149 check_metrics::<PythonParser>(
1150 "def f(a, b):
1151 if a:
1152 return a
1153 def f(a, b):
1154 if b:
1155 return b",
1156 "foo.py",
1157 |metric| {
1158 // 2 functions
1159 insta::assert_json_snapshot!(
1160 metric.nargs,
1161 @r#"
1162 {
1163 "function_args": 4,
1164 "closure_args": 0,
1165 "function_args_average": 2.0,
1166 "closure_args_average": 0.0,
1167 "total": 4,
1168 "average": 2.0,
1169 "function_args_min": 0,
1170 "function_args_max": 2,
1171 "closure_args_min": 0,
1172 "closure_args_max": 0
1173 }
1174 "#
1175 );
1176 },
1177 );
1178
1179 check_metrics::<PythonParser>(
1180 "def f(a, b):
1181 if a:
1182 return a
1183 def f(a, b, c):
1184 if b:
1185 return b",
1186 "foo.py",
1187 |metric| {
1188 // 2 functions
1189 insta::assert_json_snapshot!(
1190 metric.nargs,
1191 @r#"
1192 {
1193 "function_args": 5,
1194 "closure_args": 0,
1195 "function_args_average": 2.5,
1196 "closure_args_average": 0.0,
1197 "total": 5,
1198 "average": 2.5,
1199 "function_args_min": 0,
1200 "function_args_max": 3,
1201 "closure_args_min": 0,
1202 "closure_args_max": 0
1203 }
1204 "#
1205 );
1206 },
1207 );
1208 }
1209
1210 #[test]
1211 fn rust_functions() {
1212 check_metrics::<RustParser>(
1213 "fn f(a: bool, b: usize) {
1214 if a {
1215 return a;
1216 }
1217 }
1218 fn f1(a: bool, b: usize) {
1219 if a {
1220 return a;
1221 }
1222 }",
1223 "foo.rs",
1224 |metric| {
1225 // 2 functions
1226 insta::assert_json_snapshot!(
1227 metric.nargs,
1228 @r#"
1229 {
1230 "function_args": 4,
1231 "closure_args": 0,
1232 "function_args_average": 2.0,
1233 "closure_args_average": 0.0,
1234 "total": 4,
1235 "average": 2.0,
1236 "function_args_min": 0,
1237 "function_args_max": 2,
1238 "closure_args_min": 0,
1239 "closure_args_max": 0
1240 }
1241 "#
1242 );
1243 },
1244 );
1245
1246 check_metrics::<RustParser>(
1247 "fn f(a: bool, b: usize) {
1248 if a {
1249 return a;
1250 }
1251 }
1252 fn f1(a: bool, b: usize, c: usize) {
1253 if a {
1254 return a;
1255 }
1256 }",
1257 "foo.rs",
1258 |metric| {
1259 // 2 functions
1260 insta::assert_json_snapshot!(
1261 metric.nargs,
1262 @r#"
1263 {
1264 "function_args": 5,
1265 "closure_args": 0,
1266 "function_args_average": 2.5,
1267 "closure_args_average": 0.0,
1268 "total": 5,
1269 "average": 2.5,
1270 "function_args_min": 0,
1271 "function_args_max": 3,
1272 "closure_args_min": 0,
1273 "closure_args_max": 0
1274 }
1275 "#
1276 );
1277 },
1278 );
1279 }
1280
1281 /// The `self` receiver (`self`, `&self`, `&mut self`) parses as a
1282 /// `self_parameter` node and, like Go's `receiver` field and C++'s
1283 /// implicit `this`, must not be counted as a formal parameter (#457).
1284 #[test]
1285 fn rust_method_excludes_self_receiver() {
1286 check_metrics::<RustParser>(
1287 "struct S;
1288 impl S {
1289 fn a(self) {} // self -> 0 args
1290 fn b(&self, x: i32) {} // &self + 1 -> 1 arg
1291 fn c(&mut self, x: i32, y: i32) {} // &mut self + 2 -> 2 args
1292 }",
1293 "foo.rs",
1294 |metric| {
1295 // 3 methods: 0 + 1 + 2 explicit params. The three receiver
1296 // forms contribute nothing. sum = 3, max = 2.
1297 let s = &metric.nargs;
1298 assert_eq!(s.function_args_sum(), 3);
1299 assert_eq!(s.function_args_max(), 2);
1300 },
1301 );
1302
1303 // A *typed* receiver (`self: Box<Self>`, `self: Rc<Self>`,
1304 // `self: Pin<&mut Self>`) parses as an ordinary `parameter` node —
1305 // not `self_parameter` — but its binding is the `self` keyword, so
1306 // it is still a receiver and must be excluded too, matching the
1307 // bare-receiver case and Go/C++ receiver parity (#457). A normal
1308 // `parameter` like `x: i32` binds an `identifier`, never `self`.
1309 check_metrics::<RustParser>(
1310 "use std::rc::Rc;
1311 use std::pin::Pin;
1312 struct S;
1313 impl S {
1314 fn a(self: Box<Self>, x: i32, y: i32) {} // receiver + 2 -> 2
1315 fn b(self: Rc<Self>, x: i32) {} // receiver + 1 -> 1
1316 fn c(self: Pin<&mut Self>) {} // receiver -> 0
1317 }",
1318 "foo.rs",
1319 |metric| {
1320 // Each typed receiver contributes nothing. sum = 2+1+0 = 3,
1321 // max = 2 (from method `a`).
1322 let s = &metric.nargs;
1323 assert_eq!(s.function_args_sum(), 3);
1324 assert_eq!(s.function_args_max(), 2);
1325 },
1326 );
1327 }
1328
1329 #[test]
1330 fn c_functions() {
1331 check_metrics::<CParser>(
1332 "int f(int a, int b) {
1333 if (a) {
1334 return a;
1335 }
1336 }
1337 int f1(int a, int b) {
1338 if (a) {
1339 return a;
1340 }
1341 }",
1342 "foo.c",
1343 |metric| {
1344 // 2 functions
1345 insta::assert_json_snapshot!(
1346 metric.nargs,
1347 @r#"
1348 {
1349 "function_args": 4,
1350 "closure_args": 0,
1351 "function_args_average": 2.0,
1352 "closure_args_average": 0.0,
1353 "total": 4,
1354 "average": 2.0,
1355 "function_args_min": 0,
1356 "function_args_max": 2,
1357 "closure_args_min": 0,
1358 "closure_args_max": 0
1359 }
1360 "#
1361 );
1362 },
1363 );
1364
1365 check_metrics::<CppParser>(
1366 "int f(int a, int b) {
1367 if (a) {
1368 return a;
1369 }
1370 }
1371 int f1(int a, int b, int c) {
1372 if (a) {
1373 return a;
1374 }
1375 }",
1376 "foo.c",
1377 |metric| {
1378 // 2 functions
1379 insta::assert_json_snapshot!(
1380 metric.nargs,
1381 @r#"
1382 {
1383 "function_args": 5,
1384 "closure_args": 0,
1385 "function_args_average": 2.5,
1386 "closure_args_average": 0.0,
1387 "total": 5,
1388 "average": 2.5,
1389 "function_args_min": 0,
1390 "function_args_max": 3,
1391 "closure_args_min": 0,
1392 "closure_args_max": 0
1393 }
1394 "#
1395 );
1396 },
1397 );
1398 }
1399
1400 #[test]
1401 fn javascript_functions() {
1402 check_metrics::<JavascriptParser>(
1403 "function f(a, b) {
1404 return a * b;
1405 }
1406 function f1(a, b) {
1407 return a * b;
1408 }",
1409 "foo.js",
1410 |metric| {
1411 // 2 functions
1412 insta::assert_json_snapshot!(
1413 metric.nargs,
1414 @r#"
1415 {
1416 "function_args": 4,
1417 "closure_args": 0,
1418 "function_args_average": 2.0,
1419 "closure_args_average": 0.0,
1420 "total": 4,
1421 "average": 2.0,
1422 "function_args_min": 0,
1423 "function_args_max": 2,
1424 "closure_args_min": 0,
1425 "closure_args_max": 0
1426 }
1427 "#
1428 );
1429 },
1430 );
1431
1432 check_metrics::<JavascriptParser>(
1433 "function f(a, b) {
1434 return a * b;
1435 }
1436 function f1(a, b, c) {
1437 return a * b;
1438 }",
1439 "foo.js",
1440 |metric| {
1441 // 2 functions
1442 insta::assert_json_snapshot!(
1443 metric.nargs,
1444 @r#"
1445 {
1446 "function_args": 5,
1447 "closure_args": 0,
1448 "function_args_average": 2.5,
1449 "closure_args_average": 0.0,
1450 "total": 5,
1451 "average": 2.5,
1452 "function_args_min": 0,
1453 "function_args_max": 3,
1454 "closure_args_min": 0,
1455 "closure_args_max": 0
1456 }
1457 "#
1458 );
1459 },
1460 );
1461 }
1462
1463 #[test]
1464 fn python_nested_functions() {
1465 check_metrics::<PythonParser>(
1466 "def f(a, b):
1467 def foo(a):
1468 if a:
1469 return 1
1470 bar = lambda a: lambda b: b or True or True
1471 return bar(foo(a))(a)",
1472 "foo.py",
1473 |metric| {
1474 // 2 functions + 2 lambdas = 4
1475 insta::assert_json_snapshot!(
1476 metric.nargs,
1477 @r#"
1478 {
1479 "function_args": 3,
1480 "closure_args": 2,
1481 "function_args_average": 1.5,
1482 "closure_args_average": 1.0,
1483 "total": 5,
1484 "average": 1.25,
1485 "function_args_min": 0,
1486 "function_args_max": 2,
1487 "closure_args_min": 0,
1488 "closure_args_max": 2
1489 }
1490 "#
1491 );
1492 },
1493 );
1494 }
1495
1496 #[test]
1497 fn rust_nested_functions() {
1498 check_metrics::<RustParser>(
1499 "fn f(a: i32, b: i32) -> i32 {
1500 fn foo(a: i32) -> i32 {
1501 return a;
1502 }
1503 let bar = |a: i32, b: i32| -> i32 { a + 1 };
1504 let bar1 = |b: i32| -> i32 { b + 1 };
1505 return bar(foo(a), a);
1506 }",
1507 "foo.rs",
1508 |metric| {
1509 // 2 functions + 2 lambdas = 4
1510 insta::assert_json_snapshot!(
1511 metric.nargs,
1512 @r#"
1513 {
1514 "function_args": 3,
1515 "closure_args": 3,
1516 "function_args_average": 1.5,
1517 "closure_args_average": 1.5,
1518 "total": 6,
1519 "average": 1.5,
1520 "function_args_min": 0,
1521 "function_args_max": 2,
1522 "closure_args_min": 0,
1523 "closure_args_max": 2
1524 }
1525 "#
1526 );
1527 },
1528 );
1529 }
1530
1531 #[test]
1532 fn cpp_nested_functions() {
1533 check_metrics::<CppParser>(
1534 "int f(int a, int b, int c) {
1535 auto foo = [](int x) -> int { return x; };
1536 auto bar = [](int x, int y) -> int { return x + y; };
1537 return bar(foo(a), a);
1538 }",
1539 "foo.cpp",
1540 |metric| {
1541 // 1 functions + 2 lambdas = 3
1542 insta::assert_json_snapshot!(
1543 metric.nargs,
1544 @r#"
1545 {
1546 "function_args": 3,
1547 "closure_args": 3,
1548 "function_args_average": 3.0,
1549 "closure_args_average": 1.5,
1550 "total": 6,
1551 "average": 2.0,
1552 "function_args_min": 0,
1553 "function_args_max": 3,
1554 "closure_args_min": 0,
1555 "closure_args_max": 3
1556 }
1557 "#
1558 );
1559 },
1560 );
1561 }
1562
1563 /// Default arguments still surface as separate `parameter_declaration`
1564 /// nodes — defaults are not removed from the count. A 3-param function
1565 /// whose third parameter has a default value reports `nargs = 3`.
1566 #[test]
1567 fn cpp_default_arguments() {
1568 check_metrics::<CppParser>(
1569 "int f(int a, int b, int c = 0) {
1570 return a + b + c;
1571 }",
1572 "foo.cpp",
1573 |metric| {
1574 // 1 function, 3 parameters (defaults still count).
1575 let s = &metric.nargs;
1576 assert_eq!(s.function_args_sum(), 3);
1577 assert_eq!(s.function_args_max(), 3);
1578 insta::assert_json_snapshot!(
1579 metric.nargs,
1580 @r#"
1581 {
1582 "function_args": 3,
1583 "closure_args": 0,
1584 "function_args_average": 3.0,
1585 "closure_args_average": 0.0,
1586 "total": 3,
1587 "average": 3.0,
1588 "function_args_min": 0,
1589 "function_args_max": 3,
1590 "closure_args_min": 0,
1591 "closure_args_max": 0
1592 }
1593 "#
1594 );
1595 },
1596 );
1597 }
1598
1599 /// C-style variadic `...` parameter contributes +1 (one named declarator
1600 /// plus the `...` declarator). The grammar emits the variadic ellipsis
1601 /// as a sibling parameter node that `count_args` counts, because it is
1602 /// neither a comment nor one of the `(`, `)`, `,` tokens `CCode::is_non_arg`
1603 /// rejects.
1604 #[test]
1605 fn c_variadic_function() {
1606 check_metrics::<CParser>(
1607 "int printf(const char* fmt, ...) {
1608 return 0;
1609 }",
1610 "foo.c",
1611 |metric| {
1612 // 1 function, 2 nargs: `fmt` and `...`
1613 let s = &metric.nargs;
1614 assert_eq!(s.function_args_sum(), 2);
1615 assert_eq!(s.function_args_max(), 2);
1616 insta::assert_json_snapshot!(
1617 metric.nargs,
1618 @r#"
1619 {
1620 "function_args": 2,
1621 "closure_args": 0,
1622 "function_args_average": 2.0,
1623 "closure_args_average": 0.0,
1624 "total": 2,
1625 "average": 2.0,
1626 "function_args_min": 0,
1627 "function_args_max": 2,
1628 "closure_args_min": 0,
1629 "closure_args_max": 0
1630 }
1631 "#
1632 );
1633 },
1634 );
1635 }
1636
1637 /// C++ template parameter packs (`Args... args`) count as one runtime
1638 /// parameter (the parameter pack itself), not as N — the template
1639 /// arguments are compile-time and live on the template-parameter list,
1640 /// not on `parameters`. The tree-sitter-cpp grammar represents
1641 /// `Args... args` as a single `variadic_parameter_declaration` under
1642 /// `parameters`.
1643 #[test]
1644 fn cpp_template_parameter_pack() {
1645 check_metrics::<CppParser>(
1646 "template<typename... Args>
1647 int sum(int seed, Args... args) {
1648 return seed;
1649 }",
1650 "foo.cpp",
1651 |metric| {
1652 // 1 function, 2 nargs: `seed` and `Args... args`
1653 let s = &metric.nargs;
1654 assert_eq!(s.function_args_sum(), 2);
1655 assert_eq!(s.function_args_max(), 2);
1656 insta::assert_json_snapshot!(
1657 metric.nargs,
1658 @r#"
1659 {
1660 "function_args": 2,
1661 "closure_args": 0,
1662 "function_args_average": 2.0,
1663 "closure_args_average": 0.0,
1664 "total": 2,
1665 "average": 2.0,
1666 "function_args_min": 0,
1667 "function_args_max": 2,
1668 "closure_args_min": 0,
1669 "closure_args_max": 0
1670 }
1671 "#
1672 );
1673 },
1674 );
1675 }
1676
1677 /// Lambda capture list (`[=, &x]`) is not part of the parameter list.
1678 /// `compute_args` reads the `declarator` field, which only contains the
1679 /// `( … )` parameter list. Variables captured for the closure body do
1680 /// not inflate `nargs`.
1681 #[test]
1682 fn cpp_lambda_capture_not_counted() {
1683 check_metrics::<CppParser>(
1684 "int f() {
1685 int x = 1;
1686 int y = 2;
1687 auto g = [=, &x](int a, int b) -> int { return a + b + x + y; };
1688 return g(1, 2);
1689 }",
1690 "foo.cpp",
1691 |metric| {
1692 // 1 function (0 args), 1 lambda (2 args: a, b — captures `=, &x` excluded).
1693 let s = &metric.nargs;
1694 assert_eq!(s.function_args_sum(), 0);
1695 assert_eq!(s.closure_args_sum(), 2);
1696 assert_eq!(s.closure_args_max(), 2);
1697 insta::assert_json_snapshot!(
1698 metric.nargs,
1699 @r#"
1700 {
1701 "function_args": 0,
1702 "closure_args": 2,
1703 "function_args_average": 0.0,
1704 "closure_args_average": 2.0,
1705 "total": 2,
1706 "average": 1.0,
1707 "function_args_min": 0,
1708 "function_args_max": 0,
1709 "closure_args_min": 0,
1710 "closure_args_max": 2
1711 }
1712 "#
1713 );
1714 },
1715 );
1716 }
1717
1718 /// Implicit `this` on a member function is not part of the AST
1719 /// parameter list — it is an implicit argument at the language level
1720 /// only. A non-static member function `void M(int a)` reports
1721 /// `nargs = 1`, not 2.
1722 #[test]
1723 fn cpp_member_function_this_not_counted() {
1724 check_metrics::<CppParser>(
1725 "struct S {
1726 int x;
1727 int set(int a) { // implicit `this` is NOT counted
1728 this->x = a;
1729 return a;
1730 }
1731 };",
1732 "foo.cpp",
1733 |metric| {
1734 // 1 member function with 1 explicit parameter `a`.
1735 let s = &metric.nargs;
1736 assert_eq!(s.function_args_sum(), 1);
1737 assert_eq!(s.function_args_max(), 1);
1738 insta::assert_json_snapshot!(
1739 metric.nargs,
1740 @r#"
1741 {
1742 "function_args": 1,
1743 "closure_args": 0,
1744 "function_args_average": 1.0,
1745 "closure_args_average": 0.0,
1746 "total": 1,
1747 "average": 1.0,
1748 "function_args_min": 0,
1749 "function_args_max": 1,
1750 "closure_args_min": 0,
1751 "closure_args_max": 0
1752 }
1753 "#
1754 );
1755 },
1756 );
1757 }
1758
1759 #[test]
1760 fn go_zero_args() {
1761 check_metrics::<GoParser>(
1762 "package main
1763 func f() {}",
1764 "foo.go",
1765 |metric| {
1766 insta::assert_json_snapshot!(
1767 metric.nargs,
1768 @r#"
1769 {
1770 "function_args": 0,
1771 "closure_args": 0,
1772 "function_args_average": 0.0,
1773 "closure_args_average": 0.0,
1774 "total": 0,
1775 "average": 0.0,
1776 "function_args_min": 0,
1777 "function_args_max": 0,
1778 "closure_args_min": 0,
1779 "closure_args_max": 0
1780 }
1781 "#
1782 );
1783 },
1784 );
1785 }
1786
1787 #[test]
1788 fn go_multiple_args() {
1789 check_metrics::<GoParser>(
1790 "package main
1791 func f(a int, b string, c bool) {}",
1792 "foo.go",
1793 |metric| {
1794 insta::assert_json_snapshot!(
1795 metric.nargs,
1796 @r#"
1797 {
1798 "function_args": 3,
1799 "closure_args": 0,
1800 "function_args_average": 3.0,
1801 "closure_args_average": 0.0,
1802 "total": 3,
1803 "average": 3.0,
1804 "function_args_min": 0,
1805 "function_args_max": 3,
1806 "closure_args_min": 0,
1807 "closure_args_max": 0
1808 }
1809 "#
1810 );
1811 },
1812 );
1813 }
1814
1815 #[test]
1816 fn go_method_excludes_receiver() {
1817 check_metrics::<GoParser>(
1818 "package main
1819 type T struct{}
1820 func (t *T) Greet(name string) string {
1821 return name
1822 }",
1823 "foo.go",
1824 |metric| {
1825 // Receiver is in a separate `receiver` field and is not counted.
1826 insta::assert_json_snapshot!(
1827 metric.nargs,
1828 @r#"
1829 {
1830 "function_args": 1,
1831 "closure_args": 0,
1832 "function_args_average": 1.0,
1833 "closure_args_average": 0.0,
1834 "total": 1,
1835 "average": 1.0,
1836 "function_args_min": 0,
1837 "function_args_max": 1,
1838 "closure_args_min": 0,
1839 "closure_args_max": 0
1840 }
1841 "#
1842 );
1843 },
1844 );
1845 }
1846
1847 #[test]
1848 fn go_variadic() {
1849 check_metrics::<GoParser>(
1850 "package main
1851 func f(args ...int) {}",
1852 "foo.go",
1853 |metric| {
1854 insta::assert_json_snapshot!(
1855 metric.nargs,
1856 @r#"
1857 {
1858 "function_args": 1,
1859 "closure_args": 0,
1860 "function_args_average": 1.0,
1861 "closure_args_average": 0.0,
1862 "total": 1,
1863 "average": 1.0,
1864 "function_args_min": 0,
1865 "function_args_max": 1,
1866 "closure_args_min": 0,
1867 "closure_args_max": 0
1868 }
1869 "#
1870 );
1871 },
1872 );
1873 }
1874
1875 #[test]
1876 fn go_grouped_params() {
1877 check_metrics::<GoParser>(
1878 "package main
1879 func f(a, b int, c string) {}",
1880 "foo.go",
1881 |metric| {
1882 // `a, b int` is one parameter_declaration with two `name`
1883 // children — semantically two parameters.
1884 insta::assert_json_snapshot!(
1885 metric.nargs,
1886 @r#"
1887 {
1888 "function_args": 3,
1889 "closure_args": 0,
1890 "function_args_average": 3.0,
1891 "closure_args_average": 0.0,
1892 "total": 3,
1893 "average": 3.0,
1894 "function_args_min": 0,
1895 "function_args_max": 3,
1896 "closure_args_min": 0,
1897 "closure_args_max": 0
1898 }
1899 "#
1900 );
1901 },
1902 );
1903 }
1904
1905 #[test]
1906 fn go_func_literal_args() {
1907 check_metrics::<GoParser>(
1908 "package main
1909 var f = func(x, y int) int { return x + y }",
1910 "foo.go",
1911 |metric| {
1912 // Closure with grouped params: `x, y int` -> 2 closure args.
1913 insta::assert_json_snapshot!(
1914 metric.nargs,
1915 @r#"
1916 {
1917 "function_args": 0,
1918 "closure_args": 2,
1919 "function_args_average": 0.0,
1920 "closure_args_average": 2.0,
1921 "total": 2,
1922 "average": 2.0,
1923 "function_args_min": 0,
1924 "function_args_max": 0,
1925 "closure_args_min": 0,
1926 "closure_args_max": 2
1927 }
1928 "#
1929 );
1930 },
1931 );
1932 }
1933
1934 #[test]
1935 fn javascript_nested_functions() {
1936 check_metrics::<JavascriptParser>(
1937 "function f(a, b) {
1938 function foo(a, c) {
1939 return a;
1940 }
1941 var bar = function (a, b) {return a + b};
1942 function (a) {return a};
1943 return bar(foo(a), a);
1944 }",
1945 "foo.js",
1946 |metric| {
1947 // 3 functions + 1 lambdas = 4
1948 insta::assert_json_snapshot!(
1949 metric.nargs,
1950 @r#"
1951 {
1952 "function_args": 6,
1953 "closure_args": 1,
1954 "function_args_average": 2.0,
1955 "closure_args_average": 1.0,
1956 "total": 7,
1957 "average": 1.75,
1958 "function_args_min": 0,
1959 "function_args_max": 2,
1960 "closure_args_min": 0,
1961 "closure_args_max": 1
1962 }
1963 "#
1964 );
1965 },
1966 );
1967 }
1968
1969 #[test]
1970 fn perl_no_functions_and_closures() {
1971 check_metrics::<PerlParser>(
1972 "my $x = 1;
1973 print $x;",
1974 "foo.pl",
1975 |metric| {
1976 // Cross-check via nom that no spurious sub/closure was
1977 // recognised — symmetric with the other `perl_*` nargs
1978 // tests, and would catch a regression that miscounted
1979 // `print` (or similar) as a function.
1980 assert_eq!(metric.nom.functions_sum(), 0);
1981 assert_eq!(metric.nom.closures_sum(), 0);
1982 insta::assert_json_snapshot!(
1983 metric.nargs,
1984 @r#"
1985 {
1986 "function_args": 0,
1987 "closure_args": 0,
1988 "function_args_average": 0.0,
1989 "closure_args_average": 0.0,
1990 "total": 0,
1991 "average": 0.0,
1992 "function_args_min": 0,
1993 "function_args_max": 0,
1994 "closure_args_min": 0,
1995 "closure_args_max": 0
1996 }
1997 "#
1998 );
1999 },
2000 );
2001 }
2002
2003 #[test]
2004 fn perl_single_function() {
2005 // This sub declares no signature, so it has no formal parameters to
2006 // count and nargs is 0 — args arrive via `@_`. Signature-carrying
2007 // subs are counted; see `perl_signature_function`. To make sure the
2008 // test still discriminates "function parsed" from "function silently
2009 // dropped", also assert nom recognised exactly one function.
2010 check_metrics::<PerlParser>(
2011 "sub greet {
2012 my ($name) = @_;
2013 print \"hi $name\";
2014 }",
2015 "foo.pl",
2016 |metric| {
2017 assert_eq!(metric.nom.functions_sum(), 1);
2018 assert_eq!(metric.nom.closures_sum(), 0);
2019 insta::assert_json_snapshot!(
2020 metric.nargs,
2021 @r#"
2022 {
2023 "function_args": 0,
2024 "closure_args": 0,
2025 "function_args_average": 0.0,
2026 "closure_args_average": 0.0,
2027 "total": 0,
2028 "average": 0.0,
2029 "function_args_min": 0,
2030 "function_args_max": 0,
2031 "closure_args_min": 0,
2032 "closure_args_max": 0
2033 }
2034 "#
2035 );
2036 },
2037 );
2038 }
2039
2040 #[test]
2041 fn perl_single_closure() {
2042 // This closure declares no signature, so nargs stays 0; it takes its
2043 // arguments through `@_`. A signature-carrying closure also reads 0,
2044 // for an unrelated upstream-grammar reason — see
2045 // `perl_anonymous_sub_signature_is_zero`. Assert via nom that the
2046 // anonymous function was actually identified as a closure.
2047 check_metrics::<PerlParser>(
2048 "my $f = sub {
2049 my ($x) = @_;
2050 return $x + 1;
2051 };",
2052 "foo.pl",
2053 |metric| {
2054 assert_eq!(metric.nom.functions_sum(), 0);
2055 assert_eq!(metric.nom.closures_sum(), 1);
2056 insta::assert_json_snapshot!(
2057 metric.nargs,
2058 @r#"
2059 {
2060 "function_args": 0,
2061 "closure_args": 0,
2062 "function_args_average": 0.0,
2063 "closure_args_average": 0.0,
2064 "total": 0,
2065 "average": 0.0,
2066 "function_args_min": 0,
2067 "function_args_max": 0,
2068 "closure_args_min": 0,
2069 "closure_args_max": 0
2070 }
2071 "#
2072 );
2073 },
2074 );
2075 }
2076
2077 #[test]
2078 fn perl_multiple_functions() {
2079 // Neither sub declares a signature, so both count 0. Assert nom
2080 // counted both top-level subs so the test fails if either sub is
2081 // dropped.
2082 check_metrics::<PerlParser>(
2083 "sub a { return 1; }
2084 sub b {
2085 my ($x, $y) = @_;
2086 return $x + $y;
2087 }",
2088 "foo.pl",
2089 |metric| {
2090 assert_eq!(metric.nom.functions_sum(), 2);
2091 assert_eq!(metric.nom.closures_sum(), 0);
2092 insta::assert_json_snapshot!(
2093 metric.nargs,
2094 @r#"
2095 {
2096 "function_args": 0,
2097 "closure_args": 0,
2098 "function_args_average": 0.0,
2099 "closure_args_average": 0.0,
2100 "total": 0,
2101 "average": 0.0,
2102 "function_args_min": 0,
2103 "function_args_max": 0,
2104 "closure_args_min": 0,
2105 "closure_args_max": 0
2106 }
2107 "#
2108 );
2109 },
2110 );
2111 }
2112
2113 #[test]
2114 fn perl_nested_closure() {
2115 // Neither the outer sub nor the nested closure declares a signature,
2116 // so both count 0. Assert nom recognised one outer sub plus one
2117 // nested closure.
2118 check_metrics::<PerlParser>(
2119 "sub outer {
2120 my $inner = sub { return 42; };
2121 return $inner->();
2122 }",
2123 "foo.pl",
2124 |metric| {
2125 assert_eq!(metric.nom.functions_sum(), 1);
2126 assert_eq!(metric.nom.closures_sum(), 1);
2127 insta::assert_json_snapshot!(
2128 metric.nargs,
2129 @r#"
2130 {
2131 "function_args": 0,
2132 "closure_args": 0,
2133 "function_args_average": 0.0,
2134 "closure_args_average": 0.0,
2135 "total": 0,
2136 "average": 0.0,
2137 "function_args_min": 0,
2138 "function_args_max": 0,
2139 "closure_args_min": 0,
2140 "closure_args_max": 0
2141 }
2142 "#
2143 );
2144 },
2145 );
2146 }
2147
2148 /// Regression for #1147: a signature sub reported 0 because the
2149 /// signature is an unnamed `function_signature` child, not a
2150 /// `parameters` field.
2151 #[test]
2152 fn perl_signature_function() {
2153 check_metrics::<PerlParser>(
2154 "use feature 'signatures';
2155 sub add($x, $y) { return $x + $y; }",
2156 "foo.pl",
2157 |metric| {
2158 assert_eq!(metric.nom.functions_sum(), 1);
2159 let s = &metric.nargs;
2160 assert_eq!(s.function_args_sum(), 2);
2161 assert_eq!(s.function_args_max(), 2);
2162 insta::assert_json_snapshot!(
2163 metric.nargs,
2164 @r#"
2165 {
2166 "function_args": 2,
2167 "closure_args": 0,
2168 "function_args_average": 2.0,
2169 "closure_args_average": 0.0,
2170 "total": 2,
2171 "average": 2.0,
2172 "function_args_min": 0,
2173 "function_args_max": 2,
2174 "closure_args_min": 0,
2175 "closure_args_max": 0
2176 }
2177 "#
2178 );
2179 },
2180 );
2181 }
2182
2183 /// A defaulted parameter is a `binary_expression`, not a bare
2184 /// `scalar_variable`, so counting only the variable kinds would report
2185 /// 2 here instead of 3. Pins the negative filter in
2186 /// `compute_perl_args` (#1147).
2187 #[test]
2188 fn perl_signature_defaults_and_slurpy() {
2189 check_metrics::<PerlParser>(
2190 "use feature 'signatures';
2191 sub deflt($x, $y = 5, @rest) { return $x; }",
2192 "foo.pl",
2193 |metric| {
2194 assert_eq!(metric.nom.functions_sum(), 1);
2195 let s = &metric.nargs;
2196 assert_eq!(s.function_args_sum(), 3);
2197 assert_eq!(s.function_args_max(), 3);
2198 insta::assert_json_snapshot!(
2199 metric.nargs,
2200 @r#"
2201 {
2202 "function_args": 3,
2203 "closure_args": 0,
2204 "function_args_average": 3.0,
2205 "closure_args_average": 0.0,
2206 "total": 3,
2207 "average": 3.0,
2208 "function_args_min": 0,
2209 "function_args_max": 3,
2210 "closure_args_min": 0,
2211 "closure_args_max": 0
2212 }
2213 "#
2214 );
2215 },
2216 );
2217 }
2218
2219 /// A signature sub and an `@_` sub in one file: the min/max and the
2220 /// average have to keep the zero-argument sub in the divisor rather
2221 /// than folding it away.
2222 #[test]
2223 fn perl_signature_and_at_underscore_mixed() {
2224 check_metrics::<PerlParser>(
2225 "use feature 'signatures';
2226 sub sig($x, $y, $z) { return $x; }
2227 sub legacy { my ($a) = @_; return $a; }",
2228 "foo.pl",
2229 |metric| {
2230 assert_eq!(metric.nom.functions_sum(), 2);
2231 let s = &metric.nargs;
2232 assert_eq!(s.function_args_sum(), 3);
2233 assert_eq!(s.function_args_max(), 3);
2234 // 3 args over 2 functions: the zero-argument sub stays in
2235 // the divisor, so a fold that dropped it would read 3.0.
2236 insta::assert_json_snapshot!(
2237 metric.nargs,
2238 @r#"
2239 {
2240 "function_args": 3,
2241 "closure_args": 0,
2242 "function_args_average": 1.5,
2243 "closure_args_average": 0.0,
2244 "total": 3,
2245 "average": 1.5,
2246 "function_args_min": 0,
2247 "function_args_max": 3,
2248 "closure_args_min": 0,
2249 "closure_args_max": 0
2250 }
2251 "#
2252 );
2253 },
2254 );
2255 }
2256
2257 /// Perl puts subroutine attributes before the signature
2258 /// (`sub NAME ATTRS SIG BLOCK`), and a bare attribute swallows the
2259 /// signature into its own `function_attribute` node. Pins the
2260 /// one-level descent in `perl_signature`.
2261 #[test]
2262 fn perl_signature_behind_attribute() {
2263 check_metrics::<PerlParser>(
2264 "use feature 'signatures';
2265 sub attrs :lvalue ($z) { return $z; }",
2266 "foo.pl",
2267 |metric| {
2268 assert_eq!(metric.nom.functions_sum(), 1);
2269 let s = &metric.nargs;
2270 assert_eq!(s.function_args_sum(), 1);
2271 assert_eq!(s.function_args_max(), 1);
2272 insta::assert_json_snapshot!(
2273 metric.nargs,
2274 @r#"
2275 {
2276 "function_args": 1,
2277 "closure_args": 0,
2278 "function_args_average": 1.0,
2279 "closure_args_average": 0.0,
2280 "total": 1,
2281 "average": 1.0,
2282 "function_args_min": 0,
2283 "function_args_max": 1,
2284 "closure_args_min": 0,
2285 "closure_args_max": 0
2286 }
2287 "#
2288 );
2289 },
2290 );
2291 }
2292
2293 /// A multi-line signature documents its parameters with `comments`
2294 /// children sitting directly under `function_signature`, so the
2295 /// negative filter has to exclude them or a documented 3-parameter sub
2296 /// reads 6 and trips the default `nargs` limit of 5.
2297 #[test]
2298 fn perl_signature_comments_are_not_parameters() {
2299 check_metrics::<PerlParser>(
2300 "use feature 'signatures';
2301 sub documented(
2302 $host, # hostname to connect to
2303 $port, # TCP port
2304 $timeout, # seconds
2305 ) { return $host; }",
2306 "foo.pl",
2307 |metric| {
2308 assert_eq!(metric.nom.functions_sum(), 1);
2309 let s = &metric.nargs;
2310 assert_eq!(s.function_args_sum(), 3);
2311 assert_eq!(s.function_args_max(), 3);
2312 insta::assert_json_snapshot!(
2313 metric.nargs,
2314 @r#"
2315 {
2316 "function_args": 3,
2317 "closure_args": 0,
2318 "function_args_average": 3.0,
2319 "closure_args_average": 0.0,
2320 "total": 3,
2321 "average": 3.0,
2322 "function_args_min": 0,
2323 "function_args_max": 3,
2324 "closure_args_min": 0,
2325 "closure_args_max": 0
2326 }
2327 "#
2328 );
2329 },
2330 );
2331 }
2332
2333 /// The two shapes that must stay at 0 for reasons the counting rule
2334 /// depends on: an empty signature has no children but the parens, and
2335 /// a prototype (`($$)`) is a `function_prototype`, a different kind
2336 /// that `perl_signature` deliberately does not match.
2337 #[test]
2338 fn perl_empty_signature_and_prototype_are_zero() {
2339 check_metrics::<PerlParser>(
2340 "use feature 'signatures';
2341 sub empty() { return 1; }
2342 sub proto($$) { return 1; }",
2343 "foo.pl",
2344 |metric| {
2345 assert_eq!(metric.nom.functions_sum(), 2);
2346 let s = &metric.nargs;
2347 assert_eq!(s.function_args_sum(), 0);
2348 assert_eq!(s.function_args_max(), 0);
2349 },
2350 );
2351 }
2352
2353 /// Perl 5.38's `method` is a second `is_func` kind
2354 /// (`function_definition_without_sub`) reaching the same helper, so it
2355 /// gets its own fixture rather than riding on the `sub` tests.
2356 #[test]
2357 fn perl_method_signature_function() {
2358 check_metrics::<PerlParser>(
2359 "use v5.38;
2360 class Point {
2361 method shift_by($dx, $dy) { return $dx; }
2362 }",
2363 "foo.pl",
2364 |metric| {
2365 let s = &metric.nargs;
2366 assert_eq!(s.function_args_sum(), 2);
2367 assert_eq!(s.function_args_max(), 2);
2368 },
2369 );
2370 }
2371
2372 /// `FunctionSignature2` is the hidden `_function_signature` supertype;
2373 /// `perl_signature` lists it defensively. Pin that the grammar never
2374 /// emits it, so a bump that promotes the rule fails loudly instead of
2375 /// changing behaviour invisibly (lesson 34).
2376 #[test]
2377 fn perl_hidden_function_signature_is_unreachable() {
2378 let mut hidden = false;
2379 let mut emitted = false;
2380 crate::test_support::for_each_node_with_chain::<PerlCode>(
2381 b"use feature 'signatures';\nsub add($x, $y) { return $x + $y; }\n",
2382 |node, _| {
2383 hidden |= node.kind_id() == Perl::FunctionSignature2 as u16;
2384 emitted |= node.kind_id() == Perl::FunctionSignature as u16;
2385 },
2386 );
2387 assert!(
2388 emitted,
2389 "fixture must reach a real `function_signature`, else the \
2390 hidden-rule check below is vacuous"
2391 );
2392 assert!(
2393 !hidden,
2394 "grammar now emits the hidden `_function_signature`; re-check \
2395 the defensive arm in `perl_signature`"
2396 );
2397 }
2398
2399 /// Upstream-grammar limitation, deliberately pinned: tree-sitter-perl
2400 /// 1.1.2 parses an anonymous sub's signature inside an `ERROR` node,
2401 /// so a signature-carrying closure counts 0. A grammar bump that fixes
2402 /// the parse should fail this test rather than shift metrics silently.
2403 #[test]
2404 fn perl_anonymous_sub_signature_is_zero() {
2405 check_metrics::<PerlParser>(
2406 "use feature 'signatures';
2407 my $mul = sub ($p, $q) { return $p * $q; };",
2408 "foo.pl",
2409 |metric| {
2410 assert_eq!(metric.nom.functions_sum(), 0);
2411 assert_eq!(metric.nom.closures_sum(), 1);
2412 let s = &metric.nargs;
2413 assert_eq!(s.closure_args_sum(), 0);
2414 assert_eq!(s.closure_args_max(), 0);
2415 insta::assert_json_snapshot!(
2416 metric.nargs,
2417 @r#"
2418 {
2419 "function_args": 0,
2420 "closure_args": 0,
2421 "function_args_average": 0.0,
2422 "closure_args_average": 0.0,
2423 "total": 0,
2424 "average": 0.0,
2425 "function_args_min": 0,
2426 "function_args_max": 0,
2427 "closure_args_min": 0,
2428 "closure_args_max": 0
2429 }
2430 "#
2431 );
2432 },
2433 );
2434 }
2435
2436 #[test]
2437 fn java_no_functions() {
2438 check_metrics::<JavaParser>(
2439 "class Foo {
2440 int x = 42;
2441 String name = \"hello\";
2442 }",
2443 "foo.java",
2444 |metric| {
2445 insta::assert_json_snapshot!(
2446 metric.nargs,
2447 @r#"
2448 {
2449 "function_args": 0,
2450 "closure_args": 0,
2451 "function_args_average": 0.0,
2452 "closure_args_average": 0.0,
2453 "total": 0,
2454 "average": 0.0,
2455 "function_args_min": 0,
2456 "function_args_max": 0,
2457 "closure_args_min": 0,
2458 "closure_args_max": 0
2459 }
2460 "#
2461 );
2462 },
2463 );
2464 }
2465
2466 #[test]
2467 fn java_single_method() {
2468 check_metrics::<JavaParser>(
2469 "class Foo {
2470 void greet(String name, int count) {
2471 return;
2472 }
2473 }",
2474 "foo.java",
2475 |metric| {
2476 insta::assert_json_snapshot!(
2477 metric.nargs,
2478 @r#"
2479 {
2480 "function_args": 2,
2481 "closure_args": 0,
2482 "function_args_average": 2.0,
2483 "closure_args_average": 0.0,
2484 "total": 2,
2485 "average": 2.0,
2486 "function_args_min": 0,
2487 "function_args_max": 2,
2488 "closure_args_min": 0,
2489 "closure_args_max": 0
2490 }
2491 "#
2492 );
2493 },
2494 );
2495 }
2496
2497 #[test]
2498 fn java_multiple_methods() {
2499 check_metrics::<JavaParser>(
2500 "class Foo {
2501 void a(int x) {
2502 return;
2503 }
2504 void b(int x, int y, int z) {
2505 return;
2506 }
2507 }",
2508 "foo.java",
2509 |metric| {
2510 insta::assert_json_snapshot!(
2511 metric.nargs,
2512 @r#"
2513 {
2514 "function_args": 4,
2515 "closure_args": 0,
2516 "function_args_average": 2.0,
2517 "closure_args_average": 0.0,
2518 "total": 4,
2519 "average": 2.0,
2520 "function_args_min": 0,
2521 "function_args_max": 3,
2522 "closure_args_min": 0,
2523 "closure_args_max": 0
2524 }
2525 "#
2526 );
2527 },
2528 );
2529 }
2530
2531 #[test]
2532 fn java_constructor_args() {
2533 check_metrics::<JavaParser>(
2534 "class Foo {
2535 Foo(String name, int age) {
2536 return;
2537 }
2538 }",
2539 "foo.java",
2540 |metric| {
2541 insta::assert_json_snapshot!(
2542 metric.nargs,
2543 @r#"
2544 {
2545 "function_args": 2,
2546 "closure_args": 0,
2547 "function_args_average": 2.0,
2548 "closure_args_average": 0.0,
2549 "total": 2,
2550 "average": 2.0,
2551 "function_args_min": 0,
2552 "function_args_max": 2,
2553 "closure_args_min": 0,
2554 "closure_args_max": 0
2555 }
2556 "#
2557 );
2558 },
2559 );
2560 }
2561
2562 /// A record's compact constructor (`R { … }`, JLS 8.10.4) writes no
2563 /// formal parameter list: its parameters are the record's components,
2564 /// which the grammar hangs off the enclosing `record_declaration`.
2565 /// `nargs` reports the component count so the two spellings of one
2566 /// constructor agree — the canonical `R(int a, int b) { … }` reports
2567 /// 2, and so does the compact form (#1160).
2568 ///
2569 /// The records are nested, and carry *different* component counts, so
2570 /// the fixture pins which record each constructor resolved to. The
2571 /// lookup steps two ancestors up from the constructor; a version that
2572 /// walked to the outermost record instead would give `Single` 2 and
2573 /// total 4, and one that read the constructor node itself — which has
2574 /// no `parameters` field — would give 0.
2575 #[test]
2576 fn java_record_compact_constructor_counts_record_components() {
2577 check_metrics::<JavaParser>(
2578 "record Pair(int a, int b) {
2579 Pair { }
2580 record Single(int c) {
2581 Single { }
2582 }
2583 }",
2584 "foo.java",
2585 |metric| {
2586 let s = &metric.nargs;
2587 // Two constructors carrying 2 and 1 arguments. Pre-fix,
2588 // `compact_constructor_declaration` was not a function at
2589 // all, so both the count and the sum were 0.
2590 assert_eq!(metric.nom.functions_sum(), 2);
2591 assert_eq!(s.function_args_sum(), 3);
2592 // A sum of 3 across two functions whose largest is 2 can
2593 // only be 2 + 1.
2594 assert_eq!(s.function_args_max(), 2);
2595 assert_eq!(s.closure_args_sum(), 0);
2596 },
2597 );
2598 }
2599
2600 /// Java's explicit receiver parameter (`void m(S this, int a)`, JLS
2601 /// 8.4.1) parses as a `receiver_parameter` node — distinct from a real
2602 /// `formal_parameter` — and binds `this`, not a value. Like Rust's
2603 /// `self_parameter` (#457), Go's `receiver` field, and C++'s implicit
2604 /// `this`, it must not be counted as a formal parameter (#470).
2605 #[test]
2606 fn java_method_excludes_explicit_receiver() {
2607 check_metrics::<JavaParser>(
2608 "class S {
2609 void m(S this, int a) {} // receiver + 1 -> 1 arg
2610 void n(int a, int b) {} // control: 2 real params
2611 void r(S this) {} // receiver only -> 0 args
2612 }",
2613 "foo.java",
2614 |metric| {
2615 // m:1 + n:2 + r:0. The two receiver parameters contribute
2616 // nothing. Pre-fix, the receivers inflated this to sum = 5
2617 // (m:2 + n:2 + r:1), max = 2. After #470: sum = 3, max = 2.
2618 let s = &metric.nargs;
2619 assert_eq!(s.function_args_sum(), 3);
2620 assert_eq!(s.function_args_max(), 2);
2621 },
2622 );
2623 }
2624
2625 #[test]
2626 fn java_lambda_args() {
2627 check_metrics::<JavaParser>(
2628 "class Foo {
2629 void run() {
2630 Runnable r = (int a, int b) -> a + b;
2631 }
2632 }",
2633 "foo.java",
2634 |metric| {
2635 insta::assert_json_snapshot!(
2636 metric.nargs,
2637 @r#"
2638 {
2639 "function_args": 0,
2640 "closure_args": 2,
2641 "function_args_average": 0.0,
2642 "closure_args_average": 2.0,
2643 "total": 2,
2644 "average": 1.0,
2645 "function_args_min": 0,
2646 "function_args_max": 0,
2647 "closure_args_min": 0,
2648 "closure_args_max": 2
2649 }
2650 "#
2651 );
2652 },
2653 );
2654 }
2655
2656 #[test]
2657 fn groovy_no_functions_and_closures() {
2658 check_metrics::<GroovyParser>("int x = 1", "foo.groovy", |metric| {
2659 assert_eq!(metric.nargs.total(), 0);
2660 });
2661 }
2662
2663 #[test]
2664 fn groovy_single_method() {
2665 check_metrics::<GroovyParser>(
2666 "class A {
2667 void greet(String name, int times) {
2668 println(name)
2669 }
2670 }",
2671 "foo.groovy",
2672 |metric| {
2673 assert_eq!(metric.nargs.function_args_sum(), 2);
2674 assert_eq!(metric.nargs.closure_args_sum(), 0);
2675 },
2676 );
2677 }
2678
2679 #[test]
2680 fn groovy_multiple_methods() {
2681 check_metrics::<GroovyParser>(
2682 "class A {
2683 int add(int x, int y) { x + y }
2684 int sub(int x, int y, int z) { x - y - z }
2685 }",
2686 "foo.groovy",
2687 |metric| {
2688 assert_eq!(metric.nargs.function_args_sum(), 5);
2689 },
2690 );
2691 }
2692
2693 #[test]
2694 fn groovy_lambda_args() {
2695 // Two-parameter Groovy closure inside a method body. Groovy's
2696 // primary lambda-shaped construct is the closure
2697 // (`{ params -> body }`); the dekobon grammar does not model
2698 // Java's `(params) -> body` arrow form because real-world
2699 // Groovy code rarely uses it.
2700 check_metrics::<GroovyParser>(
2701 "class Foo {
2702 void run() {
2703 def f = { int a, int b -> a + b }
2704 }
2705 }",
2706 "foo.groovy",
2707 |metric| {
2708 assert_eq!(metric.nargs.closure_args_sum(), 2);
2709 },
2710 );
2711 }
2712
2713 #[test]
2714 fn groovy_implicit_it_not_counted() {
2715 // The `it` implicit closure parameter is just an identifier in
2716 // the grammar — no `formal_parameters` node. `nargs` counts
2717 // declared parameters only, so this closure has 0.
2718 check_metrics::<GroovyParser>(
2719 "class A {
2720 void apply() {
2721 [1, 2, 3].each { println(it) }
2722 }
2723 }",
2724 "foo.groovy",
2725 |metric| {
2726 assert_eq!(metric.nargs.closure_args_sum(), 0);
2727 },
2728 );
2729 }
2730
2731 #[test]
2732 fn csharp_no_functions() {
2733 check_metrics::<CsharpParser>(
2734 "class Foo {
2735 int x = 42;
2736 string Name = \"hello\";
2737 }",
2738 "foo.cs",
2739 |metric| {
2740 insta::assert_json_snapshot!(
2741 metric.nargs,
2742 @r#"
2743 {
2744 "function_args": 0,
2745 "closure_args": 0,
2746 "function_args_average": 0.0,
2747 "closure_args_average": 0.0,
2748 "total": 0,
2749 "average": 0.0,
2750 "function_args_min": 0,
2751 "function_args_max": 0,
2752 "closure_args_min": 0,
2753 "closure_args_max": 0
2754 }
2755 "#
2756 );
2757 },
2758 );
2759 }
2760
2761 #[test]
2762 fn csharp_single_method() {
2763 check_metrics::<CsharpParser>(
2764 "class Foo {
2765 void Greet(string name, int count) {
2766 return;
2767 }
2768 }",
2769 "foo.cs",
2770 |metric| {
2771 insta::assert_json_snapshot!(
2772 metric.nargs,
2773 @r#"
2774 {
2775 "function_args": 2,
2776 "closure_args": 0,
2777 "function_args_average": 2.0,
2778 "closure_args_average": 0.0,
2779 "total": 2,
2780 "average": 2.0,
2781 "function_args_min": 0,
2782 "function_args_max": 2,
2783 "closure_args_min": 0,
2784 "closure_args_max": 0
2785 }
2786 "#
2787 );
2788 },
2789 );
2790 }
2791
2792 #[test]
2793 fn csharp_multiple_methods() {
2794 check_metrics::<CsharpParser>(
2795 "class Foo {
2796 void A(int x) {
2797 return;
2798 }
2799 void B(int x, int y, int z) {
2800 return;
2801 }
2802 }",
2803 "foo.cs",
2804 |metric| {
2805 insta::assert_json_snapshot!(
2806 metric.nargs,
2807 @r#"
2808 {
2809 "function_args": 4,
2810 "closure_args": 0,
2811 "function_args_average": 2.0,
2812 "closure_args_average": 0.0,
2813 "total": 4,
2814 "average": 2.0,
2815 "function_args_min": 0,
2816 "function_args_max": 3,
2817 "closure_args_min": 0,
2818 "closure_args_max": 0
2819 }
2820 "#
2821 );
2822 },
2823 );
2824 }
2825
2826 #[test]
2827 fn csharp_constructor_args() {
2828 check_metrics::<CsharpParser>(
2829 "class Foo {
2830 public Foo(string name, int age) {
2831 return;
2832 }
2833 }",
2834 "foo.cs",
2835 |metric| {
2836 insta::assert_json_snapshot!(
2837 metric.nargs,
2838 @r#"
2839 {
2840 "function_args": 2,
2841 "closure_args": 0,
2842 "function_args_average": 2.0,
2843 "closure_args_average": 0.0,
2844 "total": 2,
2845 "average": 2.0,
2846 "function_args_min": 0,
2847 "function_args_max": 2,
2848 "closure_args_min": 0,
2849 "closure_args_max": 0
2850 }
2851 "#
2852 );
2853 },
2854 );
2855 }
2856
2857 #[test]
2858 fn csharp_lambda_args() {
2859 check_metrics::<CsharpParser>(
2860 "class Foo {
2861 void Run() {
2862 System.Func<int, int, int> f = (int a, int b) => a + b;
2863 }
2864 }",
2865 "foo.cs",
2866 |metric| {
2867 insta::assert_json_snapshot!(
2868 metric.nargs,
2869 @r#"
2870 {
2871 "function_args": 0,
2872 "closure_args": 2,
2873 "function_args_average": 0.0,
2874 "closure_args_average": 2.0,
2875 "total": 2,
2876 "average": 1.0,
2877 "function_args_min": 0,
2878 "function_args_max": 0,
2879 "closure_args_min": 0,
2880 "closure_args_max": 2
2881 }
2882 "#
2883 );
2884 },
2885 );
2886 }
2887
2888 #[test]
2889 fn tsx_function_and_arrow() {
2890 check_metrics::<TsxParser>(
2891 "function add(a: number, b: number): number {
2892 return a + b;
2893 }
2894 const multiply = (x: number, y: number) => x * y;",
2895 "foo.tsx",
2896 |metric| {
2897 insta::assert_json_snapshot!(
2898 metric.nargs,
2899 @r#"
2900 {
2901 "function_args": 4,
2902 "closure_args": 0,
2903 "function_args_average": 2.0,
2904 "closure_args_average": 0.0,
2905 "total": 4,
2906 "average": 2.0,
2907 "function_args_min": 0,
2908 "function_args_max": 2,
2909 "closure_args_min": 0,
2910 "closure_args_max": 0
2911 }
2912 "#
2913 );
2914 },
2915 );
2916 }
2917
2918 #[test]
2919 fn typescript_typed_and_optional_params() {
2920 check_metrics::<TypescriptParser>(
2921 "function format(value: number, prefix?: string, suffix?: string): string {
2922 return (prefix ?? '') + value.toString() + (suffix ?? '');
2923 }
2924 const identity = (x: number): number => x;",
2925 "foo.ts",
2926 |metric| {
2927 insta::assert_json_snapshot!(
2928 metric.nargs,
2929 @r#"
2930 {
2931 "function_args": 4,
2932 "closure_args": 0,
2933 "function_args_average": 2.0,
2934 "closure_args_average": 0.0,
2935 "total": 4,
2936 "average": 2.0,
2937 "function_args_min": 0,
2938 "function_args_max": 3,
2939 "closure_args_min": 0,
2940 "closure_args_max": 0
2941 }
2942 "#
2943 );
2944 },
2945 );
2946 }
2947
2948 #[test]
2949 fn mozjs_single_function() {
2950 check_metrics::<MozjsParser>(
2951 "function f(a, b) {
2952 return a * b;
2953 }",
2954 "foo.js",
2955 |metric| {
2956 insta::assert_json_snapshot!(
2957 metric.nargs,
2958 @r#"
2959 {
2960 "function_args": 2,
2961 "closure_args": 0,
2962 "function_args_average": 2.0,
2963 "closure_args_average": 0.0,
2964 "total": 2,
2965 "average": 2.0,
2966 "function_args_min": 0,
2967 "function_args_max": 2,
2968 "closure_args_min": 0,
2969 "closure_args_max": 0
2970 }
2971 "#
2972 );
2973 },
2974 );
2975 }
2976
2977 #[test]
2978 fn mozjs_closure_args() {
2979 check_metrics::<MozjsParser>("function (a, b) {return a + b};", "foo.js", |metric| {
2980 insta::assert_json_snapshot!(
2981 metric.nargs,
2982 @r#"
2983 {
2984 "function_args": 0,
2985 "closure_args": 2,
2986 "function_args_average": 0.0,
2987 "closure_args_average": 2.0,
2988 "total": 2,
2989 "average": 2.0,
2990 "function_args_min": 0,
2991 "function_args_max": 0,
2992 "closure_args_min": 0,
2993 "closure_args_max": 2
2994 }
2995 "#
2996 );
2997 });
2998 }
2999
3000 // Regression tests for issue #77: bare-identifier arrow functions
3001 // (`x => x`) use the singular `parameter` field instead of the plural
3002 // `parameters` field, and were previously counted as nargs=0.
3003 //
3004 // `total` is used so the assertion is independent of whether the
3005 // arrow function is classified as a function or a closure (this depends
3006 // on its enclosing context — e.g. a `VariableDeclarator` ancestor makes
3007 // it a function).
3008
3009 #[test]
3010 fn javascript_bare_arrow_function() {
3011 check_metrics::<JavascriptParser>("const f = x => x;", "foo.js", |metric| {
3012 assert_eq!(metric.nargs.total(), 1);
3013 });
3014 }
3015
3016 #[test]
3017 fn javascript_async_bare_arrow_function() {
3018 check_metrics::<JavascriptParser>("const f = async x => x;", "foo.js", |metric| {
3019 assert_eq!(metric.nargs.total(), 1);
3020 });
3021 }
3022
3023 #[test]
3024 fn javascript_parenthesized_arrow_function() {
3025 check_metrics::<JavascriptParser>("const f = (x) => x;", "foo.js", |metric| {
3026 assert_eq!(metric.nargs.total(), 1);
3027 });
3028 }
3029
3030 #[test]
3031 fn javascript_multi_parenthesized_arrow_function() {
3032 check_metrics::<JavascriptParser>("const f = (x, y) => x + y;", "foo.js", |metric| {
3033 assert_eq!(metric.nargs.total(), 2);
3034 });
3035 }
3036
3037 #[test]
3038 fn typescript_bare_arrow_function() {
3039 check_metrics::<TypescriptParser>("const f = x => x;", "foo.ts", |metric| {
3040 assert_eq!(metric.nargs.total(), 1);
3041 });
3042 }
3043
3044 #[test]
3045 fn typescript_async_bare_arrow_function() {
3046 check_metrics::<TypescriptParser>("const f = async x => x;", "foo.ts", |metric| {
3047 assert_eq!(metric.nargs.total(), 1);
3048 });
3049 }
3050
3051 #[test]
3052 fn typescript_parenthesized_arrow_function() {
3053 check_metrics::<TypescriptParser>("const f = (x: number) => x;", "foo.ts", |metric| {
3054 assert_eq!(metric.nargs.total(), 1);
3055 });
3056 }
3057
3058 #[test]
3059 fn typescript_multi_parenthesized_arrow_function() {
3060 check_metrics::<TypescriptParser>(
3061 "const f = (x: number, y: number) => x + y;",
3062 "foo.ts",
3063 |metric| {
3064 assert_eq!(metric.nargs.total(), 2);
3065 },
3066 );
3067 }
3068
3069 #[test]
3070 fn tsx_bare_arrow_function() {
3071 check_metrics::<TsxParser>("const f = x => x;", "foo.tsx", |metric| {
3072 assert_eq!(metric.nargs.total(), 1);
3073 });
3074 }
3075
3076 #[test]
3077 fn tsx_async_bare_arrow_function() {
3078 check_metrics::<TsxParser>("const f = async x => x;", "foo.tsx", |metric| {
3079 assert_eq!(metric.nargs.total(), 1);
3080 });
3081 }
3082
3083 #[test]
3084 fn tsx_parenthesized_arrow_function() {
3085 check_metrics::<TsxParser>("const f = (x: number) => x;", "foo.tsx", |metric| {
3086 assert_eq!(metric.nargs.total(), 1);
3087 });
3088 }
3089
3090 #[test]
3091 fn tsx_multi_parenthesized_arrow_function() {
3092 check_metrics::<TsxParser>(
3093 "const f = (x: number, y: number) => x + y;",
3094 "foo.tsx",
3095 |metric| {
3096 assert_eq!(metric.nargs.total(), 2);
3097 },
3098 );
3099 }
3100
3101 #[test]
3102 fn mozjs_bare_arrow_function() {
3103 check_metrics::<MozjsParser>("const f = x => x;", "foo.js", |metric| {
3104 assert_eq!(metric.nargs.total(), 1);
3105 });
3106 }
3107
3108 #[test]
3109 fn mozjs_async_bare_arrow_function() {
3110 check_metrics::<MozjsParser>("const f = async x => x;", "foo.js", |metric| {
3111 assert_eq!(metric.nargs.total(), 1);
3112 });
3113 }
3114
3115 #[test]
3116 fn mozjs_parenthesized_arrow_function() {
3117 check_metrics::<MozjsParser>("const f = (x) => x;", "foo.js", |metric| {
3118 assert_eq!(metric.nargs.total(), 1);
3119 });
3120 }
3121
3122 #[test]
3123 fn mozjs_multi_parenthesized_arrow_function() {
3124 check_metrics::<MozjsParser>("const f = (x, y) => x + y;", "foo.js", |metric| {
3125 assert_eq!(metric.nargs.total(), 2);
3126 });
3127 }
3128
3129 #[test]
3130 fn kotlin_nargs_functions_and_closures() {
3131 check_metrics::<KotlinParser>(
3132 "fun add(a: Int, b: Int): Int {
3133 val transform = { x: Int, y: Int -> x + y }
3134 return transform(a, b)
3135 }",
3136 "foo.kt",
3137 |metric| {
3138 insta::assert_json_snapshot!(
3139 metric.nargs,
3140 @r#"
3141 {
3142 "function_args": 2,
3143 "closure_args": 2,
3144 "function_args_average": 2.0,
3145 "closure_args_average": 2.0,
3146 "total": 4,
3147 "average": 2.0,
3148 "function_args_min": 0,
3149 "function_args_max": 2,
3150 "closure_args_min": 0,
3151 "closure_args_max": 2
3152 }
3153 "#
3154 );
3155 },
3156 );
3157 }
3158
3159 #[test]
3160 fn lua_no_functions_and_closures() {
3161 check_metrics::<LuaParser>("local x = 1", "foo.lua", |metric| {
3162 // No functions or closures: both halves are zero.
3163 assert_eq!(metric.nargs.function_args_sum(), 0);
3164 assert_eq!(metric.nargs.closure_args_sum(), 0);
3165 insta::assert_json_snapshot!(metric.nargs);
3166 });
3167 }
3168
3169 #[test]
3170 fn lua_single_function() {
3171 check_metrics::<LuaParser>("function f(a, b) return a + b end", "foo.lua", |metric| {
3172 // f(a, b) → fn_args_sum 2, no closures.
3173 assert_eq!(metric.nargs.function_args_sum(), 2);
3174 assert_eq!(metric.nargs.closure_args_sum(), 0);
3175 insta::assert_json_snapshot!(metric.nargs);
3176 });
3177 }
3178
3179 #[test]
3180 fn lua_single_closure() {
3181 check_metrics::<LuaParser>(
3182 "local f = function(a, b) return a + b end",
3183 "foo.lua",
3184 |metric| {
3185 // Anonymous `function(a, b)` bound via `local` → closure_args_sum 2.
3186 assert_eq!(metric.nargs.function_args_sum(), 0);
3187 assert_eq!(metric.nargs.closure_args_sum(), 2);
3188 insta::assert_json_snapshot!(metric.nargs);
3189 },
3190 );
3191 }
3192
3193 #[test]
3194 fn lua_functions() {
3195 check_metrics::<LuaParser>(
3196 "function f(a) return a end
3197function g(x, y, z) return x + y + z end",
3198 "foo.lua",
3199 |metric| {
3200 // f(a)=1 + g(x,y,z)=3 → fn_args_sum 4.
3201 assert_eq!(metric.nargs.function_args_sum(), 4);
3202 assert_eq!(metric.nargs.closure_args_sum(), 0);
3203 insta::assert_json_snapshot!(metric.nargs);
3204 },
3205 );
3206 }
3207
3208 #[test]
3209 fn lua_vararg_function() {
3210 // `...` is a vararg_expression node and counts as one argument.
3211 check_metrics::<LuaParser>("function f(a, ...) return a end", "foo.lua", |metric| {
3212 // a + ... → fn_args_sum 2.
3213 assert_eq!(metric.nargs.function_args_sum(), 2);
3214 assert_eq!(metric.nargs.closure_args_sum(), 0);
3215 insta::assert_json_snapshot!(metric.nargs);
3216 });
3217 }
3218
3219 #[test]
3220 fn lua_colon_method_nargs() {
3221 // Colon syntax: `self` is implicit and NOT in the `parameters` node.
3222 // Only explicit params (a, b) are counted.
3223 check_metrics::<LuaParser>(
3224 "function obj:method(a, b) return a + b end",
3225 "foo.lua",
3226 |metric| {
3227 // Only explicit a, b → fn_args_sum 2 (implicit self excluded).
3228 assert_eq!(metric.nargs.function_args_sum(), 2);
3229 assert_eq!(metric.nargs.closure_args_sum(), 0);
3230 insta::assert_json_snapshot!(metric.nargs);
3231 },
3232 );
3233 }
3234
3235 #[test]
3236 fn tcl_no_functions() {
3237 check_metrics::<TclParser>("set x 1", "foo.tcl", |metric| {
3238 // Bare `set` command, no procs → both halves zero.
3239 assert_eq!(metric.nargs.function_args_sum(), 0);
3240 assert_eq!(metric.nargs.closure_args_sum(), 0);
3241 insta::assert_json_snapshot!(metric.nargs);
3242 });
3243 }
3244
3245 #[test]
3246 fn tcl_single_function() {
3247 check_metrics::<TclParser>("proc f {a b} { puts $a }", "foo.tcl", |metric| {
3248 // proc f {a b} → fn_args_sum 2.
3249 assert_eq!(metric.nargs.function_args_sum(), 2);
3250 assert_eq!(metric.nargs.closure_args_sum(), 0);
3251 insta::assert_json_snapshot!(metric.nargs);
3252 });
3253 }
3254
3255 #[test]
3256 fn tcl_single_function_no_args() {
3257 check_metrics::<TclParser>("proc f {} { puts hello }", "foo.tcl", |metric| {
3258 // proc f {} → empty arg list, fn_args_sum 0.
3259 assert_eq!(metric.nargs.function_args_sum(), 0);
3260 assert_eq!(metric.nargs.closure_args_sum(), 0);
3261 insta::assert_json_snapshot!(metric.nargs);
3262 });
3263 }
3264
3265 #[test]
3266 fn tcl_functions() {
3267 check_metrics::<TclParser>(
3268 "proc f {a b} { puts $a }
3269proc g {x y z} { puts $x }",
3270 "foo.tcl",
3271 |metric| {
3272 // f(a,b)=2 + g(x,y,z)=3 → fn_args_sum 5.
3273 assert_eq!(metric.nargs.function_args_sum(), 5);
3274 assert_eq!(metric.nargs.closure_args_sum(), 0);
3275 insta::assert_json_snapshot!(metric.nargs);
3276 },
3277 );
3278 }
3279
3280 #[test]
3281 fn tcl_nested_functions() {
3282 check_metrics::<TclParser>(
3283 "proc outer {a} {
3284 proc inner {x y} { puts $x }
3285 inner $a $a
3286}",
3287 "foo.tcl",
3288 |metric| {
3289 // outer(a)=1 + inner(x,y)=2 → fn_args_sum 3.
3290 assert_eq!(metric.nargs.function_args_sum(), 3);
3291 assert_eq!(metric.nargs.closure_args_sum(), 0);
3292 insta::assert_json_snapshot!(metric.nargs);
3293 },
3294 );
3295 }
3296
3297 #[test]
3298 fn tcl_args_vararg() {
3299 // `args` is the Tcl variadic catch-all; it counts as one argument.
3300 check_metrics::<TclParser>("proc f {a b args} { puts $a }", "foo.tcl", |metric| {
3301 // a + b + args → fn_args_sum 3 (variadic is one slot).
3302 assert_eq!(metric.nargs.function_args_sum(), 3);
3303 assert_eq!(metric.nargs.closure_args_sum(), 0);
3304 insta::assert_json_snapshot!(metric.nargs);
3305 });
3306 }
3307
3308 #[test]
3309 fn tcl_default_arg() {
3310 // `{name default}` is a single argument with a default value.
3311 check_metrics::<TclParser>(
3312 "proc greet {{name World} greeting} {
3313 puts \"$greeting, $name!\"
3314}",
3315 "foo.tcl",
3316 |metric| {
3317 // {name World} counts as one slot + greeting → fn_args_sum 2.
3318 assert_eq!(metric.nargs.function_args_sum(), 2);
3319 assert_eq!(metric.nargs.closure_args_sum(), 0);
3320 insta::assert_json_snapshot!(metric.nargs);
3321 },
3322 );
3323 }
3324
3325 #[test]
3326 fn kotlin_zero_args() {
3327 check_metrics::<KotlinParser>("fun f(): Int { return 42 }", "foo.kt", |metric| {
3328 // fun f() → empty parameter list, fn_args_sum 0.
3329 assert_eq!(metric.nargs.function_args_sum(), 0);
3330 assert_eq!(metric.nargs.closure_args_sum(), 0);
3331 insta::assert_json_snapshot!(metric.nargs);
3332 });
3333 }
3334
3335 #[test]
3336 fn kotlin_single_arg() {
3337 check_metrics::<KotlinParser>(
3338 "fun double(x: Int): Int { return x * 2 }",
3339 "foo.kt",
3340 |metric| {
3341 // double(x) → fn_args_sum 1.
3342 assert_eq!(metric.nargs.function_args_sum(), 1);
3343 assert_eq!(metric.nargs.closure_args_sum(), 0);
3344 insta::assert_json_snapshot!(metric.nargs);
3345 },
3346 );
3347 }
3348
3349 #[test]
3350 fn kotlin_multiple_args() {
3351 check_metrics::<KotlinParser>(
3352 "fun add(a: Int, b: Int, c: Int): Int { return a + b + c }",
3353 "foo.kt",
3354 |metric| {
3355 // add(a, b, c) → fn_args_sum 3.
3356 assert_eq!(metric.nargs.function_args_sum(), 3);
3357 assert_eq!(metric.nargs.closure_args_sum(), 0);
3358 insta::assert_json_snapshot!(metric.nargs);
3359 },
3360 );
3361 }
3362
3363 #[test]
3364 fn kotlin_default_args() {
3365 check_metrics::<KotlinParser>(
3366 "fun greet(name: String = \"World\", greeting: String = \"Hello\"): String {
3367 return \"$greeting, $name!\"
3368 }",
3369 "foo.kt",
3370 |metric| {
3371 // Defaults still count as parameter slots → fn_args_sum 2.
3372 assert_eq!(metric.nargs.function_args_sum(), 2);
3373 assert_eq!(metric.nargs.closure_args_sum(), 0);
3374 insta::assert_json_snapshot!(metric.nargs);
3375 },
3376 );
3377 }
3378
3379 #[test]
3380 fn kotlin_empty_lambda() {
3381 // Two lambdas in the same function body: one with two explicit parameters
3382 // (proving the lambda path is taken and args are counted), and one with an
3383 // explicit empty parameter list `{ -> expr }` (proving
3384 // `compute_kotlin_lambda_args` returns 0 for it without crashing or
3385 // accidentally counting tokens inside the arrow expression).
3386 // If the grammar fails to parse either lambda, `total_closures` would be
3387 // lower than 2, making the snapshot unambiguous.
3388 check_metrics::<KotlinParser>(
3389 "fun f() {
3390 val two = { x: Int, y: Int -> x + y }
3391 val zero = { -> 42 }
3392 }",
3393 "foo.kt",
3394 |metric| {
3395 // Outer fun f() has 0 params; two lambdas counted as closures:
3396 // {x, y -> ...} contributes 2, {-> 42} contributes 0 →
3397 // closure_args_sum 2 across two closure entries.
3398 assert_eq!(metric.nargs.function_args_sum(), 0);
3399 assert_eq!(metric.nargs.closure_args_sum(), 2);
3400 insta::assert_json_snapshot!(metric.nargs);
3401 },
3402 );
3403 }
3404
3405 #[test]
3406 fn kotlin_anonymous_function() {
3407 // `fun(x: Int, y: Int) = x + y` — anonymous function expression.
3408 // The grammar surfaces it as an `AnonymousFunction` node, which routes
3409 // through `compute_kotlin_func_args` (not the lambda path).
3410 check_metrics::<KotlinParser>(
3411 "val add = fun(x: Int, y: Int): Int = x + y",
3412 "foo.kt",
3413 |metric| {
3414 // Anonymous fun(x, y) is classified as a closure → closure_args_sum 2.
3415 assert_eq!(metric.nargs.function_args_sum(), 0);
3416 assert_eq!(metric.nargs.closure_args_sum(), 2);
3417 insta::assert_json_snapshot!(metric.nargs);
3418 },
3419 );
3420 }
3421
3422 #[test]
3423 fn php_no_functions_and_closures() {
3424 check_metrics::<PhpParser>("<?php $a = 42;", "foo.php", |metric| {
3425 insta::assert_json_snapshot!(
3426 metric.nargs,
3427 @r#"
3428 {
3429 "function_args": 0,
3430 "closure_args": 0,
3431 "function_args_average": 0.0,
3432 "closure_args_average": 0.0,
3433 "total": 0,
3434 "average": 0.0,
3435 "function_args_min": 0,
3436 "function_args_max": 0,
3437 "closure_args_min": 0,
3438 "closure_args_max": 0
3439 }
3440 "#
3441 );
3442 });
3443 }
3444
3445 #[test]
3446 fn php_single_function() {
3447 // Two parameters in a regular function.
3448 check_metrics::<PhpParser>(
3449 "<?php
3450 function f(bool $a, int $b): bool {
3451 if ($a) { return $a; }
3452 return false;
3453 }",
3454 "foo.php",
3455 |metric| {
3456 insta::assert_json_snapshot!(
3457 metric.nargs,
3458 @r#"
3459 {
3460 "function_args": 2,
3461 "closure_args": 0,
3462 "function_args_average": 2.0,
3463 "closure_args_average": 0.0,
3464 "total": 2,
3465 "average": 2.0,
3466 "function_args_min": 0,
3467 "function_args_max": 2,
3468 "closure_args_min": 0,
3469 "closure_args_max": 0
3470 }
3471 "#
3472 );
3473 },
3474 );
3475 }
3476
3477 #[test]
3478 fn php_single_closure() {
3479 // Anonymous function with 2 params + arrow function with 1 param.
3480 // Each is a separate closure space.
3481 check_metrics::<PhpParser>(
3482 "<?php
3483 $f = function (int $a, int $b) { return $a + $b; };
3484 $g = fn (int $x) => $x * 2;",
3485 "foo.php",
3486 |metric| {
3487 insta::assert_json_snapshot!(
3488 metric.nargs,
3489 @r#"
3490 {
3491 "function_args": 0,
3492 "closure_args": 3,
3493 "function_args_average": 0.0,
3494 "closure_args_average": 1.5,
3495 "total": 3,
3496 "average": 1.5,
3497 "function_args_min": 0,
3498 "function_args_max": 0,
3499 "closure_args_min": 0,
3500 "closure_args_max": 2
3501 }
3502 "#
3503 );
3504 },
3505 );
3506 }
3507
3508 #[test]
3509 fn php_functions() {
3510 // Two top-level functions, 1 + 2 args.
3511 check_metrics::<PhpParser>(
3512 "<?php
3513 function a(int $x): int { return $x; }
3514 function b(int $x, int $y): int { return $x + $y; }",
3515 "foo.php",
3516 |metric| {
3517 insta::assert_json_snapshot!(
3518 metric.nargs,
3519 @r#"
3520 {
3521 "function_args": 3,
3522 "closure_args": 0,
3523 "function_args_average": 1.5,
3524 "closure_args_average": 0.0,
3525 "total": 3,
3526 "average": 1.5,
3527 "function_args_min": 0,
3528 "function_args_max": 2,
3529 "closure_args_min": 0,
3530 "closure_args_max": 0
3531 }
3532 "#
3533 );
3534 },
3535 );
3536 }
3537
3538 #[test]
3539 fn php_nested_functions() {
3540 // PHP cannot define nested named functions inside a function body
3541 // syntactically, but a class with methods exhibits the same shape:
3542 // a top-level scope plus inner function-spaces.
3543 check_metrics::<PhpParser>(
3544 "<?php
3545 class A {
3546 public function outer(int $a): int {
3547 $f = function (int $b) use ($a) { return $a + $b; };
3548 return $f($a);
3549 }
3550 }",
3551 "foo.php",
3552 |metric| {
3553 insta::assert_json_snapshot!(
3554 metric.nargs,
3555 @r#"
3556 {
3557 "function_args": 1,
3558 "closure_args": 1,
3559 "function_args_average": 1.0,
3560 "closure_args_average": 1.0,
3561 "total": 2,
3562 "average": 1.0,
3563 "function_args_min": 0,
3564 "function_args_max": 1,
3565 "closure_args_min": 0,
3566 "closure_args_max": 1
3567 }
3568 "#
3569 );
3570 },
3571 );
3572 }
3573
3574 /// Regression for #1142: the parameter list sits two `arguments`
3575 /// levels down, so the `parameters`-field heuristic found nothing and
3576 /// every Elixir function reported 0.
3577 #[test]
3578 fn elixir_named_function_args() {
3579 check_metrics::<ElixirParser>(
3580 "defmodule Foo do\n def bar(a, b, c) do\n a + b + c\n end\nend\n",
3581 "foo.ex",
3582 |metric| {
3583 assert_eq!(metric.nom.functions_sum(), 1);
3584 let s = &metric.nargs;
3585 assert_eq!(s.function_args_sum(), 3);
3586 assert_eq!(s.function_args_max(), 3);
3587 },
3588 );
3589 }
3590
3591 /// A guard interposes a `when` `binary_operator` between the macro's
3592 /// `arguments` and the head `Call`. Without unwrapping it every
3593 /// guarded clause — a large fraction of real Elixir — counts 0.
3594 #[test]
3595 fn elixir_guarded_clause_args() {
3596 check_metrics::<ElixirParser>(
3597 "defmodule Foo do\n defp baz(x) when is_integer(x), do: x\nend\n",
3598 "foo.ex",
3599 |metric| {
3600 assert_eq!(metric.nom.functions_sum(), 1);
3601 let s = &metric.nargs;
3602 assert_eq!(s.function_args_sum(), 1);
3603 assert_eq!(s.function_args_max(), 1);
3604 },
3605 );
3606 }
3607
3608 /// `def noargs, do: 1` puts a bare `identifier` where the head `Call`
3609 /// would be. It has no parameter list, and the walk must stop there
3610 /// rather than fall through to the enclosing `arguments` — which
3611 /// holds the `do:` keyword pair and would count 1.
3612 #[test]
3613 fn elixir_zero_arg_function_has_no_parameter_list() {
3614 check_metrics::<ElixirParser>(
3615 "defmodule Foo do\n def noargs, do: 1\nend\n",
3616 "foo.ex",
3617 |metric| {
3618 assert_eq!(metric.nom.functions_sum(), 1);
3619 assert_eq!(metric.nargs.function_args_sum(), 0);
3620 },
3621 );
3622 }
3623
3624 /// Pattern and defaulted parameters are `map` and `binary_operator`
3625 /// nodes rather than plain identifiers, so the punctuation-negative
3626 /// filter is what keeps them counted.
3627 #[test]
3628 fn elixir_pattern_and_default_args() {
3629 check_metrics::<ElixirParser>(
3630 "defmodule Foo do\n def f(%{a: x}, b \\\\ 1), do: {x, b}\nend\n",
3631 "foo.ex",
3632 |metric| {
3633 assert_eq!(metric.nom.functions_sum(), 1);
3634 let s = &metric.nargs;
3635 assert_eq!(s.function_args_sum(), 2);
3636 assert_eq!(s.function_args_max(), 2);
3637 },
3638 );
3639 }
3640
3641 /// Every clause of one `fn` has the same arity, so a two-clause
3642 /// two-argument closure is 2 — summing the clauses would report 4.
3643 #[test]
3644 fn elixir_multi_clause_closure_counts_one_clause() {
3645 check_metrics::<ElixirParser>(
3646 "defmodule Foo do\n def run do\n fn\n a, b -> a + b\n a, _ -> a\n end\n end\nend\n",
3647 "foo.ex",
3648 |metric| {
3649 assert_eq!(metric.nom.closures_sum(), 1);
3650 let s = &metric.nargs;
3651 assert_eq!(s.closure_args_sum(), 2);
3652 assert_eq!(s.closure_args_max(), 2);
3653 },
3654 );
3655 }
3656
3657 /// A guarded `fn` clause aliases its `left` to the same `when`
3658 /// `binary_operator` a guarded `def` head uses, so it needs the same
3659 /// unwrap. Without it the count is the guard expression's fixed three
3660 /// children — 3 for any arity, which is why the four-parameter form is
3661 /// the fixture here.
3662 #[test]
3663 fn elixir_guarded_closure_args() {
3664 check_metrics::<ElixirParser>(
3665 "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",
3666 "foo.ex",
3667 |metric| {
3668 assert_eq!(metric.nom.closures_sum(), 1);
3669 let s = &metric.nargs;
3670 assert_eq!(s.closure_args_sum(), 4);
3671 assert_eq!(s.closure_args_max(), 4);
3672 },
3673 );
3674 }
3675
3676 /// `def a + b` and `def -a` define the operator functions `+/2` and
3677 /// `-/1`. Their head is the operator node itself, with no `arguments`
3678 /// container to walk, so the arity comes from the operator's shape.
3679 #[test]
3680 fn elixir_operator_definition_args() {
3681 check_metrics::<ElixirParser>(
3682 "defmodule Foo do\n def a + b, do: {a, b}\n def -a, do: a\nend\n",
3683 "foo.ex",
3684 |metric| {
3685 assert_eq!(metric.nom.functions_sum(), 2);
3686 let s = &metric.nargs;
3687 assert_eq!(s.function_args_sum(), 3);
3688 assert_eq!(s.function_args_max(), 2);
3689 },
3690 );
3691 }
3692
3693 /// A `def` inside `quote do … end` is a code template, not a
3694 /// declaration, and must not contribute arguments (#310). The quoted
3695 /// head carries three parameters, so dropping the rule reads 3.
3696 #[test]
3697 fn elixir_quoted_def_contributes_no_args() {
3698 check_metrics::<ElixirParser>(
3699 "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",
3700 "foo.ex",
3701 |metric| {
3702 assert_eq!(metric.nargs.function_args_sum(), 0);
3703 // Anchor the zero: if Elixir def-recognition broke
3704 // entirely, `function_args_sum` would also read 0. The
3705 // enclosing `defmacro mac` still counting proves the
3706 // recognizer ran and the quote-block gate did the
3707 // excluding.
3708 assert_eq!(metric.nom.functions_sum(), 1);
3709 },
3710 );
3711 }
3712
3713 /// Only `def` / `defp` / `defmacro` / `defmacrop` declare a function.
3714 /// `defmodule` and `defdelegate` are ordinary `Call`s of the same
3715 /// shape — `defdelegate log(msg), to: Logger` has a head `Call` with
3716 /// one parameter, so a gate that matched any macro would read 1.
3717 #[test]
3718 fn elixir_non_method_macros_count_zero() {
3719 check_metrics::<ElixirParser>(
3720 "defmodule Foo do\n defdelegate log(msg), to: Logger\nend\n",
3721 "foo.ex",
3722 |metric| {
3723 assert_eq!(metric.nargs.function_args_sum(), 0);
3724 assert_eq!(metric.nargs.closure_args_sum(), 0);
3725 },
3726 );
3727 }
3728
3729 #[test]
3730 fn ruby_no_functions_and_closures() {
3731 check_metrics::<RubyParser>("a = 42\n", "foo.rb", |metric| {
3732 assert_eq!(metric.nargs.function_args_sum(), 0);
3733 assert_eq!(metric.nargs.closure_args_sum(), 0);
3734 });
3735 }
3736
3737 #[test]
3738 fn ruby_single_function() {
3739 // Single method with 3 parameters.
3740 check_metrics::<RubyParser>("def foo(a, b, c)\n a + b + c\nend\n", "foo.rb", |metric| {
3741 assert_eq!(metric.nargs.function_args_sum(), 3);
3742 assert_eq!(metric.nargs.closure_args_sum(), 0);
3743 });
3744 }
3745
3746 #[test]
3747 fn ruby_single_closure() {
3748 // A bare block `[1,2,3].each { |x| ... }` is the only closure
3749 // here; `each` is a method call so the method-args count is 0.
3750 check_metrics::<RubyParser>("[1, 2, 3].each { |x| puts x }\n", "foo.rb", |metric| {
3751 assert_eq!(metric.nargs.function_args_sum(), 0);
3752 assert_eq!(metric.nargs.closure_args_sum(), 1);
3753 });
3754 }
3755
3756 #[test]
3757 fn ruby_functions() {
3758 // Two methods, args=2 and args=1; one lambda with args=2.
3759 check_metrics::<RubyParser>(
3760 "def add(a, b)\n a + b\nend\n\ndef neg(x)\n -x\nend\n\nf = ->(a, b) { a * b }\n",
3761 "foo.rb",
3762 |metric| {
3763 assert_eq!(metric.nargs.function_args_sum(), 3);
3764 assert_eq!(metric.nargs.closure_args_sum(), 2);
3765 },
3766 );
3767 }
3768
3769 #[test]
3770 fn ruby_nested_functions() {
3771 // An outer method with 1 arg containing an inner method with 2.
3772 check_metrics::<RubyParser>(
3773 "def outer(a)\n def inner(b, c)\n b + c\n end\n inner(a, a)\nend\n",
3774 "foo.rb",
3775 |metric| {
3776 assert_eq!(metric.nargs.function_args_sum(), 3);
3777 assert_eq!(metric.nargs.closure_args_sum(), 0);
3778 },
3779 );
3780 }
3781
3782 /// PEP 570 positional-only `/` and PEP 3102 keyword-only `*` markers are
3783 /// punctuation, not parameters. The grammar emits them as
3784 /// `positional_separator` / `keyword_separator` siblings of the real
3785 /// parameter nodes; both must be excluded from nargs (issue #414).
3786 #[test]
3787 fn python_both_parameter_separators() {
3788 // 1 function, 3 real parameters: pos_only, normal, kw_only.
3789 check_metrics::<PythonParser>(
3790 "def f(pos_only, /, normal, *, kw_only): pass",
3791 "foo.py",
3792 |metric| {
3793 assert_eq!(metric.nargs.function_args_sum(), 3);
3794 assert_eq!(metric.nargs.closure_args_sum(), 0);
3795 },
3796 );
3797 }
3798
3799 /// Trailing positional-only `/` (no following parameter) is still excluded.
3800 #[test]
3801 fn python_positional_separator_only() {
3802 // 1 function, 2 real parameters: a, b (`/` excluded).
3803 check_metrics::<PythonParser>("def f(a, b, /): pass", "foo.py", |metric| {
3804 assert_eq!(metric.nargs.function_args_sum(), 2);
3805 assert_eq!(metric.nargs.closure_args_sum(), 0);
3806 });
3807 }
3808
3809 /// Leading keyword-only `*` (forcing all following parameters to be
3810 /// keyword-only) is excluded.
3811 #[test]
3812 fn python_keyword_separator_only() {
3813 // 1 function, 2 real parameters: a, b (`*` excluded).
3814 check_metrics::<PythonParser>("def f(*, a, b): pass", "foo.py", |metric| {
3815 assert_eq!(metric.nargs.function_args_sum(), 2);
3816 assert_eq!(metric.nargs.closure_args_sum(), 0);
3817 });
3818 }
3819
3820 /// Lambdas accept the same keyword-only `*` separator; it is excluded
3821 /// from the closure arg count.
3822 #[test]
3823 fn python_lambda_keyword_separator() {
3824 // 1 lambda, 2 real parameters: a, b (`*` excluded).
3825 check_metrics::<PythonParser>("g = lambda a, *, b: a", "foo.py", |metric| {
3826 assert_eq!(metric.nargs.function_args_sum(), 0);
3827 assert_eq!(metric.nargs.closure_args_sum(), 2);
3828 });
3829 }
3830
3831 /// Regression guard: `*args` / `**kwargs` are real parameter nodes
3832 /// (`list_splat_pattern` / `dictionary_splat_pattern`), not separators,
3833 /// and must keep contributing to the count after the #414 fix.
3834 #[test]
3835 fn python_args_kwargs_still_counted() {
3836 // 1 function, 3 parameters: a, *args, **kwargs.
3837 check_metrics::<PythonParser>("def f(a, *args, **kwargs): pass", "foo.py", |metric| {
3838 assert_eq!(metric.nargs.function_args_sum(), 3);
3839 assert_eq!(metric.nargs.closure_args_sum(), 0);
3840 });
3841 }
3842
3843 /// A file of bare top-level commands has no function spaces, so the
3844 /// argument count is zero.
3845 #[test]
3846 fn irules_no_functions_and_closures() {
3847 check_metrics::<IrulesParser>("set x 1\nlog local0. $x\n", "foo.irule", |metric| {
3848 assert_eq!(metric.nargs.function_args_sum(), 0);
3849 assert_eq!(metric.nargs.closure_args_sum(), 0);
3850 });
3851 }
3852
3853 /// A `when` handler is a function space but has no formal parameters
3854 /// (the event context is implicit), so its argument count is zero —
3855 /// `when_event` carries no `arguments` field. Guards edge case #10.
3856 #[test]
3857 fn irules_handler_zero_args() {
3858 check_metrics::<IrulesParser>(
3859 "when HTTP_REQUEST { log local0. \"hit\" }\n",
3860 "foo.irule",
3861 |metric| {
3862 assert_eq!(metric.nargs.function_args_sum(), 0);
3863 assert_eq!(metric.nargs.closure_args_sum(), 0);
3864 // The handler is still counted as a function space.
3865 assert_eq!(metric.nom.functions_sum(), 1);
3866 },
3867 );
3868 }
3869
3870 /// A `proc` with two formal parameters contributes two arguments.
3871 #[test]
3872 fn irules_single_proc() {
3873 check_metrics::<IrulesParser>("proc f { a b } { return $a }\n", "foo.irule", |metric| {
3874 assert_eq!(metric.nargs.function_args_sum(), 2);
3875 assert_eq!(metric.nargs.closure_args_sum(), 0);
3876 });
3877 }
3878
3879 /// A `proc` with an empty argument list contributes zero arguments.
3880 #[test]
3881 fn irules_proc_no_args() {
3882 check_metrics::<IrulesParser>("proc f { } { return 1 }\n", "foo.irule", |metric| {
3883 assert_eq!(metric.nargs.function_args_sum(), 0);
3884 });
3885 }
3886
3887 /// A default-valued parameter (`{b 5}`) is a single `argument`, so each
3888 /// formal parameter counts once regardless of its default: `{a {b 5} c}`
3889 /// is three arguments.
3890 #[test]
3891 fn irules_proc_arg_defaults() {
3892 check_metrics::<IrulesParser>(
3893 "proc f { a {b 5} c } { return $a }\n",
3894 "foo.irule",
3895 |metric| {
3896 assert_eq!(metric.nargs.function_args_sum(), 3);
3897 },
3898 );
3899 }
3900
3901 /// A `proc` and a `when` handler in one file: only the proc's two
3902 /// parameters count; the handler contributes zero.
3903 #[test]
3904 fn irules_multiple_functions() {
3905 check_metrics::<IrulesParser>(
3906 "proc add { a b } { return [expr { $a + $b }] }
3907when HTTP_REQUEST { log local0. \"hit\" }
3908",
3909 "foo.irule",
3910 |metric| {
3911 assert_eq!(metric.nargs.function_args_sum(), 2);
3912 assert_eq!(metric.nom.functions_sum(), 2);
3913 },
3914 );
3915 }
3916
3917 /// Objective-C unary method `- (void)foo` declares zero arguments.
3918 #[test]
3919 fn objc_no_args() {
3920 check_metrics::<ObjcParser>(
3921 "@implementation Foo
3922- (void)foo {
3923 [self doWork];
3924}
3925@end
3926",
3927 "foo.m",
3928 |metric| {
3929 assert_eq!(metric.nargs.total(), 0);
3930 insta::assert_json_snapshot!(metric.nargs, @r#"
3931 {
3932 "function_args": 0,
3933 "closure_args": 0,
3934 "function_args_average": 0.0,
3935 "closure_args_average": 0.0,
3936 "total": 0,
3937 "average": 0.0,
3938 "function_args_min": 0,
3939 "function_args_max": 0,
3940 "closure_args_min": 0,
3941 "closure_args_max": 0
3942 }
3943 "#);
3944 },
3945 );
3946 }
3947
3948 /// Objective-C keyword method `- (void)foo:(int)a bar:(int)b` has two
3949 /// `method_parameter` children, so `function_args` is 2.
3950 #[test]
3951 fn objc_method_two_args() {
3952 check_metrics::<ObjcParser>(
3953 "@implementation Foo
3954- (void)foo:(int)a bar:(int)b {
3955 [self use:a];
3956}
3957@end
3958",
3959 "foo.m",
3960 |metric| {
3961 assert_eq!(metric.nargs.function_args_sum(), 2);
3962 insta::assert_json_snapshot!(metric.nargs, @r#"
3963 {
3964 "function_args": 2,
3965 "closure_args": 0,
3966 "function_args_average": 2.0,
3967 "closure_args_average": 0.0,
3968 "total": 2,
3969 "average": 2.0,
3970 "function_args_min": 0,
3971 "function_args_max": 2,
3972 "closure_args_min": 0,
3973 "closure_args_max": 0
3974 }
3975 "#);
3976 },
3977 );
3978 }
3979
3980 /// Free C `function_definition` inside an ObjC translation unit counts
3981 /// its declarator parameters: `void f(int a, int b, int c)` has 3.
3982 #[test]
3983 fn objc_function_args() {
3984 check_metrics::<ObjcParser>(
3985 "void f(int a, int b, int c) {
3986 return;
3987}
3988",
3989 "foo.m",
3990 |metric| {
3991 assert_eq!(metric.nargs.function_args_sum(), 3);
3992 insta::assert_json_snapshot!(metric.nargs, @r#"
3993 {
3994 "function_args": 3,
3995 "closure_args": 0,
3996 "function_args_average": 3.0,
3997 "closure_args_average": 0.0,
3998 "total": 3,
3999 "average": 3.0,
4000 "function_args_min": 0,
4001 "function_args_max": 3,
4002 "closure_args_min": 0,
4003 "closure_args_max": 0
4004 }
4005 "#);
4006 },
4007 );
4008 }
4009
4010 /// Objective-C block literal `^(int x, int y){ … }` is a closure
4011 /// whose `parameter_list` holds two `parameter_declaration`s, so
4012 /// `closure_args` is 2.
4013 #[test]
4014 fn objc_block_args() {
4015 check_metrics::<ObjcParser>(
4016 "@implementation Foo
4017- (void)bar {
4018 void (^blk)(int, int) = ^(int x, int y){
4019 [self use:x];
4020 };
4021 blk(1, 2);
4022}
4023@end
4024",
4025 "foo.m",
4026 |metric| {
4027 assert_eq!(metric.nargs.closure_args_sum(), 2);
4028 insta::assert_json_snapshot!(metric.nargs, @r#"
4029 {
4030 "function_args": 0,
4031 "closure_args": 2,
4032 "function_args_average": 0.0,
4033 "closure_args_average": 2.0,
4034 "total": 2,
4035 "average": 1.0,
4036 "function_args_min": 0,
4037 "function_args_max": 0,
4038 "closure_args_min": 0,
4039 "closure_args_max": 2
4040 }
4041 "#);
4042 },
4043 );
4044 }
4045
4046 /// A block's `(void)` marker declares nothing, so `^(void){ … }` is a
4047 /// closure of zero parameters (#1218).
4048 ///
4049 /// The objc grammar reuses C's `parameter_list` rule, so `^(void)`
4050 /// emits a real `parameter_declaration` for the `void` — the same
4051 /// shape `int f(void)` produces, and the reason
4052 /// `Checker::is_empty_param_marker` reads the source bytes rather
4053 /// than the tree. The block arm counted it until it began routing
4054 /// through `count_args`, while the function channel beside it was
4055 /// already correct: `host` below reports 0 either way, which is what
4056 /// makes this a test of the block channel specifically.
4057 #[test]
4058 fn objc_block_void_marker_is_not_a_parameter() {
4059 check_metrics::<ObjcParser>(
4060 "void host(void) {
4061 void (^empty)(void) = ^(void){ };
4062 empty();
4063}
4064",
4065 "foo.m",
4066 |metric| {
4067 assert_eq!(metric.nargs.closure_args_sum(), 0);
4068 assert_eq!(metric.nargs.function_args_sum(), 0);
4069 insta::assert_json_snapshot!(metric.nargs, @r#"
4070 {
4071 "function_args": 0,
4072 "closure_args": 0,
4073 "function_args_average": 0.0,
4074 "closure_args_average": 0.0,
4075 "total": 0,
4076 "average": 0.0,
4077 "function_args_min": 0,
4078 "function_args_max": 0,
4079 "closure_args_min": 0,
4080 "closure_args_max": 0
4081 }
4082 "#);
4083 },
4084 );
4085 }
4086
4087 /// A comment inside a block's parameter list is not a parameter, so
4088 /// `^(int a /* c */, int b){ … }` is 2 (#1201, #1218).
4089 ///
4090 /// **This fixture cannot fail by reverting the block arm.** The
4091 /// positive `matches!(ParameterDeclaration | VariadicParameter)` the
4092 /// arm used before #1218 already ignored a `comment` child, so the
4093 /// count was correct for the wrong reason — nothing asserted it, and
4094 /// the #1201 changelog claimed Objective-C blocks were swept when
4095 /// only the method fixture existed. It became load-bearing when the
4096 /// arm switched to `count_args`, whose *negative* filtering is what
4097 /// now makes `Checker::is_comment` live on this path. Perturb it by
4098 /// dropping `is_comment` from `count_args`, not by reverting the arm.
4099 #[test]
4100 fn objc_block_comment_is_not_a_parameter() {
4101 check_metrics::<ObjcParser>(
4102 "void host(void) {
4103 void (^two)(int, int) = ^(int a /* c */, int b){ };
4104 two(1, 2);
4105}
4106",
4107 "foo.m",
4108 |metric| {
4109 assert_eq!(metric.nargs.closure_args_sum(), 2);
4110 assert_eq!(metric.nargs.function_args_sum(), 0);
4111 },
4112 );
4113 }
4114
4115 /// A variadic block keeps its `...` counted: `^(int a, ...){ … }` is 2.
4116 ///
4117 /// The guard against #1218's fix, not against #1218. Swapping the
4118 /// arm's positive `matches!` for `count_args`' negative filters is
4119 /// what could silently drop `variadic_parameter` — it is named in the
4120 /// old match and in none of the new filters, so only a fixture says
4121 /// whether it survived. `ObjcCode::is_non_arg` covers the list's
4122 /// punctuation (`(`, `,`, `)`) and nothing else, so it does.
4123 #[test]
4124 fn objc_block_variadic_parameter_still_counts() {
4125 check_metrics::<ObjcParser>(
4126 "void host(void) {
4127 void (^var)(int, ...) = ^(int a, ...){ };
4128 var(1);
4129}
4130",
4131 "foo.m",
4132 |metric| {
4133 assert_eq!(metric.nargs.closure_args_sum(), 2);
4134 assert_eq!(metric.nargs.function_args_sum(), 0);
4135 },
4136 );
4137 }
4138
4139 /// A block written without a parameter list at all is 0.
4140 ///
4141 /// `^{ }` has no `parameter_list` child, so the arm's
4142 /// `first_child(ParameterList)` guard short-circuits before
4143 /// `count_args` is reached. Pinned beside the `^(void)` case because
4144 /// the two spellings mean the same thing and only one of them ever
4145 /// went through the counting path.
4146 #[test]
4147 fn objc_block_without_a_parameter_list_is_zero() {
4148 check_metrics::<ObjcParser>(
4149 "void host(void) {
4150 void (^none)(void) = ^{ };
4151 none();
4152}
4153",
4154 "foo.m",
4155 |metric| {
4156 assert_eq!(metric.nargs.closure_args_sum(), 0);
4157 assert_eq!(metric.nargs.function_args_sum(), 0);
4158 },
4159 );
4160 }
4161
4162 /// Regression for #782: the textual `Display` headline must report
4163 /// the cross-space *sum* (`function_args_sum`/`closure_args_sum`),
4164 /// matching the JSON/YAML/TOML/CBOR serializers, not the per-space
4165 /// direct accumulator. At a parent space that rolls up child
4166 /// function-spaces (the file/unit space of `python_nested_functions`)
4167 /// the accumulator under-counts: it reflects only the direct
4168 /// function `f` (2 args) and no merged closures (0), while the sum
4169 /// is 3 function args (f=2, foo=1) and 2 closure args. Before the
4170 /// fix Display printed `function_args: 2, closure_args: 0`.
4171 #[test]
4172 fn display_headline_matches_sum_for_nested_functions() {
4173 check_metrics::<PythonParser>(
4174 "def f(a, b):
4175 def foo(a):
4176 if a:
4177 return 1
4178 bar = lambda a: lambda b: b or True or True
4179 return bar(foo(a))(a)",
4180 "foo.py",
4181 |metric| {
4182 let stats = &metric.nargs;
4183 // The summed accessors are the cross-format source of truth.
4184 assert_eq!(stats.function_args_sum(), 3);
4185 assert_eq!(stats.closure_args_sum(), 2);
4186
4187 // The Display headline must echo those sums verbatim.
4188 let rendered = stats.to_string();
4189 assert!(
4190 rendered.starts_with(&format!(
4191 "function_args: {}, closure_args: {},",
4192 stats.function_args_sum(),
4193 stats.closure_args_sum()
4194 )),
4195 "Display headline diverged from the summed accessors: {rendered}"
4196 );
4197 },
4198 );
4199 }
4200}
4201
4202/// A lambda's parameter count must not depend on optional parentheses
4203/// (#1185).
4204///
4205/// `x -> x + 1` and `(x) -> x + 1` are the same lambda — the parens are
4206/// optional in the grammar and carry no meaning — so they must score
4207/// alike, the same "byte-equivalent constructs score identically"
4208/// contract the book states for cognitive.
4209///
4210/// The cause is shared: the `parameters` field holds a lone, childless
4211/// parameter node rather than a list, and `compute_args` walks the
4212/// field's children. The issue names Java; the sweep found **C#** has
4213/// the identical defect via `implicit_parameter`. Kotlin and Groovy
4214/// were checked and are correct — each overrides `compute` with its own
4215/// closure-parameter shape — and the JS family reaches the right answer
4216/// through the singular `parameter` field.
4217#[cfg(test)]
4218mod lambda_parenthesisation_parity {
4219 use crate::test_support::metrics_verbatim;
4220 use crate::{LANG, MetricsOptions};
4221
4222 /// `(closure_args, function_args)` — the split matters as much as
4223 /// the count: a lambda must stay in the closure channel.
4224 fn args(lang: LANG, source: &str) -> (u64, u64) {
4225 let m = metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default());
4226 (m.nargs.closure_args_sum(), m.nargs.function_args_sum())
4227 }
4228
4229 /// `(bare, parenthesised, two_params, zero_params)`.
4230 fn cases(lang: LANG) -> Option<[&'static str; 4]> {
4231 Some(match lang {
4232 LANG::Java => [
4233 "class K{ void f(){ Function<Integer,Integer> a = x -> x + 1; } }",
4234 "class K{ void f(){ Function<Integer,Integer> a = (x) -> x + 1; } }",
4235 "class K{ void f(){ BiFunction<Integer,Integer,Integer> c = (x, y) -> x + y; } }",
4236 "class K{ void f(){ Supplier<Integer> d = () -> 1; } }",
4237 ],
4238 LANG::Csharp => [
4239 "class K{ void f(){ Func<int,int> a = x => x + 1; } }",
4240 "class K{ void f(){ Func<int,int> a = (x) => x + 1; } }",
4241 "class K{ void f(){ Func<int,int,int> c = (x, y) => x + y; } }",
4242 "class K{ void f(){ Func<int> d = () => 1; } }",
4243 ],
4244 LANG::Javascript | LANG::Typescript | LANG::Tsx | LANG::Mozjs => [
4245 "function f(){ var a = x => x + 1; }",
4246 "function f(){ var a = (x) => x + 1; }",
4247 "function f(){ var c = (x, y) => x + y; }",
4248 "function f(){ var d = () => 1; }",
4249 ],
4250 _ => return None,
4251 })
4252 }
4253
4254 #[test]
4255 fn optional_parentheses_do_not_change_the_count() {
4256 let mut checked = 0;
4257 for lang in LANG::into_enum_iter() {
4258 if !lang.is_enabled() {
4259 continue;
4260 }
4261 let Some([bare, paren, two, zero]) = cases(lang) else {
4262 continue;
4263 };
4264 checked += 1;
4265
4266 let (bare_args, paren_args) = (args(lang, bare), args(lang, paren));
4267 assert_eq!(
4268 bare_args, paren_args,
4269 "{lang:?}: the parentheses changed the argument count\n bare: {bare}\n paren: {paren}"
4270 );
4271 // The absolute value, so a regression that zeroed *both*
4272 // spellings would still fail.
4273 assert_eq!(
4274 bare_args.0 + bare_args.1,
4275 1,
4276 "{lang:?}: a one-parameter lambda must report one argument"
4277 );
4278 // A zero-parameter lambda must stay 0: the bare-parameter
4279 // branch must not mistake an empty list for a parameter.
4280 assert_eq!(
4281 args(lang, zero),
4282 (0, 0),
4283 "{lang:?}: `() -> …` has no arguments"
4284 );
4285 // And the plural path must be undisturbed.
4286 let two_args = args(lang, two);
4287 assert_eq!(
4288 two_args.0 + two_args.1,
4289 2,
4290 "{lang:?}: a two-parameter lambda must report two arguments"
4291 );
4292 }
4293 assert!(
4294 checked > 0,
4295 "no lambda language enabled; this test asserted nothing"
4296 );
4297 }
4298
4299 /// The lambda stays in the *closure* channel, not the function one.
4300 ///
4301 /// Java and C# route it through `is_closure`; the JS family's arrow
4302 /// is classified by `check_if_arrow_func!` and lands in `fn_args`
4303 /// when bound to a variable, which is a separate question (#1188).
4304 /// Asserting the channel per language rather than globally keeps
4305 /// this test from encoding that as a bug.
4306 #[test]
4307 fn a_bare_lambda_stays_in_the_closure_channel() {
4308 for lang in [LANG::Java, LANG::Csharp] {
4309 if !lang.is_enabled() {
4310 continue;
4311 }
4312 let [bare, ..] = cases(lang).expect("both languages have cases");
4313 assert_eq!(
4314 args(lang, bare),
4315 (1, 0),
4316 "{lang:?}: the lambda's argument must be billed to closure_args"
4317 );
4318 }
4319 }
4320}
4321
4322/// #1201 — a comment inside a parameter list counted as a parameter.
4323///
4324/// tree-sitter attaches a comment written between two parameters as a
4325/// direct child of the parameter-*list* node, not inside the parameter
4326/// it documents. Every negative filter in this module lists punctuation
4327/// only, so each comment scored one: `int h(int a /* one */, int b)`
4328/// reported 3, and the C++ idiom for a deliberately unused parameter,
4329/// `void f(int /*unused*/)`, reported 2.
4330///
4331/// Four independent loops could carry the defect and each has its own
4332/// row below, so reverting any single one of them fails on its own:
4333/// `compute_args` (the C family through Groovy), `elixir_declared_args`,
4334/// `compute_kotlin_lambda_args`, and `compute_perl_args` — the last of
4335/// which already excluded comments and is here as a no-change guard on
4336/// its collapse onto the shared `count_args`.
4337///
4338/// Go, Lua, Objective-C *methods*, Kotlin *functions* and Groovy
4339/// *closures* were already correct — each filters positively for its
4340/// parameter kind — and are swept anyway, because "this one is a
4341/// positive filter" is the reasoning that has to hold for a grammar
4342/// bump, not just for today.
4343///
4344/// Objective-C *blocks* were on that list until #1218 and are not any
4345/// more: their arm now routes through the shared `count_args`, so what
4346/// keeps a comment out of a block's count is the same negative filter
4347/// the repaired languages rely on, not a positive parameter-kind match.
4348/// Their fixture is `objc_block_comment_is_not_a_parameter`, which sits
4349/// in the module above beside the `^(void)` case that motivated the
4350/// move rather than in the table below.
4351#[cfg(test)]
4352mod comments_in_parameter_lists {
4353 use crate::test_support::metrics_verbatim;
4354 use crate::{LANG, MetricsOptions};
4355
4356 /// `(closure_args, function_args)`. Asserting the pair rather than
4357 /// the sum keeps a fix that merely moved a count between channels
4358 /// from reading as a pass.
4359 fn args(lang: LANG, source: &str) -> (u64, u64) {
4360 let m = metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default());
4361 (m.nargs.closure_args_sum(), m.nargs.function_args_sum())
4362 }
4363
4364 /// Fixtures for the languages #1201 repaired. Every one of these
4365 /// reported an inflated count before the fix.
4366 ///
4367 /// Each fixture declares exactly two parameters — bar the C-family
4368 /// unnamed-parameter shape, which declares one — so the expected
4369 /// value is a 2 in one channel or the other. Where a language spells
4370 /// both a block and a line comment, both appear: they are separate
4371 /// `is_comment` arms in Rust, Java, C#, Kotlin, Groovy, PHP and the
4372 /// JS family, and a block-only sweep leaves the other arm untested.
4373 fn repaired_cases(lang: LANG) -> Option<&'static [(&'static str, (u64, u64))]> {
4374 Some(match lang {
4375 LANG::C => &[
4376 (
4377 "int h(int a /* one */, int b /* two */) { return a; }",
4378 (0, 2),
4379 ),
4380 ("int h(int a, // one\n int b) { return a; }", (0, 2)),
4381 // The unnamed-parameter idiom: the comment is the only
4382 // thing standing where the name would be, so counting it
4383 // doubled the arity rather than adding to it.
4384 ("void f(int /*unused*/) { }", (0, 1)),
4385 ],
4386 // Split from `C` only for the lambda, which C has no form of.
4387 LANG::Cpp | LANG::Mozcpp => &[
4388 (
4389 "int h(int a /* one */, int b /* two */) { return a; }",
4390 (0, 2),
4391 ),
4392 ("int h(int a, // one\n int b) { return a; }", (0, 2)),
4393 ("void f(int /*unused*/) { }", (0, 1)),
4394 // The closure channel, which reaches `compute_args`
4395 // through the same `declarator` field as the function
4396 // one but bills a different counter.
4397 (
4398 "int g() { auto f = [](int a, /* one */ int b){ return a + b; }; return f(1,2); }",
4399 (2, 0),
4400 ),
4401 ],
4402 LANG::Objc => &[("int h(int a /* one */, int b) { return a; }", (0, 2))],
4403 LANG::Javascript | LANG::Mozjs => &[
4404 ("function h(a, /* one */ b) { return a; }", (0, 2)),
4405 ("function h(a, // one\n b) { return a; }", (0, 2)),
4406 ],
4407 LANG::Typescript | LANG::Tsx => &[
4408 (
4409 "function h(a: number, /* one */ b: number) { return a; }",
4410 (0, 2),
4411 ),
4412 (
4413 "function h(a: number, // one\n b: number) { return a; }",
4414 (0, 2),
4415 ),
4416 ],
4417 // Python has no block comment, so the `#` form is the whole
4418 // of its exposure.
4419 LANG::Python => &[("def h(a, # one\n b):\n return a\n", (0, 2))],
4420 LANG::Rust => &[
4421 ("fn h(a: i32, /* one */ b: i32) -> i32 { a }", (0, 2)),
4422 ("fn h(a: i32, // one\n b: i32) -> i32 { a }", (0, 2)),
4423 // `compute_args` is reached a second time for closures,
4424 // with `closure_nargs` as the target. Same walk, but a
4425 // fixture that only ever asserts the function channel
4426 // cannot tell a regression at that call site apart from
4427 // a pass.
4428 (
4429 "fn g() { let f = |a: i32, /* one */ b: i32| a + b; }",
4430 (2, 0),
4431 ),
4432 ],
4433 // One source parses as both, so they share an arm rather
4434 // than tripping `clippy::match_same_arms` on two copies.
4435 LANG::Java | LANG::Csharp => &[
4436 (
4437 "class K { int h(int a, /* one */ int b) { return a; } }",
4438 (0, 2),
4439 ),
4440 (
4441 "class K { int h(int a, // one\n int b) { return a; } }",
4442 (0, 2),
4443 ),
4444 ],
4445 LANG::Php => &[
4446 ("<?php function h($a, /* one */ $b) { return $a; }", (0, 2)),
4447 (
4448 "<?php function h($a, // one\n $b) { return $a; }",
4449 (0, 2),
4450 ),
4451 ],
4452 LANG::Ruby => &[("def h(a, # one\n b)\n a\nend", (0, 2))],
4453 LANG::Groovy => &[
4454 ("def h(a, /* one */ b) { return a }", (0, 2)),
4455 ("def h(a, // one\n b) { return a }", (0, 2)),
4456 ],
4457 // Elixir's parameter list is a second `Call`'s `arguments`,
4458 // reached without ever entering `compute_args`, so nothing
4459 // above proves anything about it.
4460 LANG::Elixir => &[(
4461 "defmodule M do\n def h(a, # one\n b) do\n a\n end\nend",
4462 (0, 2),
4463 )],
4464 // `compute_kotlin_lambda_args` is a separate loop with its own
4465 // negative filter, which the Kotlin *function* guard below
4466 // never reaches.
4467 LANG::Kotlin => &[
4468 (
4469 "fun g() {\n val f = { a: Int, /* one */ b: Int -> a }\n println(f)\n}",
4470 (2, 0),
4471 ),
4472 (
4473 "fun g() {\n val f = { a: Int, // one\n b: Int -> a }\n println(f)\n}",
4474 (2, 0),
4475 ),
4476 ],
4477 // Bash functions take no formal parameters, and Tcl/iRules
4478 // have no comment in this position at all — see
4479 // `tcl_has_no_comment_inside_a_parameter_list`.
4480 _ => return None,
4481 })
4482 }
4483
4484 /// Fixtures for the counting loops that were already correct before
4485 /// #1201, each because it filters *positively* for its parameter
4486 /// kind and so never saw a comment to miscount.
4487 ///
4488 /// They are swept anyway: "this one is a positive filter" is a claim
4489 /// that has to keep holding across a grammar bump, not just today,
4490 /// and Perl's is the guard on collapsing its private comment
4491 /// exclusion onto the shared `count_args`.
4492 fn already_correct_cases(lang: LANG) -> Option<&'static [(&'static str, (u64, u64))]> {
4493 Some(match lang {
4494 // One `method_parameter` per labelled argument.
4495 LANG::Objc => &[(
4496 "@implementation K\n- (void)foo:(int)a /* one */ bar:(int)b { }\n@end",
4497 (0, 2),
4498 )],
4499 // A positive `ClosureParameter` filter.
4500 LANG::Groovy => &[("def c = { x, /* one */ y -> x }", (2, 0))],
4501 // A positive `Parameter` filter.
4502 LANG::Kotlin => &[("fun h(a: Int, /* one */ b: Int): Int { return a }", (0, 2))],
4503 // Names are counted inside each `parameter_declaration`, so a
4504 // sibling comment is invisible.
4505 LANG::Go => &[
4506 (
4507 "package m\nfunc h(a int, /* one */ b int) int { return a }",
4508 (0, 2),
4509 ),
4510 (
4511 "package m\nvar f = func(x int, /* one */ y int) int { return x }",
4512 (2, 0),
4513 ),
4514 ],
4515 // A positive `Identifier | VarargExpression` filter.
4516 LANG::Lua => &[("local f = function(a, --[[one]] b) return a end", (2, 0))],
4517 // Perl carried the comment exclusion inline before #1201
4518 // moved it into `count_args`; this pins that the move changed
4519 // nothing.
4520 LANG::Perl => &[(
4521 "use feature 'signatures';\nsub h(\n $a, # one\n $b\n) { return $a; }",
4522 (0, 2),
4523 )],
4524 _ => return None,
4525 })
4526 }
4527
4528 #[test]
4529 fn a_comment_in_a_parameter_list_is_not_a_parameter() {
4530 let (mut repaired, mut guards) = (0, 0);
4531 let mut failures = Vec::new();
4532 for lang in LANG::into_enum_iter().filter(LANG::is_enabled) {
4533 for (table, counter) in [
4534 (repaired_cases(lang), &mut repaired),
4535 (already_correct_cases(lang), &mut guards),
4536 ] {
4537 for (source, expected) in table.unwrap_or_default() {
4538 *counter += 1;
4539 let got = args(lang, source);
4540 // Collected rather than asserted inline so a revert of
4541 // any one of the four loops shows every language it
4542 // broke, not just the alphabetically first. The branch
4543 // carries no formatting — a line that runs only on
4544 // failure can never be covered, so the report is built
4545 // once, below, from the raw tuples.
4546 if got != *expected {
4547 failures.push((lang, source, *expected, got));
4548 }
4549 }
4550 }
4551 }
4552 // Bound eagerly and interpolated by name: an `assert!` argument is
4553 // evaluated only when the assertion fires, so spelling these out
4554 // as arguments would leave two more never-executed lines behind.
4555 let (failed, total) = (failures.len(), repaired + guards);
4556 assert!(
4557 failures.is_empty(),
4558 "{failed}/{total} fixtures counted a comment as a parameter: {failures:#?}"
4559 );
4560 // Both tallies, so a table that stopped being reached — a renamed
4561 // `LANG` variant, a feature that stopped being enabled — fails
4562 // here rather than passing vacuously.
4563 assert!(
4564 repaired > 0 && guards > 0,
4565 "no fixture ran (repaired={repaired}, guards={guards}); this test asserted nothing"
4566 );
4567 }
4568
4569 /// Tcl — and iRules, which shares the shape — reports **4** for a
4570 /// `#` line inside a `proc` argument list, and that is correct.
4571 ///
4572 /// Tcl recognises a comment only where a command is expected, so
4573 /// `proc h {a\n# c\n b}` really does declare four arguments named
4574 /// `a`, `#`, `c` and `b`. The grammar agrees: it emits four
4575 /// `argument` nodes and no comment node, which is why
4576 /// `compute_tcl_args` never needed the exclusion the other
4577 /// languages did. Issue #1201 cited Tcl as the language that had
4578 /// already solved this; it had not — it has no problem to solve.
4579 ///
4580 /// **Both rows assert parity over a non-problem, not a defence.**
4581 /// Neither language has an exclusion here that a regression could
4582 /// remove; what these pin is that the *shape* stays the one described
4583 /// above, so a grammar bump that started emitting a comment node
4584 /// would surface as a count change rather than silently. The iRules
4585 /// row is the second half of a claim this doc and the #1201 changelog
4586 /// entry both made while only Tcl was exercised (#1218). It is a
4587 /// separate dialect grammar, and dialect grammars do diverge on leaf
4588 /// naming — its `argument` is kind 137 against Tcl's 93 — so it was
4589 /// dumped rather than assumed: `proc h {a\n# c\n b}` yields four
4590 /// `argument` nodes and no comment node under both.
4591 // Gated on the fixtures' own features for the reason #1220 names: the
4592 // case list is two languages wide and the loop below asserts it ran, so
4593 // a feature set enabling neither — `--no-default-features --features
4594 // rust` — would fail here and read as a defect in whatever was being
4595 // changed. The gate makes the test absent rather than vacuous; the
4596 // `ran > 0` assertion then covers the narrower case where `is_enabled`
4597 // stops agreeing with the feature it is compiled under.
4598 #[test]
4599 #[cfg(any(feature = "tcl", feature = "irules"))]
4600 fn tcl_has_no_comment_inside_a_parameter_list() {
4601 // Guarded per language rather than once: the two features are
4602 // independent, so a build with only one enabled must still run
4603 // that one's row.
4604 let mut ran = 0;
4605 for lang in [LANG::Tcl, LANG::Irules]
4606 .into_iter()
4607 .filter(LANG::is_enabled)
4608 {
4609 ran += 1;
4610 assert_eq!(
4611 args(lang, "proc h {a\n # c\n b} { return $a }"),
4612 (0, 4),
4613 "{lang:?}: a `#` in an argument list is an argument named `#`, not a comment"
4614 );
4615 // The uncommented control, so a regression that zeroed the
4616 // whole count would not read as this rule holding.
4617 assert_eq!(args(lang, "proc h {a b} { return $a }"), (0, 2), "{lang:?}");
4618 }
4619 // A feature set enabling neither leaves a loop of zero iterations
4620 // and a test that passes having asserted nothing — the shape
4621 // `assert_fixtures_present` exists to make loud (#1220).
4622 assert!(
4623 ran > 0,
4624 "neither tcl nor irules is enabled; this test asserted nothing"
4625 );
4626 }
4627
4628 /// The one path `count_args` never runs on: `Checker::is_bare_param`
4629 /// short-circuits before the child walk, so a comment on an
4630 /// un-parenthesised lambda parameter is safe only because of where
4631 /// the *grammar* puts it. Confirmed rather than reasoned, per
4632 /// `.claude/rules/grammar-dispatch.md`: dumping
4633 /// `x /* c */ -> x` shows `block_comment` as a **sibling** of the
4634 /// bare `identifier`, and the `parameters` field points at that
4635 /// childless identifier.
4636 ///
4637 /// So the comment is not a discriminating input here, and this test
4638 /// is deliberately written as a *parity* assertion rather than a
4639 /// count one. No perturbation distinguishes the commented spelling
4640 /// from the bare one — both fail together under every perturbation of
4641 /// the bare-parameter branch, which
4642 /// `lambda_parenthesisation_parity` already covers. What this adds
4643 /// is the guarantee that the two spellings cannot diverge, which is
4644 /// what would break if a grammar bump moved the comment inside the
4645 /// field.
4646 #[test]
4647 fn a_comment_on_a_bare_lambda_parameter_changes_nothing() {
4648 for (lang, commented, bare) in [
4649 (
4650 LANG::Java,
4651 "class K { java.util.function.Function<Integer,Integer> f = x /* c */ -> x; }",
4652 "class K { java.util.function.Function<Integer,Integer> f = x -> x; }",
4653 ),
4654 (
4655 LANG::Csharp,
4656 "class K { System.Func<int,int> f = x /* c */ => x + 1; }",
4657 "class K { System.Func<int,int> f = x => x + 1; }",
4658 ),
4659 ]
4660 .into_iter()
4661 .filter(|(lang, ..)| lang.is_enabled())
4662 {
4663 let got = args(lang, commented);
4664 assert_eq!(
4665 got,
4666 args(lang, bare),
4667 "{lang:?}: the comment moved the count off the bare spelling's answer"
4668 );
4669 // The absolute value too, so a regression that zeroed both
4670 // spellings would not read as parity holding.
4671 assert_eq!(
4672 got,
4673 (1, 0),
4674 "{lang:?}: a bare lambda parameter is one closure argument"
4675 );
4676 }
4677 }
4678}
4679
4680#[cfg(test)]
4681mod c_family_return_type_declarators {
4682 use crate::test_support::space_verbatim;
4683 use crate::{LANG, MetricsOptions};
4684
4685 /// `(closure_args, function_args)` read from the fixture's sole
4686 /// nested space.
4687 ///
4688 /// The space rather than the file roll-up, because since #1196 the
4689 /// `nargs` gate reads a callable's *own* parameter count: a fix that
4690 /// repaired only the roll-up would leave the gate exactly as blind
4691 /// as #1200 found it. The pair rather than the total, so a
4692 /// regression that merely moved a count between the function and
4693 /// closure channels cannot read as a pass.
4694 #[track_caller]
4695 fn sole_space_args(lang: LANG, source: &str) -> (u64, u64) {
4696 let root = space_verbatim(lang, source.as_bytes(), MetricsOptions::default());
4697 // Descend to the *innermost* sole space, not the first one.
4698 // A free function is one level down, but a conversion operator
4699 // is two — its `struct` opens a container space in between,
4700 // whose own counters stay at zero however badly the operator is
4701 // counted. Stopping at the first level made both `operator_cast`
4702 // fixtures assert about the struct and pass with the defect
4703 // reinstated.
4704 let mut space = &root;
4705 let mut depth = 0;
4706 while let [only] = space.spaces.as_slice() {
4707 space = only;
4708 depth += 1;
4709 }
4710 assert!(depth > 0, "{lang:?}: fixture opened no space at all");
4711 (
4712 space.metrics.nargs.closure_args(),
4713 space.metrics.nargs.function_args(),
4714 )
4715 }
4716
4717 /// The return shapes every C-derived grammar here shares, plus the
4718 /// unwrapped control that keeps the fix honest.
4719 ///
4720 /// Every pointer row reported **0** before #1200 and the nested rows
4721 /// reported the *return type's* arity; the plain row already passed
4722 /// and is here so a helper that returned nothing at all could not
4723 /// look like a fix.
4724 ///
4725 /// Each nested row deliberately gives the inner and outer parameter
4726 /// lists **different** lengths. An earlier draft of the `__cdecl`
4727 /// row spelled both as one argument, which made it agree with the
4728 /// answer it was written to reject.
4729 fn c_declarator_shapes(lang: LANG) -> Option<&'static [(&'static str, (u64, u64))]> {
4730 if !matches!(lang, LANG::C | LANG::Cpp | LANG::Mozcpp | LANG::Objc) {
4731 return None;
4732 }
4733 // C declarator syntax, shared by all four grammars. `Foo` is
4734 // deliberately an undeclared type: tree-sitter resolves the
4735 // shape syntactically, and a fixture that leaned on a typedef
4736 // would be testing the fixture.
4737 Some(&[
4738 // A pointer return: the reported symptom in #1200.
4739 ("FILE *f(int a, int b, int c) { return 0; }", (0, 3)),
4740 // Two levels of `pointer_declarator`, so a walk that steps
4741 // exactly once still fails.
4742 ("int **g(int a, int b) { return 0; }", (0, 2)),
4743 // A storage-class specifier ahead of the pointer, which
4744 // sits outside the declarator entirely.
4745 ("static int *h(int a) { return 0; }", (0, 1)),
4746 // A function returning a pointer to a one-argument
4747 // function. The *outer* `function_declarator` owns
4748 // `(int c)` — the return type's list — so taking the first
4749 // `parameters` found reports 1. `fp` takes two.
4750 ("int (*fp(int a, int b))(int c) { return 0; }", (0, 2)),
4751 // The same shape with an MSVC calling convention, which
4752 // parses as a real `ms_call_modifier` node *preceding* the
4753 // declarator inside the `parenthesized_declarator`. This is
4754 // what makes the fallback take the last named child rather
4755 // than the first.
4756 (
4757 "int (__cdecl *w(int a, int b))(int c) { return 0; }",
4758 (0, 2),
4759 ),
4760 // The GNU attribute spelling, which all four grammars
4761 // absorb *into* the `function_declarator` rather than
4762 // wrapping it — so it never builds an
4763 // `attributed_declarator` and was never miscounted. It is
4764 // the control for the C++11 spelling below, a different
4765 // tree for the same source-level idea.
4766 (
4767 "int gdef(int a, int b) __attribute__((deprecated)) { return a; }",
4768 (0, 2),
4769 ),
4770 // C's `(void)` marker declares *no* parameters, but the
4771 // grammar emits a real `parameter_declaration` for it, so
4772 // every negative filter counted it as one.
4773 ("int none(void) { return 0; }", (0, 0)),
4774 // The two shapes `(void)` must not be confused with. An
4775 // unnamed parameter is structurally identical — a bare type
4776 // with no declarator — and really is one argument, so only
4777 // the bytes separate them. `void *` carries a declarator
4778 // and is likewise a real parameter.
4779 ("int unnamed(int) { return 0; }", (0, 1)),
4780 ("int ptr(void *p, int a) { return 0; }", (0, 2)),
4781 // The unwrapped control: its `declarator` field already is
4782 // the `function_declarator`, so it passed before the fix
4783 // and must keep passing after it.
4784 ("int plain(int a, int b) { return a; }", (0, 2)),
4785 // An unexpanded function-like macro in declarator position,
4786 // the shape every JNI shim takes (#1213). The macro's
4787 // `(name)` is the innermost list, so #1200's walk read 1
4788 // where the function declares 2.
4789 ("void MACRO(name)(int a, int b) { }", (0, 2)),
4790 // The multi-argument spelling. A gate keyed on the inner
4791 // list holding a single bare `type_identifier` — the
4792 // heuristic form the report proposed — passes the row above
4793 // and misses this one, which read the macro's 2 rather than
4794 // the function's 1. The two lists are deliberately
4795 // different lengths and in the opposite direction to the
4796 // row above, so neither row can agree with the other's
4797 // wrong answer.
4798 ("void MACRO(a, b)(int x) { }", (0, 1)),
4799 // The two mechanisms composed: a return type to step
4800 // through *and* a macro to stop at, so this is the only row
4801 // where the gate fires at a link the chain reached rather
4802 // than at the one it started from. It is also why the gate
4803 // tests `current`'s kind and not just the inner link's — a
4804 // `pointer_declarator`'s `declarator` field is a
4805 // `function_declarator` too, and stopping there lands on a
4806 // node with no `parameters`. That half is not exclusively
4807 // this row's to guard, though: dropping it also regresses
4808 // #1200's own `FILE *f(…)` and `int **g(…)` rows to 0.
4809 ("char *MACRO(n)(int a, int b) { return 0; }", (0, 2)),
4810 // Two nested invocations, so a gate that stopped one link
4811 // in still reads a macro's list. Three distinct lengths
4812 // because the single-nesting spelling `A(b)(c)(int x)`
4813 // reads 1 both before the fix and after it, and would
4814 // prove nothing.
4815 ("void A(b, c)(d)(int x, int y, int z) { }", (0, 3)),
4816 // A return type that nests without being a
4817 // `function_declarator` at all: the outer link is an
4818 // `array_declarator`, so the macro gate has nothing to fire
4819 // on and the chain must still reach `arr`'s own list. Two
4820 // arguments rather than the `(void)` this row first
4821 // carried — `(0, 0)` is what a walk returning `None` for
4822 // *everything* reports, so the row passed with the whole
4823 // chain dead while 28 others failed
4824 // (`.claude/rules/testing.md`, "Seed the state you claim to
4825 // assert on").
4826 ("int (*arr(int a, int b))[4] { return 0; }", (0, 2)),
4827 ])
4828 }
4829
4830 /// The C++11 `[[…]]` attribute, which is the only spelling that
4831 /// builds an `attributed_declarator` — the one fieldless rule
4832 /// putting its declarator *first*, so the last-named-child fallback
4833 /// lands on the attribute unless `attribute_declaration` is
4834 /// excluded. Reported 0 both before #1200 and after its first cut.
4835 ///
4836 /// Objective-C is absent because its grammar parses `[[…]]` on a
4837 /// *definition* as a `declaration` — no `function_definition`, so no
4838 /// space and nothing to count. That is upstream, not a miscount of
4839 /// ours; the GNU spelling above covers Objective-C's attribute path.
4840 fn cpp11_attribute_shapes(lang: LANG) -> Option<&'static [(&'static str, (u64, u64))]> {
4841 matches!(lang, LANG::C | LANG::Cpp | LANG::Mozcpp).then_some(&[(
4842 "int attr(int a, int b) [[deprecated]] { return a; }",
4843 (0, 2),
4844 )])
4845 }
4846
4847 /// The shapes only C++ has: `reference_declarator`, which — unlike
4848 /// `pointer_declarator` — exposes no `declarator` field at all, and
4849 /// the lambda, which reaches `params_owner` through the closure
4850 /// channel rather than the function one.
4851 fn cpp_only_shapes(lang: LANG) -> Option<&'static [(&'static str, (u64, u64))]> {
4852 Some(match lang {
4853 LANG::Cpp | LANG::Mozcpp => &[
4854 ("int &r(int a, int b) { static int x; return x; }", (0, 2)),
4855 // The rule's other spelling: `&&` is a distinct token
4856 // in the same `reference_declarator`, so a fix keyed on
4857 // the `&` token alone would pass the row above.
4858 (
4859 "Foo &&m(int a) { static Foo f; return static_cast<Foo &&>(f); }",
4860 (0, 1),
4861 ),
4862 // A member function returning a reference: the chain
4863 // ends at a `qualified_identifier` rather than a bare
4864 // one, which has named children of its own.
4865 ("Foo &Bar::get(int a) { static Foo f; return f; }", (0, 1)),
4866 // `operator()` is the one construct whose *source text*
4867 // looks like the macro nesting #1213 gates on, and it is
4868 // not rare: 1,546 function spaces across `DeepSpeech`,
4869 // against 46 direct nestings not one of which is an
4870 // operator. The grammar emits a single `operator_name`
4871 // with the parameter list as its sibling, so the gate is
4872 // unreachable from here rather than merely inactive.
4873 //
4874 // Which is what this row guards, and it is worth being
4875 // precise: no widening of the gate can fail it, because
4876 // the chain already stops at this declarator. It fails
4877 // if a future `tree-sitter-cpp` starts spelling
4878 // `operator()` as a nested `function_declarator` — at
4879 // which point the gate would silently halve the reported
4880 // arity of that whole population.
4881 (
4882 "struct S { int operator()(int a, int b) const { return a; } };",
4883 (0, 2),
4884 ),
4885 // An explicit template argument spelling a function
4886 // type. `template_function` is another name form with
4887 // named children, and its last one is the argument
4888 // list — so the last-named-child fallback walks off the
4889 // name side into `int (*)(int x, int y)` and bills that
4890 // type's two parameters to a one-argument function
4891 // unless `template_argument_list` is excluded. The two
4892 // lists are deliberately different lengths, so the row
4893 // cannot agree with the answer it rejects.
4894 (
4895 "template <> void tspec<int (*)(int x, int y)>(int a) { }",
4896 (0, 1),
4897 ),
4898 // A conversion operator takes no arguments, however
4899 // many its target *type* has. `operator_cast` is the
4900 // one link whose `declarator` field leaves the name
4901 // side, and following it billed the converted-to
4902 // function-pointer type's `(int x)` to the operator —
4903 // a regression the first cut of #1200 introduced and
4904 // no fixture then covered.
4905 (
4906 "struct S { operator int (*)(int x) { return nullptr; } };",
4907 (0, 0),
4908 ),
4909 // The same shape through a reference, so the fix cannot
4910 // be keyed on the pointer spelling alone.
4911 (
4912 "struct S { operator int (&)(int x, int y) { static int *p; return *reinterpret_cast<int (*)(int, int)>(p); } };",
4913 (0, 0),
4914 ),
4915 (
4916 "int g() { auto f = [](int a, int b){ return a + b; }; return f(1, 2); }",
4917 (2, 0),
4918 ),
4919 // The one shape with no declarator to walk: a
4920 // parameterless lambda has no
4921 // `abstract_function_declarator` at all, so the walk
4922 // returns `None` on its first step and `params_owner`
4923 // falls back to the node. `g` carries parameters of its
4924 // own so the expected pair is not all-zero — an
4925 // all-default expectation would hold however badly the
4926 // fallback behaved.
4927 (
4928 "int g(int a, int b) { auto f = []{ return 1; }; return f(); }",
4929 (0, 2),
4930 ),
4931 // …and the shape that makes that first step *matter*.
4932 // The row above executes the early return but cannot
4933 // discriminate it — perturbing the walk to begin at the
4934 // last named child instead of the `declarator` field
4935 // fails none of the suite, because a parameterless
4936 // lambda's body has nothing carrying `parameters` down
4937 // its last-child spine.
4938 //
4939 // A local *function declaration* does. `declaration`
4940 // has a `declarator` field of its own, so a walk that
4941 // starts outside the declarator chain lands on `q`'s
4942 // `function_declarator` and bills its two parameters to
4943 // the enclosing lambda, which declares none.
4944 (
4945 "int g(int a, int b) { auto f = []{ int q(int x, int y); }; f(); return a + b; }",
4946 (0, 2),
4947 ),
4948 // The guard for the walk's stop condition. A lambda's
4949 // `abstract_function_declarator` carries `parameters`
4950 // but its `declarator` field is *optional* and absent
4951 // here, so a walk that falls through to the last named
4952 // child descends into the parameter list and reports
4953 // `cb`'s own `(int x)` — one argument instead of two.
4954 (
4955 "int g() { auto f = [](int a, int (*cb)(int x)){ return cb(a); }; return 0; }",
4956 (2, 0),
4957 ),
4958 ],
4959 _ => return None,
4960 })
4961 }
4962
4963 #[test]
4964 fn a_wrapped_return_type_does_not_hide_the_parameter_list() {
4965 let (mut shared, mut cpp_only, mut attributed) = (0, 0, 0);
4966 let mut failures = Vec::new();
4967 for lang in LANG::into_enum_iter().filter(LANG::is_enabled) {
4968 for (table, counter) in [
4969 (c_declarator_shapes(lang), &mut shared),
4970 (cpp_only_shapes(lang), &mut cpp_only),
4971 (cpp11_attribute_shapes(lang), &mut attributed),
4972 ] {
4973 for (source, expected) in table.unwrap_or_default() {
4974 *counter += 1;
4975 let got = sole_space_args(lang, source);
4976 // Collected rather than asserted inline so a
4977 // regression shows every language and shape it
4978 // broke, not just the first. The branch carries no
4979 // formatting — a line that runs only on failure can
4980 // never be covered.
4981 if got != *expected {
4982 failures.push((lang, source, *expected, got));
4983 }
4984 }
4985 }
4986 }
4987 let (failed, checked) = (failures.len(), shared + cpp_only + attributed);
4988 assert!(
4989 failures.is_empty(),
4990 "{failed}/{checked} return-type shapes lost their parameter list: {failures:#?}"
4991 );
4992 // Every tally, so a renamed `LANG` variant or a feature that
4993 // stopped being enabled fails here rather than passing
4994 // vacuously.
4995 assert!(
4996 shared > 0 && cpp_only > 0 && attributed > 0,
4997 "no fixture ran (shared={shared}, cpp_only={cpp_only}, \
4998 attributed={attributed}); this test asserted nothing"
4999 );
5000 }
5001
5002 // The #1208 bug-lock that stood here — asserting these shapes lost
5003 // their space *name* while keeping their arity — was retired when
5004 // #1208 landed. The name half now lives beside the walk it shares
5005 // with this module, in `crate::c_declarator`.
5006}