Skip to main content

big_code_analysis/metrics/
loc.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(
8    clippy::enum_glob_use,
9    clippy::match_same_arms,
10    clippy::struct_field_names,
11    clippy::wildcard_imports
12)]
13// Metric counts (token, function, branch, argument, etc.) are stored as
14// `usize` and crossed with `f64` averages, ratios, and Halstead scores
15// across the cyclomatic / MI / Halstead computations. The `usize as f64`
16// and `f64 as usize` casts are intentional and snapshot-anchored — every
17// site is bounded by the count it came from. Allowing the lints at the
18// module level keeps the metric arithmetic legible.
19#![allow(
20    clippy::cast_precision_loss,
21    clippy::cast_possible_truncation,
22    clippy::cast_sign_loss
23)]
24
25use std::collections::HashSet;
26
27use crate::checker::Checker;
28use serde::Serialize;
29use serde::ser::{SerializeStruct, Serializer};
30use std::fmt;
31
32use crate::macros::implement_metric_trait;
33use crate::*;
34
35// Collapse the `usize::MAX` sentinel that `*_min` fields are
36// initialised to on `Default` into `0.0`, so a never-observed space
37// serializes to a meaningful number rather than `1.8446744e19`.
38// Mirrors `tokens::Stats::tokens_min`'s guard.
39#[inline]
40fn min_or_zero(v: usize) -> f64 {
41    if v == usize::MAX { 0.0 } else { v as f64 }
42}
43
44/// The `SLoc` metric suite.
45#[derive(Debug, Clone)]
46pub struct Sloc {
47    start: usize,
48    end: usize,
49    unit: bool,
50    sloc_min: usize,
51    sloc_max: usize,
52}
53
54impl Default for Sloc {
55    fn default() -> Self {
56        Self {
57            start: 0,
58            end: 0,
59            unit: false,
60            sloc_min: usize::MAX,
61            sloc_max: 0,
62        }
63    }
64}
65
66impl Sloc {
67    /// The `SLOC` metric value for this space (source lines, including blanks and comments).
68    #[inline]
69    #[must_use]
70    pub fn sloc(&self) -> f64 {
71        // This metric counts the number of lines in a file
72        // The if construct is needed to count the line of code that represents
73        // the function signature in a function space
74        let sloc = if self.unit {
75            self.end - self.start
76        } else {
77            (self.end - self.start) + 1
78        };
79        sloc as f64
80    }
81
82    /// The `Sloc` metric minimum value. See `min_or_zero` for the
83    /// `usize::MAX` sentinel guard.
84    #[inline]
85    #[must_use]
86    pub fn sloc_min(&self) -> f64 {
87        min_or_zero(self.sloc_min)
88    }
89
90    /// The `Sloc` metric maximum value.
91    #[inline]
92    #[must_use]
93    pub fn sloc_max(&self) -> f64 {
94        self.sloc_max as f64
95    }
96
97    /// Folds `other` into `self`, updating the min/max accumulators.
98    #[inline]
99    pub fn merge(&mut self, other: &Sloc) {
100        self.sloc_min = self.sloc_min.min(other.sloc() as usize);
101        self.sloc_max = self.sloc_max.max(other.sloc() as usize);
102    }
103
104    #[inline]
105    pub(crate) fn compute_minmax(&mut self) {
106        if self.sloc_min == usize::MAX {
107            self.sloc_min = self.sloc_min.min(self.sloc() as usize);
108            self.sloc_max = self.sloc_max.max(self.sloc() as usize);
109        }
110    }
111}
112
113/// The `PLoc` metric suite.
114#[derive(Debug, Clone)]
115pub struct Ploc {
116    lines: HashSet<usize>,
117    ploc_min: usize,
118    ploc_max: usize,
119}
120
121impl Default for Ploc {
122    fn default() -> Self {
123        Self {
124            lines: HashSet::default(),
125            ploc_min: usize::MAX,
126            ploc_max: 0,
127        }
128    }
129}
130
131impl Ploc {
132    /// The `PLOC` metric value for this space (physical lines of code, excluding blanks and comments).
133    #[inline]
134    #[must_use]
135    pub fn ploc(&self) -> f64 {
136        // This metric counts the number of instruction lines in a code
137        // https://en.wikipedia.org/wiki/Source_lines_of_code
138        self.lines.len() as f64
139    }
140
141    /// The `Ploc` metric minimum value. See `min_or_zero` for the
142    /// `usize::MAX` sentinel guard.
143    #[inline]
144    #[must_use]
145    pub fn ploc_min(&self) -> f64 {
146        min_or_zero(self.ploc_min)
147    }
148
149    /// The `Ploc` metric maximum value.
150    #[inline]
151    #[must_use]
152    pub fn ploc_max(&self) -> f64 {
153        self.ploc_max as f64
154    }
155
156    /// Folds `other` into `self`, unioning the line set and updating min/max.
157    #[inline]
158    pub fn merge(&mut self, other: &Ploc) {
159        // Merge ploc lines
160        for l in &other.lines {
161            self.lines.insert(*l);
162        }
163
164        self.ploc_min = self.ploc_min.min(other.ploc() as usize);
165        self.ploc_max = self.ploc_max.max(other.ploc() as usize);
166    }
167
168    #[inline]
169    pub(crate) fn compute_minmax(&mut self) {
170        if self.ploc_min == usize::MAX {
171            self.ploc_min = self.ploc_min.min(self.ploc() as usize);
172            self.ploc_max = self.ploc_max.max(self.ploc() as usize);
173        }
174    }
175}
176
177/// The `CLoc` metric suite.
178#[derive(Debug, Clone)]
179pub struct Cloc {
180    only_comment_lines: usize,
181    code_comment_lines: usize,
182    comment_line_end: Option<usize>,
183    cloc_min: usize,
184    cloc_max: usize,
185}
186
187impl Default for Cloc {
188    fn default() -> Self {
189        Self {
190            only_comment_lines: 0,
191            code_comment_lines: 0,
192            comment_line_end: Option::default(),
193            cloc_min: usize::MAX,
194            cloc_max: 0,
195        }
196    }
197}
198
199impl Cloc {
200    /// The `CLOC` metric value for this space (comment lines, standalone + trailing).
201    #[inline]
202    #[must_use]
203    pub fn cloc(&self) -> f64 {
204        // Comments are counted regardless of their placement
205        // https://en.wikipedia.org/wiki/Source_lines_of_code
206        (self.only_comment_lines + self.code_comment_lines) as f64
207    }
208
209    /// The `Cloc` metric minimum value. See `min_or_zero` for the
210    /// `usize::MAX` sentinel guard.
211    #[inline]
212    #[must_use]
213    pub fn cloc_min(&self) -> f64 {
214        min_or_zero(self.cloc_min)
215    }
216
217    /// The `Cloc` metric maximum value.
218    #[inline]
219    #[must_use]
220    pub fn cloc_max(&self) -> f64 {
221        self.cloc_max as f64
222    }
223
224    /// Folds `other` into `self`, summing comment counts and updating min/max.
225    #[inline]
226    pub fn merge(&mut self, other: &Cloc) {
227        // Merge cloc lines
228        self.only_comment_lines += other.only_comment_lines;
229        self.code_comment_lines += other.code_comment_lines;
230
231        self.cloc_min = self.cloc_min.min(other.cloc() as usize);
232        self.cloc_max = self.cloc_max.max(other.cloc() as usize);
233    }
234
235    #[inline]
236    pub(crate) fn compute_minmax(&mut self) {
237        if self.cloc_min == usize::MAX {
238            self.cloc_min = self.cloc_min.min(self.cloc() as usize);
239            self.cloc_max = self.cloc_max.max(self.cloc() as usize);
240        }
241    }
242}
243
244/// The `LLoc` metric suite.
245#[derive(Debug, Clone)]
246pub struct Lloc {
247    logical_lines: usize,
248    lloc_min: usize,
249    lloc_max: usize,
250}
251
252impl Default for Lloc {
253    fn default() -> Self {
254        Self {
255            logical_lines: 0,
256            lloc_min: usize::MAX,
257            lloc_max: 0,
258        }
259    }
260}
261
262impl Lloc {
263    /// The `LLOC` metric value for this space (logical statements).
264    #[inline]
265    #[must_use]
266    pub fn lloc(&self) -> f64 {
267        // This metric counts the number of statements in a code
268        // https://en.wikipedia.org/wiki/Source_lines_of_code
269        self.logical_lines as f64
270    }
271
272    /// The `Lloc` metric minimum value. See `min_or_zero` for the
273    /// `usize::MAX` sentinel guard.
274    #[inline]
275    #[must_use]
276    pub fn lloc_min(&self) -> f64 {
277        min_or_zero(self.lloc_min)
278    }
279
280    /// The `Lloc` metric maximum value.
281    #[inline]
282    #[must_use]
283    pub fn lloc_max(&self) -> f64 {
284        self.lloc_max as f64
285    }
286
287    /// Folds `other` into `self`, summing statement counts and updating min/max.
288    #[inline]
289    pub fn merge(&mut self, other: &Lloc) {
290        // Merge lloc lines
291        self.logical_lines += other.logical_lines;
292        self.lloc_min = self.lloc_min.min(other.lloc() as usize);
293        self.lloc_max = self.lloc_max.max(other.lloc() as usize);
294    }
295
296    #[inline]
297    pub(crate) fn compute_minmax(&mut self) {
298        if self.lloc_min == usize::MAX {
299            self.lloc_min = self.lloc_min.min(self.lloc() as usize);
300            self.lloc_max = self.lloc_max.max(self.lloc() as usize);
301        }
302    }
303}
304
305/// The `Loc` metric suite.
306#[derive(Debug, Clone)]
307pub struct Stats {
308    sloc: Sloc,
309    ploc: Ploc,
310    cloc: Cloc,
311    lloc: Lloc,
312    space_count: usize,
313    blank_min: usize,
314    blank_max: usize,
315}
316
317impl Default for Stats {
318    fn default() -> Self {
319        Self {
320            sloc: Sloc::default(),
321            ploc: Ploc::default(),
322            cloc: Cloc::default(),
323            lloc: Lloc::default(),
324            space_count: 1,
325            blank_min: usize::MAX,
326            blank_max: 0,
327        }
328    }
329}
330
331impl Serialize for Stats {
332    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
333    where
334        S: Serializer,
335    {
336        let mut st = serializer.serialize_struct("loc", 20)?;
337        st.serialize_field("sloc", &self.sloc())?;
338        st.serialize_field("ploc", &self.ploc())?;
339        st.serialize_field("lloc", &self.lloc())?;
340        st.serialize_field("cloc", &self.cloc())?;
341        st.serialize_field("blank", &self.blank())?;
342        st.serialize_field("sloc_average", &self.sloc_average())?;
343        st.serialize_field("ploc_average", &self.ploc_average())?;
344        st.serialize_field("lloc_average", &self.lloc_average())?;
345        st.serialize_field("cloc_average", &self.cloc_average())?;
346        st.serialize_field("blank_average", &self.blank_average())?;
347        st.serialize_field("sloc_min", &self.sloc_min())?;
348        st.serialize_field("sloc_max", &self.sloc_max())?;
349        st.serialize_field("cloc_min", &self.cloc_min())?;
350        st.serialize_field("cloc_max", &self.cloc_max())?;
351        st.serialize_field("ploc_min", &self.ploc_min())?;
352        st.serialize_field("ploc_max", &self.ploc_max())?;
353        st.serialize_field("lloc_min", &self.lloc_min())?;
354        st.serialize_field("lloc_max", &self.lloc_max())?;
355        st.serialize_field("blank_min", &self.blank_min())?;
356        st.serialize_field("blank_max", &self.blank_max())?;
357        st.end()
358    }
359}
360
361impl fmt::Display for Stats {
362    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
363        write!(
364            f,
365            "sloc: {}, ploc: {}, lloc: {}, cloc: {}, blank: {}, sloc_average: {}, ploc_average: {}, lloc_average: {}, cloc_average: {}, blank_average: {}, sloc_min: {}, sloc_max: {}, cloc_min: {}, cloc_max: {}, ploc_min: {}, ploc_max: {}, lloc_min: {}, lloc_max: {}, blank_min: {}, blank_max: {}",
366            self.sloc(),
367            self.ploc(),
368            self.lloc(),
369            self.cloc(),
370            self.blank(),
371            self.sloc_average(),
372            self.ploc_average(),
373            self.lloc_average(),
374            self.cloc_average(),
375            self.blank_average(),
376            self.sloc_min(),
377            self.sloc_max(),
378            self.cloc_min(),
379            self.cloc_max(),
380            self.ploc_min(),
381            self.ploc_max(),
382            self.lloc_min(),
383            self.lloc_max(),
384            self.blank_min(),
385            self.blank_max(),
386        )
387    }
388}
389
390impl Stats {
391    /// Merges a second `Loc` metric suite into the first one
392    pub fn merge(&mut self, other: &Stats) {
393        self.sloc.merge(&other.sloc);
394        self.ploc.merge(&other.ploc);
395        self.cloc.merge(&other.cloc);
396        self.lloc.merge(&other.lloc);
397
398        // Count spaces
399        self.space_count += other.space_count;
400
401        // min and max
402
403        self.blank_min = self.blank_min.min(other.blank() as usize);
404        self.blank_max = self.blank_max.max(other.blank() as usize);
405    }
406
407    /// The `Sloc` metric.
408    ///
409    /// Counts the number of lines in a scope
410    #[inline]
411    #[must_use]
412    pub fn sloc(&self) -> f64 {
413        self.sloc.sloc()
414    }
415
416    /// The `Ploc` metric.
417    ///
418    /// Counts the number of instruction lines in a scope
419    #[inline]
420    #[must_use]
421    pub fn ploc(&self) -> f64 {
422        self.ploc.ploc()
423    }
424
425    /// The `Lloc` metric.
426    ///
427    /// Counts the number of statements in a scope
428    #[inline]
429    #[must_use]
430    pub fn lloc(&self) -> f64 {
431        self.lloc.lloc()
432    }
433
434    /// The `Cloc` metric.
435    ///
436    /// Counts the number of comments in a scope
437    #[inline]
438    #[must_use]
439    pub fn cloc(&self) -> f64 {
440        self.cloc.cloc()
441    }
442
443    /// The `Blank` metric.
444    ///
445    /// Counts the number of blank lines in a scope
446    #[inline]
447    #[must_use]
448    pub fn blank(&self) -> f64 {
449        self.sloc() - self.ploc() - self.cloc.only_comment_lines as f64
450    }
451
452    /// The `Sloc` metric average value.
453    ///
454    /// This value is computed dividing the `Sloc` value for the number of spaces
455    #[inline]
456    #[must_use]
457    pub fn sloc_average(&self) -> f64 {
458        self.sloc() / self.space_count as f64
459    }
460
461    /// The `Ploc` metric average value.
462    ///
463    /// This value is computed dividing the `Ploc` value for the number of spaces
464    #[inline]
465    #[must_use]
466    pub fn ploc_average(&self) -> f64 {
467        self.ploc() / self.space_count as f64
468    }
469
470    /// The `Lloc` metric average value.
471    ///
472    /// This value is computed dividing the `Lloc` value for the number of spaces
473    #[inline]
474    #[must_use]
475    pub fn lloc_average(&self) -> f64 {
476        self.lloc() / self.space_count as f64
477    }
478
479    /// The `Cloc` metric average value.
480    ///
481    /// This value is computed dividing the `Cloc` value for the number of spaces
482    #[inline]
483    #[must_use]
484    pub fn cloc_average(&self) -> f64 {
485        self.cloc() / self.space_count as f64
486    }
487
488    /// The `Blank` metric average value.
489    ///
490    /// This value is computed dividing the `Blank` value for the number of spaces
491    #[inline]
492    #[must_use]
493    pub fn blank_average(&self) -> f64 {
494        self.blank() / self.space_count as f64
495    }
496
497    /// The `Sloc` metric minimum value.
498    #[inline]
499    #[must_use]
500    pub fn sloc_min(&self) -> f64 {
501        self.sloc.sloc_min()
502    }
503
504    /// The `Sloc` metric maximum value.
505    #[inline]
506    #[must_use]
507    pub fn sloc_max(&self) -> f64 {
508        self.sloc.sloc_max()
509    }
510
511    /// The `Cloc` metric minimum value.
512    #[inline]
513    #[must_use]
514    pub fn cloc_min(&self) -> f64 {
515        self.cloc.cloc_min()
516    }
517
518    /// The `Cloc` metric maximum value.
519    #[inline]
520    #[must_use]
521    pub fn cloc_max(&self) -> f64 {
522        self.cloc.cloc_max()
523    }
524
525    /// The `Ploc` metric minimum value.
526    #[inline]
527    #[must_use]
528    pub fn ploc_min(&self) -> f64 {
529        self.ploc.ploc_min()
530    }
531
532    /// The `Ploc` metric maximum value.
533    #[inline]
534    #[must_use]
535    pub fn ploc_max(&self) -> f64 {
536        self.ploc.ploc_max()
537    }
538
539    /// The `Lloc` metric minimum value.
540    #[inline]
541    #[must_use]
542    pub fn lloc_min(&self) -> f64 {
543        self.lloc.lloc_min()
544    }
545
546    /// The `Lloc` metric maximum value.
547    #[inline]
548    #[must_use]
549    pub fn lloc_max(&self) -> f64 {
550        self.lloc.lloc_max()
551    }
552
553    /// The `Blank` metric minimum value. See `min_or_zero` for the
554    /// `usize::MAX` sentinel guard.
555    #[inline]
556    #[must_use]
557    pub fn blank_min(&self) -> f64 {
558        min_or_zero(self.blank_min)
559    }
560
561    /// The `Blank` metric maximum value.
562    #[inline]
563    #[must_use]
564    pub fn blank_max(&self) -> f64 {
565        self.blank_max as f64
566    }
567
568    #[inline]
569    pub(crate) fn compute_minmax(&mut self) {
570        self.sloc.compute_minmax();
571        self.ploc.compute_minmax();
572        self.cloc.compute_minmax();
573        self.lloc.compute_minmax();
574
575        if self.blank_min == usize::MAX {
576            self.blank_min = self.blank_min.min(self.blank() as usize);
577            self.blank_max = self.blank_max.max(self.blank() as usize);
578        }
579    }
580
581    pub(crate) fn init_unit_span(&mut self, start: usize, end: usize) {
582        self.sloc.start = start;
583        self.sloc.end = end;
584        self.sloc.unit = true;
585    }
586}
587
588#[doc(hidden)]
589/// Per-language computation of the lines-of-code metrics.
590pub trait Loc
591where
592    Self: Checker,
593{
594    /// Walk `node` and update `stats` with this metric for the language
595    /// implementing the trait.
596    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool);
597}
598
599#[inline]
600fn init(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) -> (usize, usize) {
601    let start = node.start_row();
602    let end = node.end_row();
603
604    if is_func_space {
605        stats.sloc.start = start;
606        stats.sloc.end = end;
607        stats.sloc.unit = is_unit;
608    }
609    (start, end)
610}
611
612#[inline]
613// Discriminates among the comments that are *after* a code line and
614// the ones that are on an independent line.
615// This difference is necessary in order to avoid having
616// a wrong count for the blank metric.
617fn add_cloc_lines(stats: &mut Stats, start: usize, end: usize) {
618    let comment_diff = end - start;
619    let is_comment_after_code_line = stats.ploc.lines.contains(&start);
620    if is_comment_after_code_line && comment_diff == 0 {
621        // A comment is *entirely* next to a code line
622        stats.cloc.code_comment_lines += 1;
623    } else if is_comment_after_code_line && comment_diff > 0 {
624        // A block comment that starts next to a code line and ends on
625        // independent lines.
626        stats.cloc.code_comment_lines += 1;
627        stats.cloc.only_comment_lines += comment_diff;
628    } else {
629        // A comment on an independent line AND
630        // a block comment on independent lines OR
631        // a comment *before* a code line
632        stats.cloc.only_comment_lines += (end - start) + 1;
633        // Save line end of a comment to check whether
634        // a comment *before* a code line is considered
635        stats.cloc.comment_line_end = Some(end);
636    }
637}
638
639#[inline]
640// Detects the comments that are on a code line but *before* the code part.
641// This difference is necessary in order to avoid having
642// a wrong count for the blank metric.
643fn check_comment_ends_on_code_line(stats: &mut Stats, start_code_line: usize) {
644    if let Some(end) = stats.cloc.comment_line_end
645        && end == start_code_line
646        && !stats.ploc.lines.contains(&start_code_line)
647    {
648        // Comment entirely *before* a code line
649        stats.cloc.only_comment_lines -= 1;
650        stats.cloc.code_comment_lines += 1;
651    }
652}
653
654impl Loc for PythonCode {
655    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
656        use Python::*;
657
658        let (start, end) = init(node, stats, is_func_space, is_unit);
659
660        match node.kind_id().into() {
661            StringStart | StringEnd | StringContent | Block | Module => {}
662            Comment => {
663                add_cloc_lines(stats, start, end);
664            }
665            String => {
666                let Some(parent) = node.parent() else { return };
667                if let ExpressionStatement = parent.kind_id().into() {
668                    add_cloc_lines(stats, start, end);
669                } else if parent.start_row() != start {
670                    check_comment_ends_on_code_line(stats, start);
671                    stats.ploc.lines.insert(start);
672                }
673            }
674            Statement
675            | SimpleStatements
676            | ImportStatement
677            | FutureImportStatement
678            | ImportFromStatement
679            | PrintStatement
680            | AssertStatement
681            | ReturnStatement
682            | DeleteStatement
683            | RaiseStatement
684            | PassStatement
685            | BreakStatement
686            | ContinueStatement
687            | IfStatement
688            | ForStatement
689            | WhileStatement
690            | TryStatement
691            | WithStatement
692            | GlobalStatement
693            | NonlocalStatement
694            | ExecStatement
695            | ExpressionStatement => {
696                stats.lloc.logical_lines += 1;
697            }
698            _ => {
699                check_comment_ends_on_code_line(stats, start);
700                stats.ploc.lines.insert(start);
701            }
702        }
703    }
704}
705
706impl Loc for MozjsCode {
707    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
708        use Mozjs::*;
709
710        let (start, end) = init(node, stats, is_func_space, is_unit);
711
712        match node.kind_id().into() {
713            String | DQUOTE | Program => {}
714            Comment => {
715                add_cloc_lines(stats, start, end);
716            }
717            ExpressionStatement | ExportStatement | ImportStatement | StatementBlock
718            | IfStatement | SwitchStatement | ForStatement | ForInStatement | WhileStatement
719            | DoStatement | TryStatement | WithStatement | BreakStatement | ContinueStatement
720            | DebuggerStatement | ReturnStatement | ThrowStatement | EmptyStatement
721            | StatementIdentifier => {
722                stats.lloc.logical_lines += 1;
723            }
724            _ => {
725                check_comment_ends_on_code_line(stats, start);
726                stats.ploc.lines.insert(start);
727            }
728        }
729    }
730}
731
732impl Loc for JavascriptCode {
733    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
734        use Javascript::*;
735
736        let (start, end) = init(node, stats, is_func_space, is_unit);
737
738        match node.kind_id().into() {
739            String | DQUOTE | Program => {}
740            Comment => {
741                add_cloc_lines(stats, start, end);
742            }
743            ExpressionStatement | ExportStatement | ImportStatement | StatementBlock
744            | IfStatement | SwitchStatement | ForStatement | ForInStatement | WhileStatement
745            | DoStatement | TryStatement | WithStatement | BreakStatement | ContinueStatement
746            | DebuggerStatement | ReturnStatement | ThrowStatement | EmptyStatement
747            | StatementIdentifier => {
748                stats.lloc.logical_lines += 1;
749            }
750            _ => {
751                check_comment_ends_on_code_line(stats, start);
752                stats.ploc.lines.insert(start);
753            }
754        }
755    }
756}
757
758impl Loc for TypescriptCode {
759    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
760        use Typescript::*;
761
762        let (start, end) = init(node, stats, is_func_space, is_unit);
763
764        match node.kind_id().into() {
765            String | DQUOTE | Program => {}
766            Comment => {
767                add_cloc_lines(stats, start, end);
768            }
769            ExpressionStatement | ExportStatement | ImportStatement | StatementBlock
770            | IfStatement | SwitchStatement | ForStatement | ForInStatement | WhileStatement
771            | DoStatement | TryStatement | WithStatement | BreakStatement | ContinueStatement
772            | DebuggerStatement | ReturnStatement | ThrowStatement | EmptyStatement
773            | StatementIdentifier => {
774                stats.lloc.logical_lines += 1;
775            }
776            _ => {
777                check_comment_ends_on_code_line(stats, start);
778                stats.ploc.lines.insert(start);
779            }
780        }
781    }
782}
783
784impl Loc for TsxCode {
785    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
786        use Tsx::*;
787
788        let (start, end) = init(node, stats, is_func_space, is_unit);
789
790        match node.kind_id().into() {
791            String | DQUOTE | Program => {}
792            Comment => {
793                add_cloc_lines(stats, start, end);
794            }
795            ExpressionStatement | ExportStatement | ImportStatement | StatementBlock
796            | IfStatement | SwitchStatement | ForStatement | ForInStatement | WhileStatement
797            | DoStatement | TryStatement | WithStatement | BreakStatement | ContinueStatement
798            | DebuggerStatement | ReturnStatement | ThrowStatement | EmptyStatement
799            | StatementIdentifier => {
800                stats.lloc.logical_lines += 1;
801            }
802            _ => {
803                check_comment_ends_on_code_line(stats, start);
804                stats.ploc.lines.insert(start);
805            }
806        }
807    }
808}
809
810impl Loc for RustCode {
811    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
812        use Rust::*;
813
814        let (start, end) = init(node, stats, is_func_space, is_unit);
815
816        match node.kind_id().into() {
817            StringLiteral
818            | RawStringLiteral
819            | Block
820            | SourceFile
821            | SLASH
822            | SLASHSLASH
823            | SLASHSTAR
824            | STARSLASH
825            | OuterDocCommentMarker
826            | OuterDocCommentMarker2
827            | DocComment
828            | InnerDocCommentMarker
829            | BANG => {}
830            BlockComment => {
831                add_cloc_lines(stats, start, end);
832            }
833            LineComment => {
834                // Exclude the last line for `LineComment` containing a `DocComment`,
835                // since the `DocComment` includes the newline,
836                // as explained here: https://github.com/tree-sitter/tree-sitter-rust/blob/2eaf126458a4d6a69401089b6ba78c5e5d6c1ced/src/scanner.c#L194-L195
837                let end = if node.is_child(DocComment as u16) {
838                    end - 1
839                } else {
840                    end
841                };
842                add_cloc_lines(stats, start, end);
843            }
844            Statement
845            | EmptyStatement
846            | ExpressionStatement
847            | LetDeclaration
848            | AssignmentExpression
849            | CompoundAssignmentExpr => {
850                stats.lloc.logical_lines += 1;
851            }
852            _ => {
853                check_comment_ends_on_code_line(stats, start);
854                stats.ploc.lines.insert(start);
855            }
856        }
857    }
858}
859
860impl Loc for CppCode {
861    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
862        use Cpp::*;
863
864        let (start, end) = init(node, stats, is_func_space, is_unit);
865
866        match node.kind_id().into() {
867            RawStringLiteral | StringLiteral | DeclarationList | FieldDeclarationList
868            | TranslationUnit => {}
869            Comment => {
870                add_cloc_lines(stats, start, end);
871            }
872            WhileStatement | SwitchStatement | CaseStatement | IfStatement | ForStatement
873            | ReturnStatement | BreakStatement | ContinueStatement | GotoStatement
874            | ThrowStatement | TryStatement | TryStatement2 | ExpressionStatement
875            | ExpressionStatement2 | LabeledStatement | StatementIdentifier => {
876                stats.lloc.logical_lines += 1;
877            }
878            Declaration => {
879                if node.count_specific_ancestors::<CppParser>(
880                    |node| {
881                        matches!(
882                            node.kind_id().into(),
883                            WhileStatement | ForStatement | IfStatement
884                        )
885                    },
886                    |node| node.kind_id() == CompoundStatement,
887                ) == 0
888                {
889                    stats.lloc.logical_lines += 1;
890                }
891            }
892            _ => {
893                check_comment_ends_on_code_line(stats, start);
894                stats.ploc.lines.insert(start);
895
896                // As reported here: https://github.com/tree-sitter/tree-sitter-cpp/issues/276
897                // `tree-sitter-cpp` doesn't expand macros, providing a single `PreprocArg` node for the entire macro argument.
898                // Therefore, all lines from `start_row` to `end_row` must be added to PLOC to account for the unexpanded macro content
899                if let PreprocArg = node.kind_id().into() {
900                    (node.start_row() + 1..=node.end_row()).for_each(|line| {
901                        stats.ploc.lines.insert(line);
902                    });
903                }
904            }
905        }
906    }
907}
908
909impl Loc for JavaCode {
910    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
911        use Java::*;
912
913        let (start, end) = init(node, stats, is_func_space, is_unit);
914        let kind_id: Java = node.kind_id().into();
915        // LLOC in Java is counted for statements only
916        // https://docs.oracle.com/javase/tutorial/java/nutsandbolts/expressions.html
917        match kind_id {
918            Program => {}
919            LineComment | BlockComment => {
920                add_cloc_lines(stats, start, end);
921            }
922            AssertStatement | BreakStatement | ContinueStatement | DoStatement
923            | EnhancedForStatement | ExpressionStatement | ForStatement | IfStatement
924            | ReturnStatement | SwitchExpression | ThrowStatement | TryStatement
925            | WhileStatement => {
926                stats.lloc.logical_lines += 1;
927            }
928            LocalVariableDeclaration => {
929                if node.count_specific_ancestors::<JavaParser>(
930                    |node| node.kind_id() == ForStatement,
931                    |node| node.kind_id() == Block,
932                ) == 0
933                {
934                    // The initializer, condition, and increment in a for loop are expressions.
935                    // Don't count the variable declaration if in a ForStatement.
936                    // https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html
937                    stats.lloc.logical_lines += 1;
938                }
939            }
940            _ => {
941                check_comment_ends_on_code_line(stats, start);
942                stats.ploc.lines.insert(start);
943            }
944        }
945    }
946}
947
948impl Loc for GroovyCode {
949    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
950        use Groovy::*;
951
952        let (start, end) = init(node, stats, is_func_space, is_unit);
953        let kind_id: Groovy = node.kind_id().into();
954        // LLOC counts statements only — same definition as Java.
955        // Groovy additions over Java's list:
956        //   - `YieldStatement` for the Java-14+ switch-expression form.
957        //   - `CommandChain` for Groovy's parens-less call as a top-
958        //     level statement (the dekobon grammar's distinct node;
959        //     the prior amaanq grammar called this `juxt_function_call`).
960        //   - `ForInStatement` (the dekobon grammar's name for the
961        //     `for (x in xs)` / `for (Foo x : xs)` shape; the prior
962        //     amaanq grammar called this `enhanced_for_statement`).
963        //   - `PipelineStatement` is the dekobon grammar's distinct
964        //     node for a Jenkinsfile `pipeline { … }` block, treated
965        //     here as a single statement.
966        match kind_id {
967            SourceFile => {}
968            LineComment | BlockComment | GroovydocComment => {
969                add_cloc_lines(stats, start, end);
970            }
971            // An `ExpressionStatement` whose only child is a bare
972            // `Closure` is a Groovy-specific grammar artifact: the
973            // alternative branch of `if (x) { … } else { … }` wraps
974            // the brace-block as `expression_statement (closure)`
975            // even though the user wrote it as part of the surrounding
976            // `if`. Skipping the wrapper avoids double-counting the
977            // else-branch as a separate LLOC. Real expression
978            // statements like `expression_statement (command_chain)`
979            // for `println(x)` keep firing because their child is not
980            // a bare `Closure`.
981            ExpressionStatement if node.child(0).is_some_and(|c| c.kind_id() == Closure) => {
982                // No-op: do not count as LLOC.
983            }
984            AssertStatement | BreakStatement | CommandChain | ContinueStatement
985            | DoWhileStatement | ExpressionStatement | ForInStatement | ForStatement
986            | IfStatement | PipelineStatement | ReturnStatement | SwitchExpression
987            | ThrowStatement | TryStatement | WhileStatement | YieldStatement => {
988                stats.lloc.logical_lines += 1;
989            }
990            LocalVariableDeclaration => {
991                if node.count_specific_ancestors::<GroovyParser>(
992                    |node| node.kind_id() == ForStatement,
993                    |node| node.kind_id() == Block,
994                ) == 0
995                {
996                    // Skip the initializer slot of a classic `for` loop —
997                    // same reason as Java's impl.
998                    stats.lloc.logical_lines += 1;
999                }
1000            }
1001            _ => {
1002                check_comment_ends_on_code_line(stats, start);
1003                stats.ploc.lines.insert(start);
1004            }
1005        }
1006    }
1007}
1008
1009impl Loc for CsharpCode {
1010    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
1011        use Csharp::*;
1012
1013        let (start, end) = init(node, stats, is_func_space, is_unit);
1014        let kind_id: Csharp = node.kind_id().into();
1015        match kind_id {
1016            CompilationUnit => {}
1017            Comment => {
1018                add_cloc_lines(stats, start, end);
1019            }
1020            BreakStatement | CheckedStatement | ContinueStatement | DoStatement
1021            | ExpressionStatement | FixedStatement | ForStatement | ForeachStatement
1022            | GotoStatement | IfStatement | LabeledStatement | LockStatement | ReturnStatement
1023            | SwitchStatement | ThrowStatement | TryStatement | UnsafeStatement
1024            | UsingStatement | WhileStatement | YieldStatement => {
1025                stats.lloc.logical_lines += 1;
1026            }
1027            LocalDeclarationStatement => {
1028                // Variable declarations inside a `for_statement` init/condition/update
1029                // (e.g. `for (int i = 0; i < n; i++)`) shouldn't bump LLOC; the
1030                // surrounding `for_statement` already counts.
1031                if node.count_specific_ancestors::<CsharpParser>(
1032                    |n| n.kind_id() == ForStatement,
1033                    |n| n.kind_id() == Block,
1034                ) == 0
1035                {
1036                    stats.lloc.logical_lines += 1;
1037                }
1038            }
1039            _ => {
1040                check_comment_ends_on_code_line(stats, start);
1041                stats.ploc.lines.insert(start);
1042            }
1043        }
1044    }
1045}
1046
1047impl Loc for GoCode {
1048    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
1049        // Aliased because `Go::Go` (the `go` keyword variant) collides with
1050        // the bare enum name in pattern position under `use Go::*;`.
1051        use Go as G;
1052
1053        let (start, end) = init(node, stats, is_func_space, is_unit);
1054
1055        match node.kind_id().into() {
1056            G::SourceFile | G::RawStringLiteral | G::InterpretedStringLiteral => {}
1057            G::Comment => {
1058                add_cloc_lines(stats, start, end);
1059            }
1060            G::FallthroughStatement
1061            | G::BreakStatement
1062            | G::ContinueStatement
1063            | G::GotoStatement
1064            | G::ReturnStatement
1065            | G::GoStatement
1066            | G::DeferStatement
1067            | G::IfStatement
1068            | G::ForStatement
1069            | G::ExpressionSwitchStatement
1070            | G::TypeSwitchStatement
1071            | G::SelectStatement
1072            | G::LabeledStatement => {
1073                stats.lloc.logical_lines += 1;
1074            }
1075            G::ExpressionStatement
1076            | G::SendStatement
1077            | G::IncStatement
1078            | G::DecStatement
1079            | G::AssignmentStatement
1080            | G::ShortVarDeclaration
1081            | G::VarDeclaration
1082            | G::ConstDeclaration => {
1083                // Skip simple statements / declarations that appear inside a
1084                // for-clause init or update slot (e.g. `for i := 0; i < n; i++`);
1085                // the surrounding `for_statement` already counts as one
1086                // logical line.
1087                if node.count_specific_ancestors::<GoParser>(
1088                    |n| n.kind_id() == G::ForClause,
1089                    |n| n.kind_id() == G::Block,
1090                ) == 0
1091                {
1092                    stats.lloc.logical_lines += 1;
1093                }
1094            }
1095            _ => {
1096                check_comment_ends_on_code_line(stats, start);
1097                stats.ploc.lines.insert(start);
1098            }
1099        }
1100    }
1101}
1102
1103impl Loc for PerlCode {
1104    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
1105        use Perl as P;
1106
1107        let (start, end) = init(node, stats, is_func_space, is_unit);
1108
1109        match node.kind_id().into() {
1110            P::SourceFile
1111            | P::Block
1112            | P::StandaloneBlock
1113            | P::HeredocBodyStatement
1114            | P::HeredocContent
1115            | P::PodContent
1116            | P::StringSingleQuoted
1117            | P::StringDoubleQuoted
1118            | P::StringQQuoted
1119            | P::StringQqQuoted
1120            | P::BacktickQuoted
1121            | P::CommandQxQuoted
1122            // Internal string tokens — already accounted for by the
1123            // parent string node's start row.
1124            | P::SQUOTE
1125            | P::DQUOTE
1126            | P::StringContent
1127            | P::StringSingleQuotedContent
1128            | P::StringSingleQQuotedContent
1129            | P::StringQqQuotedContent
1130            | P::StringDoubleQuotedContent
1131            | P::EscapeSequence
1132            | P::EscapeSequenceToken1
1133            | P::Interpolation => {}
1134            P::Comments | P::PodStatement => {
1135                add_cloc_lines(stats, start, end);
1136            }
1137            P::SingleLineStatement
1138            | P::IfStatement
1139            | P::UnlessStatement
1140            | P::WhileStatement
1141            | P::UntilStatement
1142            | P::ForStatement1
1143            | P::ForStatement2
1144            | P::LoopControlStatement
1145            | P::PackageStatement
1146            | P::RequireStatement
1147            | P::UseNoStatement
1148            | P::UseNoFeatureStatement
1149            | P::UseNoIfStatement
1150            | P::UseNoSubsStatement
1151            | P::UseConstantStatement
1152            | P::UseParentStatement
1153            | P::UseNoVersion
1154            | P::EllipsisStatement => {
1155                stats.lloc.logical_lines += 1;
1156            }
1157            P::SEMI => {
1158                // A `;` at top of `source_file` / a function `block` ends a
1159                // statement (Perl wraps simple expressions in semicolons
1160                // rather than emitting a dedicated statement kind), so it
1161                // contributes one LLOC. Then fall through to the same PLOC
1162                // bookkeeping the catch-all arm does.
1163                if let Some(parent) = node.parent()
1164                    && matches!(parent.kind_id().into(), P::SourceFile | P::Block)
1165                {
1166                    stats.lloc.logical_lines += 1;
1167                }
1168                check_comment_ends_on_code_line(stats, start);
1169                stats.ploc.lines.insert(start);
1170            }
1171            _ => {
1172                check_comment_ends_on_code_line(stats, start);
1173                stats.ploc.lines.insert(start);
1174            }
1175        }
1176    }
1177}
1178
1179impl Loc for LuaCode {
1180    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
1181        let (start, end) = init(node, stats, is_func_space, is_unit);
1182
1183        match node.kind_id().into() {
1184            // Skip root and string literals.
1185            Lua::Chunk | Lua::String => {}
1186
1187            // Skip tokens that are children of comment nodes.
1188            // Lua's comment nodes have children: DASHDASH / LBRACKLBRACK (openers),
1189            // CommentContent / CommentContent2 (body), and RBRACKRBRACK (block closer).
1190            // Without this guard they hit the `_` arm and add their rows to `ploc`,
1191            // which rows are already counted in `only_comment_lines`, producing
1192            // negative `blank`. LBRACKLBRACK / RBRACKRBRACK also appear as children of
1193            // string nodes, so we guard on the parent kind to avoid skipping them there.
1194            Lua::DASHDASH | Lua::CommentContent | Lua::CommentContent2 => {}
1195            Lua::LBRACKLBRACK | Lua::RBRACKRBRACK
1196                if node.parent().is_some_and(|p| p.kind_id() == Lua::Comment) => {}
1197
1198            Lua::Comment => {
1199                add_cloc_lines(stats, start, end);
1200            }
1201
1202            // Standalone assignment (`x = 1`). Skip when nested inside a local variable
1203            // declaration (`local x = 1`) — the parent VariableDeclaration already counts.
1204            Lua::AssignmentStatement | Lua::AssignmentStatement2
1205                if !node.parent().is_some_and(|p| {
1206                    matches!(
1207                        p.kind_id().into(),
1208                        Lua::VariableDeclaration
1209                            | Lua::VariableDeclaration2
1210                            | Lua::ImplicitVariableDeclaration
1211                    )
1212                }) =>
1213            {
1214                stats.lloc.logical_lines += 1;
1215            }
1216
1217            Lua::IfStatement
1218            | Lua::ForStatement
1219            | Lua::WhileStatement
1220            | Lua::RepeatStatement
1221            | Lua::DoStatement
1222            | Lua::ReturnStatement
1223            | Lua::BreakStatement
1224            | Lua::GotoStatement
1225            | Lua::LabelStatement
1226            | Lua::VariableDeclaration
1227            | Lua::VariableDeclaration2
1228            | Lua::ImplicitVariableDeclaration
1229            | Lua::FunctionDeclaration
1230            | Lua::FunctionDeclaration2
1231            | Lua::FunctionDeclaration3 => {
1232                stats.lloc.logical_lines += 1;
1233            }
1234
1235            _ => {
1236                check_comment_ends_on_code_line(stats, start);
1237                stats.ploc.lines.insert(start);
1238            }
1239        }
1240    }
1241}
1242
1243impl Loc for KotlinCode {
1244    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
1245        use Kotlin::*;
1246
1247        let (start, end) = init(node, stats, is_func_space, is_unit);
1248
1249        match node.kind_id().into() {
1250            SourceFile => {}
1251            LineComment | BlockComment => {
1252                add_cloc_lines(stats, start, end);
1253            }
1254            ForStatement | WhileStatement | DoWhileStatement | IfExpression | WhenExpression
1255            | TryExpression | ThrowExpression | ReturnExpression | Assignment
1256            | PropertyDeclaration => {
1257                stats.lloc.logical_lines += 1;
1258            }
1259            // Bare expression statements (e.g. `println(x)`) have no
1260            // ExpressionStatement wrapper in tree-sitter-kotlin-ng. Count
1261            // them as lloc when they appear as direct children of a block;
1262            // otherwise fall through to ploc so nested calls still count
1263            // as physical lines.
1264            CallExpression | NavigationExpression => {
1265                if let Some(parent) = node.parent()
1266                    && matches!(
1267                        parent.kind_id().into(),
1268                        Block | FunctionBody | SourceFile | CatchBlock | FinallyBlock
1269                    )
1270                {
1271                    stats.lloc.logical_lines += 1;
1272                } else {
1273                    check_comment_ends_on_code_line(stats, start);
1274                    stats.ploc.lines.insert(start);
1275                }
1276            }
1277            _ => {
1278                check_comment_ends_on_code_line(stats, start);
1279                stats.ploc.lines.insert(start);
1280            }
1281        }
1282    }
1283}
1284
1285impl Loc for PhpCode {
1286    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
1287        use Php::*;
1288
1289        let (start, end) = init(node, stats, is_func_space, is_unit);
1290
1291        match node.kind_id().into() {
1292            Program => {}
1293            Comment => {
1294                add_cloc_lines(stats, start, end);
1295            }
1296            // Statement kinds that contribute one logical line each.
1297            ExpressionStatement
1298            | EchoStatement
1299            | EmptyStatement
1300            | IfStatement
1301            | SwitchStatement
1302            | ForStatement
1303            | ForeachStatement
1304            | WhileStatement
1305            | DoStatement
1306            | TryStatement
1307            | ReturnStatement
1308            | BreakStatement
1309            | ContinueStatement
1310            | GotoStatement
1311            | UnsetStatement
1312            | DeclareStatement
1313            | NamespaceUseDeclaration
1314            | GlobalDeclaration
1315            | FunctionStaticDeclaration
1316            | ConstDeclaration
1317            | ConstDeclaration2
1318            | PropertyDeclaration
1319            | NamedLabelStatement => {
1320                stats.lloc.logical_lines += 1;
1321            }
1322            _ => {
1323                check_comment_ends_on_code_line(stats, start);
1324                stats.ploc.lines.insert(start);
1325            }
1326        }
1327    }
1328}
1329
1330// Real defaults — Loc counts on these "languages" would conflate
1331// comments / preproc directives with executable code; treating them
1332// as 0 is the documented behaviour. Audited in #188.
1333implement_metric_trait!(Loc, PreprocCode, CcommentCode);
1334
1335impl Loc for RubyCode {
1336    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
1337        use Ruby as R;
1338
1339        let (start, end) = init(node, stats, is_func_space, is_unit);
1340        match node.kind_id().into() {
1341            R::Program => {}
1342            R::Comment => {
1343                add_cloc_lines(stats, start, end);
1344            }
1345            // LLOC contributors: control-flow constructs, method/class/module
1346            // declarations, postfix statement modifiers, and the dedicated
1347            // jump/redo/retry statement nodes. Assignment expressions and
1348            // ordinary method calls in expression-statement position are
1349            // intentionally NOT counted to avoid double-counting every
1350            // sub-expression: a single `a = b + c.d(e)` line would otherwise
1351            // contribute multiple LLOC. The Ruby grammar has no
1352            // `expression_statement` wrapper to disambiguate.
1353            R::If
1354            | R::Unless
1355            | R::Elsif
1356            | R::While
1357            | R::Until
1358            | R::For
1359            | R::Case
1360            | R::CaseMatch
1361            | R::Begin
1362            | R::IfModifier
1363            | R::UnlessModifier
1364            | R::WhileModifier
1365            | R::UntilModifier
1366            | R::RescueModifier
1367            | R::RescueModifier2
1368            | R::RescueModifier3
1369            | R::Return
1370            | R::Return2
1371            | R::Yield
1372            | R::Yield2
1373            | R::Break
1374            | R::Break2
1375            | R::Next
1376            | R::Next2
1377            | R::Redo
1378            | R::Retry
1379            | R::Method
1380            | R::SingletonMethod
1381            | R::Class
1382            | R::SingletonClass
1383            | R::Module
1384            | R::BeginBlock
1385            | R::EndBlock
1386            | R::Undef
1387            | R::Alias
1388            | R::EmptyStatement => {
1389                stats.lloc.logical_lines += 1;
1390            }
1391            _ => {
1392                check_comment_ends_on_code_line(stats, start);
1393                stats.ploc.lines.insert(start);
1394            }
1395        }
1396    }
1397}
1398
1399impl Loc for ElixirCode {
1400    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
1401        use Elixir as E;
1402
1403        let (start, end) = init(node, stats, is_func_space, is_unit);
1404
1405        match node.kind_id().into() {
1406            // Root of the file — handled by `init` above.
1407            E::Source => {}
1408
1409            // CLOC: every line a comment spans.
1410            E::Comment => add_cloc_lines(stats, start, end),
1411
1412            // The `stab_clause` itself is a control-flow noise node
1413            // (case/cond/with arm header). Its `body` child holds the
1414            // actual statements executed when the pattern matches, and
1415            // those count via the parent-container check below. Skipping
1416            // the `stab_clause` keeps the count consistent with C-family
1417            // languages where `case:` labels don't count but the body
1418            // statements do. A `stab_clause` always has at least the
1419            // `->` token plus a `body`, so there is no leaf-PLOC path
1420            // to handle here.
1421            E::StabClause => {}
1422
1423            // LLOC: any named node whose parent is a statement container
1424            // is one logical line. This catches `def`/`if`/`case`/`cond`
1425            // calls (themselves `Call` nodes at the top level),
1426            // assignment `binary_operator`s in function bodies, and bare
1427            // expressions used as statements. The container kinds are
1428            // every grammar node whose direct named children represent
1429            // a sequence of executable expressions. The `is_named()`
1430            // check runs first so unnamed leaves (`do`, `end`, `,`, …)
1431            // skip the parent lookup entirely.
1432            _ => {
1433                if node.0.is_named()
1434                    && node.parent().is_some_and(|p| {
1435                        matches!(
1436                            p.kind_id().into(),
1437                            E::Source
1438                                | E::Body
1439                                | E::Block
1440                                | E::DoBlock
1441                                | E::AfterBlock
1442                                | E::RescueBlock
1443                                | E::CatchBlock
1444                                | E::ElseBlock
1445                        )
1446                    })
1447                {
1448                    stats.lloc.logical_lines += 1;
1449                }
1450                if node.child_count() == 0 {
1451                    check_comment_ends_on_code_line(stats, start);
1452                    stats.ploc.lines.insert(start);
1453                }
1454            }
1455        }
1456    }
1457}
1458
1459impl Loc for BashCode {
1460    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
1461        use Bash::*;
1462
1463        let (start, end) = init(node, stats, is_func_space, is_unit);
1464
1465        match node.kind_id().into() {
1466            Program => {}
1467            Comment => {
1468                add_cloc_lines(stats, start, end);
1469            }
1470            // LLOC: leaf statement nodes. Pipeline, Subshell, and
1471            // RedirectedStatement are excluded because they wrap inner
1472            // Command nodes that are already counted here.
1473            Command | VariableAssignment | DeclarationCommand | UnsetCommand | IfStatement
1474            | ForStatement | CStyleForStatement | WhileStatement | CaseStatement
1475            | FunctionDefinition => {
1476                stats.lloc.logical_lines += 1;
1477            }
1478            _ => {
1479                if node.child_count() == 0 {
1480                    stats.ploc.lines.insert(start);
1481                }
1482            }
1483        }
1484    }
1485}
1486
1487impl Loc for TclCode {
1488    fn compute(node: &Node, stats: &mut Stats, is_func_space: bool, is_unit: bool) {
1489        let (start, end) = init(node, stats, is_func_space, is_unit);
1490
1491        match node.kind_id().into() {
1492            Tcl::SourceFile => {}
1493
1494            Tcl::Comment => {
1495                add_cloc_lines(stats, start, end);
1496            }
1497
1498            Tcl::Procedure
1499            | Tcl::If
1500            | Tcl::Elseif
1501            | Tcl::Foreach
1502            | Tcl::While
1503            | Tcl::Set
1504            | Tcl::Global
1505            | Tcl::Namespace
1506            | Tcl::Try
1507            | Tcl::Catch
1508            | Tcl::Regexp => {
1509                stats.lloc.logical_lines += 1;
1510            }
1511
1512            // `expr` at statement level is a logical line; inside [...] it is a
1513            // sub-expression and should not be counted (same semantics as Command).
1514            Tcl::ExprCmd
1515            // Commands inside [...] are sub-expressions, not top-level statements.
1516            | Tcl::Command
1517                if node
1518                    .parent()
1519                    .is_none_or(|p| p.kind_id() != Tcl::CommandSubstitution) =>
1520            {
1521                stats.lloc.logical_lines += 1;
1522            }
1523
1524            _ => {
1525                check_comment_ends_on_code_line(stats, start);
1526                stats.ploc.lines.insert(start);
1527            }
1528        }
1529    }
1530}
1531
1532#[cfg(test)]
1533#[allow(
1534    clippy::float_cmp,
1535    clippy::cast_precision_loss,
1536    clippy::cast_possible_truncation,
1537    clippy::cast_sign_loss,
1538    clippy::similar_names,
1539    clippy::doc_markdown,
1540    clippy::needless_raw_string_hashes,
1541    clippy::too_many_lines
1542)]
1543mod tests {
1544    use crate::tools::check_metrics;
1545
1546    use super::*;
1547
1548    /// A `Stats::default()` that never sees an observation must not leak
1549    /// the `usize::MAX` sentinel for any of the LOC `_min` accumulators
1550    /// (`sloc_min`, `ploc_min`, `lloc_min`, `cloc_min`, `blank_min`).
1551    /// The getters collapse the sentinel to `0.0` so JSON never emits
1552    /// `1.8446744e19`.
1553    #[test]
1554    fn loc_empty_file_min_is_zero() {
1555        let stats = Stats::default();
1556        assert_eq!(stats.sloc_min(), 0.0);
1557        assert_eq!(stats.ploc_min(), 0.0);
1558        assert_eq!(stats.lloc_min(), 0.0);
1559        assert_eq!(stats.cloc_min(), 0.0);
1560        assert_eq!(stats.blank_min(), 0.0);
1561    }
1562
1563    /// Parses `source` with `PerlParser` and asserts the resulting tree has
1564    /// no `ERROR` nodes. Use alongside metric assertions whose expected
1565    /// values would happen to match what an error tree produces — a parse
1566    /// regression in tree-sitter-perl could otherwise leave such tests
1567    /// silently green.
1568    #[cfg(test)]
1569    fn assert_perl_parses_cleanly(source: &str) {
1570        use crate::traits::ParserTrait;
1571        // Mirror the trailing-newline normalisation `check_func_space` does
1572        // before handing input to the parser, so this helper sees the same
1573        // bytes the metric tests do.
1574        let path = std::path::PathBuf::from("foo.pl");
1575        let mut bytes = source.trim_end_matches('\n').as_bytes().to_vec();
1576        bytes.push(b'\n');
1577        let parser = PerlParser::new(bytes, &path, None);
1578        assert!(
1579            !parser.get_root().has_error(),
1580            "tree-sitter-perl returned an error tree for snippet:\n{source}"
1581        );
1582    }
1583
1584    #[test]
1585    fn python_sloc() {
1586        check_metrics::<PythonParser>(
1587            "
1588
1589            a = 42
1590
1591            ",
1592            "foo.py",
1593            |metric| {
1594                // Spaces: 1
1595                insta::assert_json_snapshot!(
1596                    metric.loc,
1597                    @r###"
1598                    {
1599                      "sloc": 1.0,
1600                      "ploc": 1.0,
1601                      "lloc": 1.0,
1602                      "cloc": 0.0,
1603                      "blank": 0.0,
1604                      "sloc_average": 1.0,
1605                      "ploc_average": 1.0,
1606                      "lloc_average": 1.0,
1607                      "cloc_average": 0.0,
1608                      "blank_average": 0.0,
1609                      "sloc_min": 1.0,
1610                      "sloc_max": 1.0,
1611                      "cloc_min": 0.0,
1612                      "cloc_max": 0.0,
1613                      "ploc_min": 1.0,
1614                      "ploc_max": 1.0,
1615                      "lloc_min": 1.0,
1616                      "lloc_max": 1.0,
1617                      "blank_min": 0.0,
1618                      "blank_max": 0.0
1619                    }"###
1620                );
1621            },
1622        );
1623    }
1624
1625    #[test]
1626    fn python_blank() {
1627        check_metrics::<PythonParser>(
1628            "
1629            a = 42
1630
1631            b = 43
1632
1633            ",
1634            "foo.py",
1635            |metric| {
1636                // Spaces: 1
1637                insta::assert_json_snapshot!(
1638                    metric.loc,
1639                    @r###"
1640                    {
1641                      "sloc": 3.0,
1642                      "ploc": 2.0,
1643                      "lloc": 2.0,
1644                      "cloc": 0.0,
1645                      "blank": 1.0,
1646                      "sloc_average": 3.0,
1647                      "ploc_average": 2.0,
1648                      "lloc_average": 2.0,
1649                      "cloc_average": 0.0,
1650                      "blank_average": 1.0,
1651                      "sloc_min": 3.0,
1652                      "sloc_max": 3.0,
1653                      "cloc_min": 0.0,
1654                      "cloc_max": 0.0,
1655                      "ploc_min": 2.0,
1656                      "ploc_max": 2.0,
1657                      "lloc_min": 2.0,
1658                      "lloc_max": 2.0,
1659                      "blank_min": 1.0,
1660                      "blank_max": 1.0
1661                    }"###
1662                );
1663            },
1664        );
1665    }
1666
1667    #[test]
1668    fn rust_blank() {
1669        check_metrics::<RustParser>(
1670            "
1671
1672            let a = 42;
1673
1674            let b = 43;
1675
1676            ",
1677            "foo.rs",
1678            |metric| {
1679                // Spaces: 1
1680                insta::assert_json_snapshot!(
1681                    metric.loc,
1682                    @r###"
1683                    {
1684                      "sloc": 3.0,
1685                      "ploc": 2.0,
1686                      "lloc": 2.0,
1687                      "cloc": 0.0,
1688                      "blank": 1.0,
1689                      "sloc_average": 3.0,
1690                      "ploc_average": 2.0,
1691                      "lloc_average": 2.0,
1692                      "cloc_average": 0.0,
1693                      "blank_average": 1.0,
1694                      "sloc_min": 3.0,
1695                      "sloc_max": 3.0,
1696                      "cloc_min": 0.0,
1697                      "cloc_max": 0.0,
1698                      "ploc_min": 2.0,
1699                      "ploc_max": 2.0,
1700                      "lloc_min": 2.0,
1701                      "lloc_max": 2.0,
1702                      "blank_min": 1.0,
1703                      "blank_max": 1.0
1704                    }"###
1705                );
1706            },
1707        );
1708
1709        check_metrics::<RustParser>("fn func() { /* comment */ }", "foo.rs", |metric| {
1710            // Spaces: 2
1711            insta::assert_json_snapshot!(
1712                metric.loc,
1713                @r###"
1714                    {
1715                      "sloc": 1.0,
1716                      "ploc": 1.0,
1717                      "lloc": 0.0,
1718                      "cloc": 1.0,
1719                      "blank": 0.0,
1720                      "sloc_average": 0.5,
1721                      "ploc_average": 0.5,
1722                      "lloc_average": 0.0,
1723                      "cloc_average": 0.5,
1724                      "blank_average": 0.0,
1725                      "sloc_min": 1.0,
1726                      "sloc_max": 1.0,
1727                      "cloc_min": 1.0,
1728                      "cloc_max": 1.0,
1729                      "ploc_min": 1.0,
1730                      "ploc_max": 1.0,
1731                      "lloc_min": 0.0,
1732                      "lloc_max": 0.0,
1733                      "blank_min": 0.0,
1734                      "blank_max": 0.0
1735                    }"###
1736            );
1737        });
1738    }
1739
1740    #[test]
1741    fn c_blank() {
1742        check_metrics::<CppParser>(
1743            "
1744
1745            int a = 42;
1746
1747            int b = 43;
1748
1749            ",
1750            "foo.c",
1751            |metric| {
1752                // Spaces: 1
1753                insta::assert_json_snapshot!(
1754                    metric.loc,
1755                    @r###"
1756                    {
1757                      "sloc": 3.0,
1758                      "ploc": 2.0,
1759                      "lloc": 2.0,
1760                      "cloc": 0.0,
1761                      "blank": 1.0,
1762                      "sloc_average": 3.0,
1763                      "ploc_average": 2.0,
1764                      "lloc_average": 2.0,
1765                      "cloc_average": 0.0,
1766                      "blank_average": 1.0,
1767                      "sloc_min": 3.0,
1768                      "sloc_max": 3.0,
1769                      "cloc_min": 0.0,
1770                      "cloc_max": 0.0,
1771                      "ploc_min": 2.0,
1772                      "ploc_max": 2.0,
1773                      "lloc_min": 2.0,
1774                      "lloc_max": 2.0,
1775                      "blank_min": 1.0,
1776                      "blank_max": 1.0
1777                    }"###
1778                );
1779            },
1780        );
1781    }
1782
1783    #[test]
1784    fn python_no_zero_blank() {
1785        // Checks that the blank metric is not equal to 0 when there are some
1786        // comments next to code lines.
1787        check_metrics::<PythonParser>(
1788            "def ConnectToUpdateServer():
1789                 pool = 4
1790
1791                 updateServer = -42
1792                 isConnected = False
1793                 currTry = 0
1794                 numRetries = 10 # Number of IPC connection retries before
1795                                 # giving up.
1796                 numTries = 20 # Number of IPC connection tries before
1797                               # giving up.",
1798            "foo.py",
1799            |metric| {
1800                // Spaces: 2
1801                insta::assert_json_snapshot!(
1802                    metric.loc,
1803                    @r###"
1804                    {
1805                      "sloc": 10.0,
1806                      "ploc": 7.0,
1807                      "lloc": 6.0,
1808                      "cloc": 4.0,
1809                      "blank": 1.0,
1810                      "sloc_average": 5.0,
1811                      "ploc_average": 3.5,
1812                      "lloc_average": 3.0,
1813                      "cloc_average": 2.0,
1814                      "blank_average": 0.5,
1815                      "sloc_min": 10.0,
1816                      "sloc_max": 10.0,
1817                      "cloc_min": 4.0,
1818                      "cloc_max": 4.0,
1819                      "ploc_min": 7.0,
1820                      "ploc_max": 7.0,
1821                      "lloc_min": 6.0,
1822                      "lloc_max": 6.0,
1823                      "blank_min": 1.0,
1824                      "blank_max": 1.0
1825                    }"###
1826                );
1827            },
1828        );
1829    }
1830
1831    #[test]
1832    fn python_no_blank() {
1833        // Checks that the blank metric is equal to 0 when there are no blank
1834        // lines and there are comments next to code lines.
1835        check_metrics::<PythonParser>(
1836            "def ConnectToUpdateServer():
1837                 pool = 4
1838                 updateServer = -42
1839                 isConnected = False
1840                 currTry = 0
1841                 numRetries = 10 # Number of IPC connection retries before
1842                                 # giving up.
1843                 numTries = 20 # Number of IPC connection tries before
1844                               # giving up.",
1845            "foo.py",
1846            |metric| {
1847                // Spaces: 2
1848                insta::assert_json_snapshot!(
1849                    metric.loc,
1850                    @r###"
1851                    {
1852                      "sloc": 9.0,
1853                      "ploc": 7.0,
1854                      "lloc": 6.0,
1855                      "cloc": 4.0,
1856                      "blank": 0.0,
1857                      "sloc_average": 4.5,
1858                      "ploc_average": 3.5,
1859                      "lloc_average": 3.0,
1860                      "cloc_average": 2.0,
1861                      "blank_average": 0.0,
1862                      "sloc_min": 9.0,
1863                      "sloc_max": 9.0,
1864                      "cloc_min": 4.0,
1865                      "cloc_max": 4.0,
1866                      "ploc_min": 7.0,
1867                      "ploc_max": 7.0,
1868                      "lloc_min": 6.0,
1869                      "lloc_max": 6.0,
1870                      "blank_min": 0.0,
1871                      "blank_max": 0.0
1872                    }"###
1873                );
1874            },
1875        );
1876    }
1877
1878    #[test]
1879    fn python_no_zero_blank_more_comments() {
1880        // Checks that the blank metric is not equal to 0 when there are more
1881        // comments next to code lines compared to the previous tests.
1882        check_metrics::<PythonParser>(
1883            "def ConnectToUpdateServer():
1884                 pool = 4
1885
1886                 updateServer = -42
1887                 isConnected = False
1888                 currTry = 0 # Set this variable to 0
1889                 numRetries = 10 # Number of IPC connection retries before
1890                                 # giving up.
1891                 numTries = 20 # Number of IPC connection tries before
1892                               # giving up.",
1893            "foo.py",
1894            |metric| {
1895                // Spaces: 2
1896                insta::assert_json_snapshot!(
1897                    metric.loc,
1898                    @r###"
1899                    {
1900                      "sloc": 10.0,
1901                      "ploc": 7.0,
1902                      "lloc": 6.0,
1903                      "cloc": 5.0,
1904                      "blank": 1.0,
1905                      "sloc_average": 5.0,
1906                      "ploc_average": 3.5,
1907                      "lloc_average": 3.0,
1908                      "cloc_average": 2.5,
1909                      "blank_average": 0.5,
1910                      "sloc_min": 10.0,
1911                      "sloc_max": 10.0,
1912                      "cloc_min": 5.0,
1913                      "cloc_max": 5.0,
1914                      "ploc_min": 7.0,
1915                      "ploc_max": 7.0,
1916                      "lloc_min": 6.0,
1917                      "lloc_max": 6.0,
1918                      "blank_min": 1.0,
1919                      "blank_max": 1.0
1920                    }"###
1921                );
1922            },
1923        );
1924    }
1925
1926    #[test]
1927    fn rust_no_zero_blank() {
1928        // Checks that the blank metric is not equal to 0 when there are some
1929        // comments next to code lines.
1930        check_metrics::<RustParser>(
1931            "fn ConnectToUpdateServer() {
1932              let pool = 0;
1933
1934              let updateServer = -42;
1935              let isConnected = false;
1936              let currTry = 0;
1937              let numRetries = 10;  // Number of IPC connection retries before
1938                                    // giving up.
1939              let numTries = 20;    // Number of IPC connection tries before
1940                                    // giving up.
1941            }",
1942            "foo.rs",
1943            |metric| {
1944                // Spaces: 2
1945                insta::assert_json_snapshot!(
1946                    metric.loc,
1947                    @r###"
1948                    {
1949                      "sloc": 11.0,
1950                      "ploc": 8.0,
1951                      "lloc": 6.0,
1952                      "cloc": 4.0,
1953                      "blank": 1.0,
1954                      "sloc_average": 5.5,
1955                      "ploc_average": 4.0,
1956                      "lloc_average": 3.0,
1957                      "cloc_average": 2.0,
1958                      "blank_average": 0.5,
1959                      "sloc_min": 11.0,
1960                      "sloc_max": 11.0,
1961                      "cloc_min": 4.0,
1962                      "cloc_max": 4.0,
1963                      "ploc_min": 8.0,
1964                      "ploc_max": 8.0,
1965                      "lloc_min": 6.0,
1966                      "lloc_max": 6.0,
1967                      "blank_min": 1.0,
1968                      "blank_max": 1.0
1969                    }"###
1970                );
1971            },
1972        );
1973    }
1974
1975    #[test]
1976    fn javascript_no_zero_blank() {
1977        // Checks that the blank metric is not equal to 0 when there are some
1978        // comments next to code lines.
1979        check_metrics::<JavascriptParser>(
1980            "function ConnectToUpdateServer() {
1981              var pool = 0;
1982
1983              var updateServer = -42;
1984              var isConnected = false;
1985              var currTry = 0;
1986              var numRetries = 10;  // Number of IPC connection retries before
1987                                    // giving up.
1988              var numTries = 20;    // Number of IPC connection tries before
1989                                    // giving up.
1990            }",
1991            "foo.js",
1992            |metric| {
1993                // Spaces: 2
1994                insta::assert_json_snapshot!(
1995                    metric.loc,
1996                    @r###"
1997                    {
1998                      "sloc": 11.0,
1999                      "ploc": 8.0,
2000                      "lloc": 1.0,
2001                      "cloc": 4.0,
2002                      "blank": 1.0,
2003                      "sloc_average": 5.5,
2004                      "ploc_average": 4.0,
2005                      "lloc_average": 0.5,
2006                      "cloc_average": 2.0,
2007                      "blank_average": 0.5,
2008                      "sloc_min": 11.0,
2009                      "sloc_max": 11.0,
2010                      "cloc_min": 4.0,
2011                      "cloc_max": 4.0,
2012                      "ploc_min": 8.0,
2013                      "ploc_max": 8.0,
2014                      "lloc_min": 1.0,
2015                      "lloc_max": 1.0,
2016                      "blank_min": 1.0,
2017                      "blank_max": 1.0
2018                    }"###
2019                );
2020            },
2021        );
2022    }
2023
2024    #[test]
2025    fn cpp_no_zero_blank() {
2026        // Checks that the blank metric is not equal to 0 when there are some
2027        // comments next to code lines.
2028        check_metrics::<CppParser>(
2029            "void ConnectToUpdateServer() {
2030              int pool;
2031
2032              int updateServer = -42;
2033              bool isConnected = false;
2034              int currTry = 0;
2035              const int numRetries = 10; // Number of IPC connection retries before
2036                                         // giving up.
2037              const int numTries = 20; // Number of IPC connection tries before
2038                                       // giving up.
2039            }",
2040            "foo.cpp",
2041            |metric| {
2042                // Spaces: 2
2043                insta::assert_json_snapshot!(
2044                    metric.loc,
2045                    @r###"
2046                    {
2047                      "sloc": 11.0,
2048                      "ploc": 8.0,
2049                      "lloc": 6.0,
2050                      "cloc": 4.0,
2051                      "blank": 1.0,
2052                      "sloc_average": 5.5,
2053                      "ploc_average": 4.0,
2054                      "lloc_average": 3.0,
2055                      "cloc_average": 2.0,
2056                      "blank_average": 0.5,
2057                      "sloc_min": 11.0,
2058                      "sloc_max": 11.0,
2059                      "cloc_min": 4.0,
2060                      "cloc_max": 4.0,
2061                      "ploc_min": 8.0,
2062                      "ploc_max": 8.0,
2063                      "lloc_min": 6.0,
2064                      "lloc_max": 6.0,
2065                      "blank_min": 1.0,
2066                      "blank_max": 1.0
2067                    }"###
2068                );
2069            },
2070        );
2071    }
2072
2073    #[test]
2074    fn cpp_code_line_start_block_blank() {
2075        // Checks that the blank metric is equal to 1 when there are
2076        // block comments starting next to code lines.
2077        check_metrics::<CppParser>(
2078            "void ConnectToUpdateServer() {
2079              int pool;
2080
2081              int updateServer = -42;
2082              bool isConnected = false;
2083              int currTry = 0;
2084              const int numRetries = 10; /* Number of IPC connection retries
2085              before
2086              giving up. */
2087              const int numTries = 20; // Number of IPC connection tries before
2088                                       // giving up.
2089            }",
2090            "foo.cpp",
2091            |metric| {
2092                // Spaces: 2
2093                insta::assert_json_snapshot!(
2094                    metric.loc,
2095                    @r###"
2096                    {
2097                      "sloc": 12.0,
2098                      "ploc": 8.0,
2099                      "lloc": 6.0,
2100                      "cloc": 5.0,
2101                      "blank": 1.0,
2102                      "sloc_average": 6.0,
2103                      "ploc_average": 4.0,
2104                      "lloc_average": 3.0,
2105                      "cloc_average": 2.5,
2106                      "blank_average": 0.5,
2107                      "sloc_min": 12.0,
2108                      "sloc_max": 12.0,
2109                      "cloc_min": 5.0,
2110                      "cloc_max": 5.0,
2111                      "ploc_min": 8.0,
2112                      "ploc_max": 8.0,
2113                      "lloc_min": 6.0,
2114                      "lloc_max": 6.0,
2115                      "blank_min": 1.0,
2116                      "blank_max": 1.0
2117                    }"###
2118                );
2119            },
2120        );
2121    }
2122
2123    #[test]
2124    fn cpp_block_comment_blank() {
2125        // Checks that the blank metric is equal to 1 when there are
2126        // block comments on independent lines.
2127        check_metrics::<CppParser>(
2128            "void ConnectToUpdateServer() {
2129              int pool;
2130
2131              int updateServer = -42;
2132              bool isConnected = false;
2133              int currTry = 0;
2134              /* Number of IPC connection retries
2135              before
2136              giving up. */
2137              const int numRetries = 10;
2138              const int numTries = 20; // Number of IPC connection tries before
2139                                       // giving up.
2140            }",
2141            "foo.cpp",
2142            |metric| {
2143                // Spaces: 2
2144                insta::assert_json_snapshot!(
2145                    metric.loc,
2146                    @r###"
2147                    {
2148                      "sloc": 13.0,
2149                      "ploc": 8.0,
2150                      "lloc": 6.0,
2151                      "cloc": 5.0,
2152                      "blank": 1.0,
2153                      "sloc_average": 6.5,
2154                      "ploc_average": 4.0,
2155                      "lloc_average": 3.0,
2156                      "cloc_average": 2.5,
2157                      "blank_average": 0.5,
2158                      "sloc_min": 13.0,
2159                      "sloc_max": 13.0,
2160                      "cloc_min": 5.0,
2161                      "cloc_max": 5.0,
2162                      "ploc_min": 8.0,
2163                      "ploc_max": 8.0,
2164                      "lloc_min": 6.0,
2165                      "lloc_max": 6.0,
2166                      "blank_min": 1.0,
2167                      "blank_max": 1.0
2168                    }"###
2169                );
2170            },
2171        );
2172    }
2173
2174    #[test]
2175    fn cpp_code_line_block_one_line_blank() {
2176        // Checks that the blank metric is equal to 1 when there are
2177        // block comments before the same code line.
2178        check_metrics::<CppParser>(
2179            "void ConnectToUpdateServer() {
2180              int pool;
2181
2182              int updateServer = -42;
2183              bool isConnected = false;
2184              int currTry = 0;
2185              /* Number of IPC connection retries before giving up. */ const int numRetries = 10;
2186              const int numTries = 20; // Number of IPC connection tries before
2187                                       // giving up.
2188            }",
2189            "foo.cpp",
2190            |metric| {
2191                // Spaces: 2
2192                insta::assert_json_snapshot!(
2193                    metric.loc,
2194                    @r###"
2195                    {
2196                      "sloc": 10.0,
2197                      "ploc": 8.0,
2198                      "lloc": 6.0,
2199                      "cloc": 3.0,
2200                      "blank": 1.0,
2201                      "sloc_average": 5.0,
2202                      "ploc_average": 4.0,
2203                      "lloc_average": 3.0,
2204                      "cloc_average": 1.5,
2205                      "blank_average": 0.5,
2206                      "sloc_min": 10.0,
2207                      "sloc_max": 10.0,
2208                      "cloc_min": 3.0,
2209                      "cloc_max": 3.0,
2210                      "ploc_min": 8.0,
2211                      "ploc_max": 8.0,
2212                      "lloc_min": 6.0,
2213                      "lloc_max": 6.0,
2214                      "blank_min": 1.0,
2215                      "blank_max": 1.0
2216                    }"###
2217                );
2218            },
2219        );
2220    }
2221
2222    #[test]
2223    fn cpp_code_line_end_block_blank() {
2224        // Checks that the blank metric is equal to 1 when there are
2225        // block comments ending next to code lines.
2226        check_metrics::<CppParser>(
2227            "void ConnectToUpdateServer() {
2228              int pool;
2229
2230              int updateServer = -42;
2231              bool isConnected = false;
2232              int currTry = 0;
2233              /* Number of IPC connection retries
2234              before
2235              giving up. */ const int numRetries = 10;
2236              const int numTries = 20; // Number of IPC connection tries before
2237                                       // giving up.
2238            }",
2239            "foo.cpp",
2240            |metric| {
2241                // Spaces: 2
2242                insta::assert_json_snapshot!(
2243                    metric.loc,
2244                    @r###"
2245                    {
2246                      "sloc": 12.0,
2247                      "ploc": 8.0,
2248                      "lloc": 6.0,
2249                      "cloc": 5.0,
2250                      "blank": 1.0,
2251                      "sloc_average": 6.0,
2252                      "ploc_average": 4.0,
2253                      "lloc_average": 3.0,
2254                      "cloc_average": 2.5,
2255                      "blank_average": 0.5,
2256                      "sloc_min": 12.0,
2257                      "sloc_max": 12.0,
2258                      "cloc_min": 5.0,
2259                      "cloc_max": 5.0,
2260                      "ploc_min": 8.0,
2261                      "ploc_max": 8.0,
2262                      "lloc_min": 6.0,
2263                      "lloc_max": 6.0,
2264                      "blank_min": 1.0,
2265                      "blank_max": 1.0
2266                    }"###
2267                );
2268            },
2269        );
2270    }
2271
2272    #[test]
2273    fn python_cloc() {
2274        check_metrics::<PythonParser>(
2275            "\"\"\"Block comment
2276            Block comment
2277            \"\"\"
2278            # Line Comment
2279            a = 42 # Line Comment",
2280            "foo.py",
2281            |metric| {
2282                // Spaces: 1
2283                insta::assert_json_snapshot!(
2284                    metric.loc,
2285                    @r###"
2286                    {
2287                      "sloc": 5.0,
2288                      "ploc": 1.0,
2289                      "lloc": 2.0,
2290                      "cloc": 5.0,
2291                      "blank": 0.0,
2292                      "sloc_average": 5.0,
2293                      "ploc_average": 1.0,
2294                      "lloc_average": 2.0,
2295                      "cloc_average": 5.0,
2296                      "blank_average": 0.0,
2297                      "sloc_min": 5.0,
2298                      "sloc_max": 5.0,
2299                      "cloc_min": 5.0,
2300                      "cloc_max": 5.0,
2301                      "ploc_min": 1.0,
2302                      "ploc_max": 1.0,
2303                      "lloc_min": 2.0,
2304                      "lloc_max": 2.0,
2305                      "blank_min": 0.0,
2306                      "blank_max": 0.0
2307                    }"###
2308                );
2309            },
2310        );
2311    }
2312
2313    #[test]
2314    fn rust_cloc() {
2315        check_metrics::<RustParser>(
2316            "/*Block comment
2317            Block Comment*/
2318            //Line Comment
2319            /*Block Comment*/ let a = 42; // Line Comment",
2320            "foo.rs",
2321            |metric| {
2322                // Spaces: 1
2323                insta::assert_json_snapshot!(
2324                    metric.loc,
2325                    @r###"
2326                    {
2327                      "sloc": 4.0,
2328                      "ploc": 1.0,
2329                      "lloc": 1.0,
2330                      "cloc": 5.0,
2331                      "blank": 0.0,
2332                      "sloc_average": 4.0,
2333                      "ploc_average": 1.0,
2334                      "lloc_average": 1.0,
2335                      "cloc_average": 5.0,
2336                      "blank_average": 0.0,
2337                      "sloc_min": 4.0,
2338                      "sloc_max": 4.0,
2339                      "cloc_min": 5.0,
2340                      "cloc_max": 5.0,
2341                      "ploc_min": 1.0,
2342                      "ploc_max": 1.0,
2343                      "lloc_min": 1.0,
2344                      "lloc_max": 1.0,
2345                      "blank_min": 0.0,
2346                      "blank_max": 0.0
2347                    }"###
2348                );
2349            },
2350        );
2351    }
2352
2353    #[test]
2354    fn c_cloc() {
2355        check_metrics::<CppParser>(
2356            "/*Block comment
2357            Block Comment*/
2358            //Line Comment
2359            /*Block Comment*/ int a = 42; // Line Comment",
2360            "foo.c",
2361            |metric| {
2362                // Spaces: 1
2363                insta::assert_json_snapshot!(
2364                    metric.loc,
2365                    @r###"
2366                    {
2367                      "sloc": 4.0,
2368                      "ploc": 1.0,
2369                      "lloc": 1.0,
2370                      "cloc": 5.0,
2371                      "blank": 0.0,
2372                      "sloc_average": 4.0,
2373                      "ploc_average": 1.0,
2374                      "lloc_average": 1.0,
2375                      "cloc_average": 5.0,
2376                      "blank_average": 0.0,
2377                      "sloc_min": 4.0,
2378                      "sloc_max": 4.0,
2379                      "cloc_min": 5.0,
2380                      "cloc_max": 5.0,
2381                      "ploc_min": 1.0,
2382                      "ploc_max": 1.0,
2383                      "lloc_min": 1.0,
2384                      "lloc_max": 1.0,
2385                      "blank_min": 0.0,
2386                      "blank_max": 0.0
2387                    }"###
2388                );
2389            },
2390        );
2391    }
2392
2393    #[test]
2394    fn python_lloc() {
2395        check_metrics::<PythonParser>(
2396            "for x in range(0,42):
2397                if x % 2 == 0:
2398                    print(x)",
2399            "foo.py",
2400            |metric| {
2401                // Spaces: 1
2402                insta::assert_json_snapshot!(
2403                    metric.loc,
2404                    @r###"
2405                    {
2406                      "sloc": 3.0,
2407                      "ploc": 3.0,
2408                      "lloc": 3.0,
2409                      "cloc": 0.0,
2410                      "blank": 0.0,
2411                      "sloc_average": 3.0,
2412                      "ploc_average": 3.0,
2413                      "lloc_average": 3.0,
2414                      "cloc_average": 0.0,
2415                      "blank_average": 0.0,
2416                      "sloc_min": 3.0,
2417                      "sloc_max": 3.0,
2418                      "cloc_min": 0.0,
2419                      "cloc_max": 0.0,
2420                      "ploc_min": 3.0,
2421                      "ploc_max": 3.0,
2422                      "lloc_min": 3.0,
2423                      "lloc_max": 3.0,
2424                      "blank_min": 0.0,
2425                      "blank_max": 0.0
2426                    }"###
2427                );
2428            },
2429        );
2430    }
2431
2432    #[test]
2433    fn rust_lloc() {
2434        check_metrics::<RustParser>(
2435            "for x in 0..42 {
2436                if x % 2 == 0 {
2437                    println!(\"{}\", x);
2438                }
2439             }",
2440            "foo.rs",
2441            |metric| {
2442                // Spaces: 1
2443                insta::assert_json_snapshot!(
2444                    metric.loc,
2445                    @r###"
2446                    {
2447                      "sloc": 5.0,
2448                      "ploc": 5.0,
2449                      "lloc": 3.0,
2450                      "cloc": 0.0,
2451                      "blank": 0.0,
2452                      "sloc_average": 5.0,
2453                      "ploc_average": 5.0,
2454                      "lloc_average": 3.0,
2455                      "cloc_average": 0.0,
2456                      "blank_average": 0.0,
2457                      "sloc_min": 5.0,
2458                      "sloc_max": 5.0,
2459                      "cloc_min": 0.0,
2460                      "cloc_max": 0.0,
2461                      "ploc_min": 5.0,
2462                      "ploc_max": 5.0,
2463                      "lloc_min": 3.0,
2464                      "lloc_max": 3.0,
2465                      "blank_min": 0.0,
2466                      "blank_max": 0.0
2467                    }"###
2468                );
2469            },
2470        );
2471
2472        // LLOC returns three because there is an empty Rust statement
2473        check_metrics::<RustParser>(
2474            "let a = 42;
2475             if true {
2476                42
2477             } else {
2478                43
2479             };",
2480            "foo.rs",
2481            |metric| {
2482                // Spaces: 1
2483                insta::assert_json_snapshot!(
2484                    metric.loc,
2485                    @r###"
2486                    {
2487                      "sloc": 6.0,
2488                      "ploc": 6.0,
2489                      "lloc": 3.0,
2490                      "cloc": 0.0,
2491                      "blank": 0.0,
2492                      "sloc_average": 6.0,
2493                      "ploc_average": 6.0,
2494                      "lloc_average": 3.0,
2495                      "cloc_average": 0.0,
2496                      "blank_average": 0.0,
2497                      "sloc_min": 6.0,
2498                      "sloc_max": 6.0,
2499                      "cloc_min": 0.0,
2500                      "cloc_max": 0.0,
2501                      "ploc_min": 6.0,
2502                      "ploc_max": 6.0,
2503                      "lloc_min": 3.0,
2504                      "lloc_max": 3.0,
2505                      "blank_min": 0.0,
2506                      "blank_max": 0.0
2507                    }"###
2508                );
2509            },
2510        );
2511    }
2512
2513    #[test]
2514    fn c_lloc() {
2515        check_metrics::<CppParser>(
2516            "for (;;)
2517                break;",
2518            "foo.c",
2519            |metric| {
2520                // Spaces: 1
2521                insta::assert_json_snapshot!(
2522                    metric.loc,
2523                    @r###"
2524                    {
2525                      "sloc": 2.0,
2526                      "ploc": 2.0,
2527                      "lloc": 2.0,
2528                      "cloc": 0.0,
2529                      "blank": 0.0,
2530                      "sloc_average": 2.0,
2531                      "ploc_average": 2.0,
2532                      "lloc_average": 2.0,
2533                      "cloc_average": 0.0,
2534                      "blank_average": 0.0,
2535                      "sloc_min": 2.0,
2536                      "sloc_max": 2.0,
2537                      "cloc_min": 0.0,
2538                      "cloc_max": 0.0,
2539                      "ploc_min": 2.0,
2540                      "ploc_max": 2.0,
2541                      "lloc_min": 2.0,
2542                      "lloc_max": 2.0,
2543                      "blank_min": 0.0,
2544                      "blank_max": 0.0
2545                    }"###
2546                );
2547            },
2548        );
2549    }
2550
2551    #[test]
2552    fn cpp_lloc() {
2553        check_metrics::<CppParser>(
2554            "nsTArray<xpcGCCallback> callbacks(extraGCCallbacks.Clone());
2555             for (uint32_t i = 0; i < callbacks.Length(); ++i) {
2556                 callbacks[i](status);
2557             }",
2558            "foo.cpp",
2559            |metric| {
2560                // Spaces: 1
2561                // lloc: nsTArray, for, callbacks
2562                insta::assert_json_snapshot!(
2563                    metric.loc,
2564                    @r###"
2565                    {
2566                      "sloc": 4.0,
2567                      "ploc": 4.0,
2568                      "lloc": 3.0,
2569                      "cloc": 0.0,
2570                      "blank": 0.0,
2571                      "sloc_average": 4.0,
2572                      "ploc_average": 4.0,
2573                      "lloc_average": 3.0,
2574                      "cloc_average": 0.0,
2575                      "blank_average": 0.0,
2576                      "sloc_min": 4.0,
2577                      "sloc_max": 4.0,
2578                      "cloc_min": 0.0,
2579                      "cloc_max": 0.0,
2580                      "ploc_min": 4.0,
2581                      "ploc_max": 4.0,
2582                      "lloc_min": 3.0,
2583                      "lloc_max": 3.0,
2584                      "blank_min": 0.0,
2585                      "blank_max": 0.0
2586                    }"###
2587                );
2588            },
2589        );
2590    }
2591
2592    #[test]
2593    fn cpp_return_lloc() {
2594        check_metrics::<CppParser>(
2595            "uint8_t* pixel_data = frame.GetFrameDataAtPos(DesktopVector(x, y));
2596             return RgbaColor(pixel_data) == blank_pixel_;",
2597            "foo.cpp",
2598            |metric| {
2599                // Spaces: 1
2600                // lloc: pixel_data, return
2601                insta::assert_json_snapshot!(
2602                    metric.loc,
2603                    @r###"
2604                    {
2605                      "sloc": 2.0,
2606                      "ploc": 2.0,
2607                      "lloc": 2.0,
2608                      "cloc": 0.0,
2609                      "blank": 0.0,
2610                      "sloc_average": 2.0,
2611                      "ploc_average": 2.0,
2612                      "lloc_average": 2.0,
2613                      "cloc_average": 0.0,
2614                      "blank_average": 0.0,
2615                      "sloc_min": 2.0,
2616                      "sloc_max": 2.0,
2617                      "cloc_min": 0.0,
2618                      "cloc_max": 0.0,
2619                      "ploc_min": 2.0,
2620                      "ploc_max": 2.0,
2621                      "lloc_min": 2.0,
2622                      "lloc_max": 2.0,
2623                      "blank_min": 0.0,
2624                      "blank_max": 0.0
2625                    }"###
2626                );
2627            },
2628        );
2629    }
2630
2631    #[test]
2632    fn cpp_for_lloc() {
2633        check_metrics::<CppParser>(
2634            "for (; start != end; ++start) {
2635                 const unsigned char idx = *start;
2636                 if (idx > 127 || !kValidTokenMap[idx]) return false;
2637             }",
2638            "foo.cpp",
2639            |metric| {
2640                // Spaces: 1
2641                // lloc: for, idx, if, return
2642                insta::assert_json_snapshot!(
2643                    metric.loc,
2644                    @r###"
2645                    {
2646                      "sloc": 4.0,
2647                      "ploc": 4.0,
2648                      "lloc": 4.0,
2649                      "cloc": 0.0,
2650                      "blank": 0.0,
2651                      "sloc_average": 4.0,
2652                      "ploc_average": 4.0,
2653                      "lloc_average": 4.0,
2654                      "cloc_average": 0.0,
2655                      "blank_average": 0.0,
2656                      "sloc_min": 4.0,
2657                      "sloc_max": 4.0,
2658                      "cloc_min": 0.0,
2659                      "cloc_max": 0.0,
2660                      "ploc_min": 4.0,
2661                      "ploc_max": 4.0,
2662                      "lloc_min": 4.0,
2663                      "lloc_max": 4.0,
2664                      "blank_min": 0.0,
2665                      "blank_max": 0.0
2666                    }"###
2667                );
2668            },
2669        );
2670    }
2671
2672    #[test]
2673    fn cpp_while_lloc() {
2674        check_metrics::<CppParser>(
2675            "while (sHeapAtoms) {
2676                 HttpHeapAtom* next = sHeapAtoms->next;
2677                 free(sHeapAtoms);
2678            }",
2679            "foo.cpp",
2680            |metric| {
2681                // Spaces: 1
2682                // lloc: while, next, free
2683                insta::assert_json_snapshot!(
2684                    metric.loc,
2685                    @r###"
2686                    {
2687                      "sloc": 4.0,
2688                      "ploc": 4.0,
2689                      "lloc": 3.0,
2690                      "cloc": 0.0,
2691                      "blank": 0.0,
2692                      "sloc_average": 4.0,
2693                      "ploc_average": 4.0,
2694                      "lloc_average": 3.0,
2695                      "cloc_average": 0.0,
2696                      "blank_average": 0.0,
2697                      "sloc_min": 4.0,
2698                      "sloc_max": 4.0,
2699                      "cloc_min": 0.0,
2700                      "cloc_max": 0.0,
2701                      "ploc_min": 4.0,
2702                      "ploc_max": 4.0,
2703                      "lloc_min": 3.0,
2704                      "lloc_max": 3.0,
2705                      "blank_min": 0.0,
2706                      "blank_max": 0.0
2707                    }"###
2708                );
2709            },
2710        );
2711    }
2712
2713    #[test]
2714    fn python_string_on_new_line() {
2715        // More lines of the same instruction were counted as blank lines
2716        check_metrics::<PythonParser>(
2717            "capabilities[\"goog:chromeOptions\"][\"androidPackage\"] = \\
2718                \"org.chromium.weblayer.shell\"",
2719            "foo.py",
2720            |metric| {
2721                // Spaces: 1
2722                insta::assert_json_snapshot!(
2723                    metric.loc,
2724                    @r###"
2725                    {
2726                      "sloc": 2.0,
2727                      "ploc": 2.0,
2728                      "lloc": 1.0,
2729                      "cloc": 0.0,
2730                      "blank": 0.0,
2731                      "sloc_average": 2.0,
2732                      "ploc_average": 2.0,
2733                      "lloc_average": 1.0,
2734                      "cloc_average": 0.0,
2735                      "blank_average": 0.0,
2736                      "sloc_min": 2.0,
2737                      "sloc_max": 2.0,
2738                      "cloc_min": 0.0,
2739                      "cloc_max": 0.0,
2740                      "ploc_min": 2.0,
2741                      "ploc_max": 2.0,
2742                      "lloc_min": 1.0,
2743                      "lloc_max": 1.0,
2744                      "blank_min": 0.0,
2745                      "blank_max": 0.0
2746                    }"###
2747                );
2748            },
2749        );
2750    }
2751
2752    #[test]
2753    fn rust_no_field_expression_lloc() {
2754        check_metrics::<RustParser>(
2755            "struct Foo {
2756                field: usize,
2757             }
2758             let foo = Foo { 42 };
2759             foo.field;",
2760            "foo.rs",
2761            |metric| {
2762                // Spaces: 1
2763                insta::assert_json_snapshot!(
2764                    metric.loc,
2765                    @r###"
2766                    {
2767                      "sloc": 5.0,
2768                      "ploc": 5.0,
2769                      "lloc": 2.0,
2770                      "cloc": 0.0,
2771                      "blank": 0.0,
2772                      "sloc_average": 5.0,
2773                      "ploc_average": 5.0,
2774                      "lloc_average": 2.0,
2775                      "cloc_average": 0.0,
2776                      "blank_average": 0.0,
2777                      "sloc_min": 5.0,
2778                      "sloc_max": 5.0,
2779                      "cloc_min": 0.0,
2780                      "cloc_max": 0.0,
2781                      "ploc_min": 5.0,
2782                      "ploc_max": 5.0,
2783                      "lloc_min": 2.0,
2784                      "lloc_max": 2.0,
2785                      "blank_min": 0.0,
2786                      "blank_max": 0.0
2787                    }"###
2788                );
2789            },
2790        );
2791    }
2792
2793    #[test]
2794    fn rust_no_parenthesized_expression_lloc() {
2795        check_metrics::<RustParser>("let a = (42 + 0);", "foo.rs", |metric| {
2796            // Spaces: 1
2797            insta::assert_json_snapshot!(
2798                metric.loc,
2799                @r###"
2800                    {
2801                      "sloc": 1.0,
2802                      "ploc": 1.0,
2803                      "lloc": 1.0,
2804                      "cloc": 0.0,
2805                      "blank": 0.0,
2806                      "sloc_average": 1.0,
2807                      "ploc_average": 1.0,
2808                      "lloc_average": 1.0,
2809                      "cloc_average": 0.0,
2810                      "blank_average": 0.0,
2811                      "sloc_min": 1.0,
2812                      "sloc_max": 1.0,
2813                      "cloc_min": 0.0,
2814                      "cloc_max": 0.0,
2815                      "ploc_min": 1.0,
2816                      "ploc_max": 1.0,
2817                      "lloc_min": 1.0,
2818                      "lloc_max": 1.0,
2819                      "blank_min": 0.0,
2820                      "blank_max": 0.0
2821                    }"###
2822            );
2823        });
2824    }
2825
2826    #[test]
2827    fn rust_no_array_expression_lloc() {
2828        check_metrics::<RustParser>("let a = [0; 42];", "foo.rs", |metric| {
2829            // Spaces: 1
2830            insta::assert_json_snapshot!(
2831                metric.loc,
2832                @r###"
2833                    {
2834                      "sloc": 1.0,
2835                      "ploc": 1.0,
2836                      "lloc": 1.0,
2837                      "cloc": 0.0,
2838                      "blank": 0.0,
2839                      "sloc_average": 1.0,
2840                      "ploc_average": 1.0,
2841                      "lloc_average": 1.0,
2842                      "cloc_average": 0.0,
2843                      "blank_average": 0.0,
2844                      "sloc_min": 1.0,
2845                      "sloc_max": 1.0,
2846                      "cloc_min": 0.0,
2847                      "cloc_max": 0.0,
2848                      "ploc_min": 1.0,
2849                      "ploc_max": 1.0,
2850                      "lloc_min": 1.0,
2851                      "lloc_max": 1.0,
2852                      "blank_min": 0.0,
2853                      "blank_max": 0.0
2854                    }"###
2855            );
2856        });
2857    }
2858
2859    #[test]
2860    fn rust_no_tuple_expression_lloc() {
2861        check_metrics::<RustParser>("let a = (0, 42);", "foo.rs", |metric| {
2862            // Spaces: 1
2863            insta::assert_json_snapshot!(
2864                metric.loc,
2865                @r###"
2866                    {
2867                      "sloc": 1.0,
2868                      "ploc": 1.0,
2869                      "lloc": 1.0,
2870                      "cloc": 0.0,
2871                      "blank": 0.0,
2872                      "sloc_average": 1.0,
2873                      "ploc_average": 1.0,
2874                      "lloc_average": 1.0,
2875                      "cloc_average": 0.0,
2876                      "blank_average": 0.0,
2877                      "sloc_min": 1.0,
2878                      "sloc_max": 1.0,
2879                      "cloc_min": 0.0,
2880                      "cloc_max": 0.0,
2881                      "ploc_min": 1.0,
2882                      "ploc_max": 1.0,
2883                      "lloc_min": 1.0,
2884                      "lloc_max": 1.0,
2885                      "blank_min": 0.0,
2886                      "blank_max": 0.0
2887                    }"###
2888            );
2889        });
2890    }
2891
2892    #[test]
2893    fn rust_no_unit_expression_lloc() {
2894        check_metrics::<RustParser>("let a = ();", "foo.rs", |metric| {
2895            // Spaces: 1
2896            insta::assert_json_snapshot!(
2897                metric.loc,
2898                @r###"
2899                    {
2900                      "sloc": 1.0,
2901                      "ploc": 1.0,
2902                      "lloc": 1.0,
2903                      "cloc": 0.0,
2904                      "blank": 0.0,
2905                      "sloc_average": 1.0,
2906                      "ploc_average": 1.0,
2907                      "lloc_average": 1.0,
2908                      "cloc_average": 0.0,
2909                      "blank_average": 0.0,
2910                      "sloc_min": 1.0,
2911                      "sloc_max": 1.0,
2912                      "cloc_min": 0.0,
2913                      "cloc_max": 0.0,
2914                      "ploc_min": 1.0,
2915                      "ploc_max": 1.0,
2916                      "lloc_min": 1.0,
2917                      "lloc_max": 1.0,
2918                      "blank_min": 0.0,
2919                      "blank_max": 0.0
2920                    }"###
2921            );
2922        });
2923    }
2924
2925    #[test]
2926    fn rust_call_function_lloc() {
2927        check_metrics::<RustParser>(
2928            "let a = foo(); // +1
2929             foo(); // +1
2930             k!(foo()); // +1",
2931            "foo.rs",
2932            |metric| {
2933                // Spaces: 1
2934                insta::assert_json_snapshot!(
2935                    metric.loc,
2936                    @r###"
2937                    {
2938                      "sloc": 3.0,
2939                      "ploc": 3.0,
2940                      "lloc": 3.0,
2941                      "cloc": 3.0,
2942                      "blank": 0.0,
2943                      "sloc_average": 3.0,
2944                      "ploc_average": 3.0,
2945                      "lloc_average": 3.0,
2946                      "cloc_average": 3.0,
2947                      "blank_average": 0.0,
2948                      "sloc_min": 3.0,
2949                      "sloc_max": 3.0,
2950                      "cloc_min": 3.0,
2951                      "cloc_max": 3.0,
2952                      "ploc_min": 3.0,
2953                      "ploc_max": 3.0,
2954                      "lloc_min": 3.0,
2955                      "lloc_max": 3.0,
2956                      "blank_min": 0.0,
2957                      "blank_max": 0.0
2958                    }"###
2959                );
2960            },
2961        );
2962    }
2963
2964    #[test]
2965    fn rust_macro_invocation_lloc() {
2966        check_metrics::<RustParser>(
2967            "let a = foo!(); // +1
2968             foo!(); // +1
2969             k(foo!()); // +1",
2970            "foo.rs",
2971            |metric| {
2972                // Spaces: 1
2973                insta::assert_json_snapshot!(
2974                    metric.loc,
2975                    @r###"
2976                    {
2977                      "sloc": 3.0,
2978                      "ploc": 3.0,
2979                      "lloc": 3.0,
2980                      "cloc": 3.0,
2981                      "blank": 0.0,
2982                      "sloc_average": 3.0,
2983                      "ploc_average": 3.0,
2984                      "lloc_average": 3.0,
2985                      "cloc_average": 3.0,
2986                      "blank_average": 0.0,
2987                      "sloc_min": 3.0,
2988                      "sloc_max": 3.0,
2989                      "cloc_min": 3.0,
2990                      "cloc_max": 3.0,
2991                      "ploc_min": 3.0,
2992                      "ploc_max": 3.0,
2993                      "lloc_min": 3.0,
2994                      "lloc_max": 3.0,
2995                      "blank_min": 0.0,
2996                      "blank_max": 0.0
2997                    }"###
2998                );
2999            },
3000        );
3001    }
3002
3003    #[test]
3004    fn rust_function_in_loop_lloc() {
3005        check_metrics::<RustParser>(
3006            "for (a, b) in c.iter().enumerate() {} // +1
3007             while (a, b) in c.iter().enumerate() {} // +1
3008             while let Some(a) = c.strip_prefix(\"hi\") {} // +1",
3009            "foo.rs",
3010            |metric| {
3011                // Spaces: 1
3012                insta::assert_json_snapshot!(
3013                    metric.loc,
3014                    @r###"
3015                    {
3016                      "sloc": 3.0,
3017                      "ploc": 3.0,
3018                      "lloc": 3.0,
3019                      "cloc": 3.0,
3020                      "blank": 0.0,
3021                      "sloc_average": 3.0,
3022                      "ploc_average": 3.0,
3023                      "lloc_average": 3.0,
3024                      "cloc_average": 3.0,
3025                      "blank_average": 0.0,
3026                      "sloc_min": 3.0,
3027                      "sloc_max": 3.0,
3028                      "cloc_min": 3.0,
3029                      "cloc_max": 3.0,
3030                      "ploc_min": 3.0,
3031                      "ploc_max": 3.0,
3032                      "lloc_min": 3.0,
3033                      "lloc_max": 3.0,
3034                      "blank_min": 0.0,
3035                      "blank_max": 0.0
3036                    }"###
3037                );
3038            },
3039        );
3040    }
3041
3042    #[test]
3043    fn rust_function_in_if_lloc() {
3044        check_metrics::<RustParser>(
3045            "if foo() {} // +1
3046             if let Some(a) = foo() {} // +1",
3047            "foo.rs",
3048            |metric| {
3049                // Spaces: 1
3050                insta::assert_json_snapshot!(
3051                    metric.loc,
3052                    @r###"
3053                    {
3054                      "sloc": 2.0,
3055                      "ploc": 2.0,
3056                      "lloc": 2.0,
3057                      "cloc": 2.0,
3058                      "blank": 0.0,
3059                      "sloc_average": 2.0,
3060                      "ploc_average": 2.0,
3061                      "lloc_average": 2.0,
3062                      "cloc_average": 2.0,
3063                      "blank_average": 0.0,
3064                      "sloc_min": 2.0,
3065                      "sloc_max": 2.0,
3066                      "cloc_min": 2.0,
3067                      "cloc_max": 2.0,
3068                      "ploc_min": 2.0,
3069                      "ploc_max": 2.0,
3070                      "lloc_min": 2.0,
3071                      "lloc_max": 2.0,
3072                      "blank_min": 0.0,
3073                      "blank_max": 0.0
3074                    }"###
3075                );
3076            },
3077        );
3078    }
3079
3080    #[test]
3081    fn rust_function_in_return_lloc() {
3082        check_metrics::<RustParser>(
3083            "return foo();
3084             await foo();",
3085            "foo.rs",
3086            |metric| {
3087                // Spaces: 1
3088                insta::assert_json_snapshot!(
3089                    metric.loc,
3090                    @r###"
3091                    {
3092                      "sloc": 2.0,
3093                      "ploc": 2.0,
3094                      "lloc": 2.0,
3095                      "cloc": 0.0,
3096                      "blank": 0.0,
3097                      "sloc_average": 2.0,
3098                      "ploc_average": 2.0,
3099                      "lloc_average": 2.0,
3100                      "cloc_average": 0.0,
3101                      "blank_average": 0.0,
3102                      "sloc_min": 2.0,
3103                      "sloc_max": 2.0,
3104                      "cloc_min": 0.0,
3105                      "cloc_max": 0.0,
3106                      "ploc_min": 2.0,
3107                      "ploc_max": 2.0,
3108                      "lloc_min": 2.0,
3109                      "lloc_max": 2.0,
3110                      "blank_min": 0.0,
3111                      "blank_max": 0.0
3112                    }"###
3113                );
3114            },
3115        );
3116    }
3117
3118    #[test]
3119    fn rust_closure_expression_lloc() {
3120        check_metrics::<RustParser>(
3121            "let a = |i: i32| -> i32 { i + 1 }; // +1
3122             a(42); // +1
3123             k(b.iter().map(|n| n.parse.ok().unwrap_or(42))); // +1",
3124            "foo.rs",
3125            |metric| {
3126                // Spaces: 3
3127                insta::assert_json_snapshot!(
3128                    metric.loc,
3129                    @r###"
3130                    {
3131                      "sloc": 3.0,
3132                      "ploc": 3.0,
3133                      "lloc": 3.0,
3134                      "cloc": 3.0,
3135                      "blank": 0.0,
3136                      "sloc_average": 1.0,
3137                      "ploc_average": 1.0,
3138                      "lloc_average": 1.0,
3139                      "cloc_average": 1.0,
3140                      "blank_average": 0.0,
3141                      "sloc_min": 1.0,
3142                      "sloc_max": 1.0,
3143                      "cloc_min": 0.0,
3144                      "cloc_max": 0.0,
3145                      "ploc_min": 1.0,
3146                      "ploc_max": 1.0,
3147                      "lloc_min": 0.0,
3148                      "lloc_max": 0.0,
3149                      "blank_min": 0.0,
3150                      "blank_max": 0.0
3151                    }"###
3152                );
3153            },
3154        );
3155    }
3156
3157    #[test]
3158    fn python_general_loc() {
3159        check_metrics::<PythonParser>(
3160            "def func(a,
3161                      b,
3162                      c):
3163                 print(a)
3164                 print(b)
3165                 print(c)",
3166            "foo.py",
3167            |metric| {
3168                // Spaces: 2
3169                insta::assert_json_snapshot!(
3170                    metric.loc,
3171                    @r###"
3172                    {
3173                      "sloc": 6.0,
3174                      "ploc": 6.0,
3175                      "lloc": 3.0,
3176                      "cloc": 0.0,
3177                      "blank": 0.0,
3178                      "sloc_average": 3.0,
3179                      "ploc_average": 3.0,
3180                      "lloc_average": 1.5,
3181                      "cloc_average": 0.0,
3182                      "blank_average": 0.0,
3183                      "sloc_min": 6.0,
3184                      "sloc_max": 6.0,
3185                      "cloc_min": 0.0,
3186                      "cloc_max": 0.0,
3187                      "ploc_min": 6.0,
3188                      "ploc_max": 6.0,
3189                      "lloc_min": 3.0,
3190                      "lloc_max": 3.0,
3191                      "blank_min": 0.0,
3192                      "blank_max": 0.0
3193                    }"###
3194                );
3195            },
3196        );
3197    }
3198
3199    #[test]
3200    fn python_real_loc() {
3201        check_metrics::<PythonParser>(
3202            "def web_socket_transfer_data(request):
3203                while True:
3204                    line = request.ws_stream.receive_message()
3205                    if line is None:
3206                        return
3207                    code, reason = line.split(' ', 1)
3208                    if code is None or reason is None:
3209                        return
3210                    request.ws_stream.close_connection(int(code), reason)
3211                    # close_connection() initiates closing handshake. It validates code
3212                    # and reason. If you want to send a broken close frame for a test,
3213                    # following code will be useful.
3214                    # > data = struct.pack('!H', int(code)) + reason.encode('UTF-8')
3215                    # > request.connection.write(stream.create_close_frame(data))
3216                    # > # Suppress to re-respond client responding close frame.
3217                    # > raise Exception(\"customized server initiated closing handshake\")",
3218            "foo.py",
3219            |metric| {
3220                // Spaces: 2
3221                insta::assert_json_snapshot!(
3222                    metric.loc,
3223                    @r###"
3224                    {
3225                      "sloc": 16.0,
3226                      "ploc": 9.0,
3227                      "lloc": 8.0,
3228                      "cloc": 7.0,
3229                      "blank": 0.0,
3230                      "sloc_average": 8.0,
3231                      "ploc_average": 4.5,
3232                      "lloc_average": 4.0,
3233                      "cloc_average": 3.5,
3234                      "blank_average": 0.0,
3235                      "sloc_min": 16.0,
3236                      "sloc_max": 16.0,
3237                      "cloc_min": 7.0,
3238                      "cloc_max": 7.0,
3239                      "ploc_min": 9.0,
3240                      "ploc_max": 9.0,
3241                      "lloc_min": 8.0,
3242                      "lloc_max": 8.0,
3243                      "blank_min": 0.0,
3244                      "blank_max": 0.0
3245                    }"###
3246                );
3247            },
3248        );
3249    }
3250
3251    #[test]
3252    fn javascript_real_loc() {
3253        check_metrics::<JavascriptParser>(
3254            "assert.throws(Test262Error, function() {
3255               for (let { poisoned: x = ++initEvalCount } = poisonedProperty; ; ) {
3256                 return;
3257               }
3258             });",
3259            "foo.js",
3260            |metric| {
3261                // Spaces: 2
3262                insta::assert_json_snapshot!(
3263                    metric.loc,
3264                    @r###"
3265                    {
3266                      "sloc": 5.0,
3267                      "ploc": 5.0,
3268                      "lloc": 6.0,
3269                      "cloc": 0.0,
3270                      "blank": 0.0,
3271                      "sloc_average": 2.5,
3272                      "ploc_average": 2.5,
3273                      "lloc_average": 3.0,
3274                      "cloc_average": 0.0,
3275                      "blank_average": 0.0,
3276                      "sloc_min": 5.0,
3277                      "sloc_max": 5.0,
3278                      "cloc_min": 0.0,
3279                      "cloc_max": 0.0,
3280                      "ploc_min": 5.0,
3281                      "ploc_max": 5.0,
3282                      "lloc_min": 5.0,
3283                      "lloc_max": 5.0,
3284                      "blank_min": 0.0,
3285                      "blank_max": 0.0
3286                    }"###
3287                );
3288            },
3289        );
3290    }
3291
3292    #[test]
3293    fn mozjs_real_loc() {
3294        check_metrics::<MozjsParser>(
3295            "assert.throws(Test262Error, function() {
3296               for (let { poisoned: x = ++initEvalCount } = poisonedProperty; ; ) {
3297                 return;
3298               }
3299             });",
3300            "foo.js",
3301            |metric| {
3302                // Spaces: 2
3303                insta::assert_json_snapshot!(
3304                    metric.loc,
3305                    @r###"
3306                    {
3307                      "sloc": 5.0,
3308                      "ploc": 5.0,
3309                      "lloc": 6.0,
3310                      "cloc": 0.0,
3311                      "blank": 0.0,
3312                      "sloc_average": 2.5,
3313                      "ploc_average": 2.5,
3314                      "lloc_average": 3.0,
3315                      "cloc_average": 0.0,
3316                      "blank_average": 0.0,
3317                      "sloc_min": 5.0,
3318                      "sloc_max": 5.0,
3319                      "cloc_min": 0.0,
3320                      "cloc_max": 0.0,
3321                      "ploc_min": 5.0,
3322                      "ploc_max": 5.0,
3323                      "lloc_min": 5.0,
3324                      "lloc_max": 5.0,
3325                      "blank_min": 0.0,
3326                      "blank_max": 0.0
3327                    }"###
3328                );
3329            },
3330        );
3331    }
3332
3333    #[test]
3334    fn mozjs_blank_and_comment_loc() {
3335        check_metrics::<MozjsParser>(
3336            "// a comment
3337             function f() {
3338
3339                 var x = 1;
3340
3341             }",
3342            "foo.js",
3343            |metric| {
3344                insta::assert_json_snapshot!(
3345                    metric.loc,
3346                    @r###"
3347                    {
3348                      "sloc": 6.0,
3349                      "ploc": 3.0,
3350                      "lloc": 1.0,
3351                      "cloc": 1.0,
3352                      "blank": 2.0,
3353                      "sloc_average": 3.0,
3354                      "ploc_average": 1.5,
3355                      "lloc_average": 0.5,
3356                      "cloc_average": 0.5,
3357                      "blank_average": 1.0,
3358                      "sloc_min": 5.0,
3359                      "sloc_max": 5.0,
3360                      "cloc_min": 0.0,
3361                      "cloc_max": 0.0,
3362                      "ploc_min": 3.0,
3363                      "ploc_max": 3.0,
3364                      "lloc_min": 1.0,
3365                      "lloc_max": 1.0,
3366                      "blank_min": 2.0,
3367                      "blank_max": 2.0
3368                    }"###
3369                );
3370            },
3371        );
3372    }
3373
3374    #[test]
3375    fn cpp_namespace_loc() {
3376        check_metrics::<CppParser>(
3377            "namespace mozilla::dom::quota {} // namespace mozilla::dom::quota",
3378            "foo.cpp",
3379            |metric| {
3380                // Spaces: 2
3381                insta::assert_json_snapshot!(
3382                    metric.loc,
3383                    @r###"
3384                    {
3385                      "sloc": 1.0,
3386                      "ploc": 1.0,
3387                      "lloc": 0.0,
3388                      "cloc": 1.0,
3389                      "blank": 0.0,
3390                      "sloc_average": 0.5,
3391                      "ploc_average": 0.5,
3392                      "lloc_average": 0.0,
3393                      "cloc_average": 0.5,
3394                      "blank_average": 0.0,
3395                      "sloc_min": 1.0,
3396                      "sloc_max": 1.0,
3397                      "cloc_min": 0.0,
3398                      "cloc_max": 0.0,
3399                      "ploc_min": 1.0,
3400                      "ploc_max": 1.0,
3401                      "lloc_min": 0.0,
3402                      "lloc_max": 0.0,
3403                      "blank_min": 0.0,
3404                      "blank_max": 0.0
3405                    }"###
3406                );
3407            },
3408        );
3409    }
3410
3411    #[test]
3412    fn java_comments() {
3413        check_metrics::<JavaParser>(
3414            "for (int i = 0; i < 100; i++) { \
3415               // Print hello
3416               System.out.println(\"hello\"); \
3417               // Print world
3418               System.out.println(\"hello\"); \
3419             }",
3420            "foo.java",
3421            |metric| {
3422                // Spaces: 1
3423                insta::assert_json_snapshot!(
3424                    metric.loc,
3425                    @r###"
3426                    {
3427                      "sloc": 3.0,
3428                      "ploc": 3.0,
3429                      "lloc": 3.0,
3430                      "cloc": 2.0,
3431                      "blank": 0.0,
3432                      "sloc_average": 3.0,
3433                      "ploc_average": 3.0,
3434                      "lloc_average": 3.0,
3435                      "cloc_average": 2.0,
3436                      "blank_average": 0.0,
3437                      "sloc_min": 3.0,
3438                      "sloc_max": 3.0,
3439                      "cloc_min": 2.0,
3440                      "cloc_max": 2.0,
3441                      "ploc_min": 3.0,
3442                      "ploc_max": 3.0,
3443                      "lloc_min": 3.0,
3444                      "lloc_max": 3.0,
3445                      "blank_min": 0.0,
3446                      "blank_max": 0.0
3447                    }"###
3448                );
3449            },
3450        );
3451    }
3452
3453    #[test]
3454    fn java_blank() {
3455        check_metrics::<JavaParser>(
3456            "int x = 1;
3457
3458
3459            int y = 2;",
3460            "foo.java",
3461            |metric| {
3462                // Spaces: 1
3463                insta::assert_json_snapshot!(
3464                    metric.loc,
3465                    @r###"
3466                    {
3467                      "sloc": 4.0,
3468                      "ploc": 2.0,
3469                      "lloc": 2.0,
3470                      "cloc": 0.0,
3471                      "blank": 2.0,
3472                      "sloc_average": 4.0,
3473                      "ploc_average": 2.0,
3474                      "lloc_average": 2.0,
3475                      "cloc_average": 0.0,
3476                      "blank_average": 2.0,
3477                      "sloc_min": 4.0,
3478                      "sloc_max": 4.0,
3479                      "cloc_min": 0.0,
3480                      "cloc_max": 0.0,
3481                      "ploc_min": 2.0,
3482                      "ploc_max": 2.0,
3483                      "lloc_min": 2.0,
3484                      "lloc_max": 2.0,
3485                      "blank_min": 2.0,
3486                      "blank_max": 2.0
3487                    }"###
3488                );
3489            },
3490        );
3491    }
3492
3493    #[test]
3494    fn java_sloc() {
3495        check_metrics::<JavaParser>(
3496            "for (int i = 0; i < 100; i++) {
3497               System.out.println(i);
3498             }",
3499            "foo.java",
3500            |metric| {
3501                // Spaces: 1
3502                insta::assert_json_snapshot!(
3503                    metric.loc,
3504                    @r###"
3505                    {
3506                      "sloc": 3.0,
3507                      "ploc": 3.0,
3508                      "lloc": 2.0,
3509                      "cloc": 0.0,
3510                      "blank": 0.0,
3511                      "sloc_average": 3.0,
3512                      "ploc_average": 3.0,
3513                      "lloc_average": 2.0,
3514                      "cloc_average": 0.0,
3515                      "blank_average": 0.0,
3516                      "sloc_min": 3.0,
3517                      "sloc_max": 3.0,
3518                      "cloc_min": 0.0,
3519                      "cloc_max": 0.0,
3520                      "ploc_min": 3.0,
3521                      "ploc_max": 3.0,
3522                      "lloc_min": 2.0,
3523                      "lloc_max": 2.0,
3524                      "blank_min": 0.0,
3525                      "blank_max": 0.0
3526                    }"###
3527                );
3528            },
3529        );
3530    }
3531
3532    #[test]
3533    fn java_module_sloc() {
3534        check_metrics::<JavaParser>(
3535            "module helloworld{
3536              exports com.test;
3537            }",
3538            "foo.java",
3539            |metric| {
3540                // Spaces: 1
3541                insta::assert_json_snapshot!(
3542                    metric.loc,
3543                    @r###"
3544                    {
3545                      "sloc": 3.0,
3546                      "ploc": 3.0,
3547                      "lloc": 0.0,
3548                      "cloc": 0.0,
3549                      "blank": 0.0,
3550                      "sloc_average": 3.0,
3551                      "ploc_average": 3.0,
3552                      "lloc_average": 0.0,
3553                      "cloc_average": 0.0,
3554                      "blank_average": 0.0,
3555                      "sloc_min": 3.0,
3556                      "sloc_max": 3.0,
3557                      "cloc_min": 0.0,
3558                      "cloc_max": 0.0,
3559                      "ploc_min": 3.0,
3560                      "ploc_max": 3.0,
3561                      "lloc_min": 0.0,
3562                      "lloc_max": 0.0,
3563                      "blank_min": 0.0,
3564                      "blank_max": 0.0
3565                    }"###
3566                );
3567            },
3568        );
3569    }
3570
3571    #[test]
3572    fn java_single_ploc() {
3573        check_metrics::<JavaParser>("int x = 1;", "foo.java", |metric| {
3574            // Spaces: 1
3575            insta::assert_json_snapshot!(
3576                metric.loc,
3577                @r###"
3578                    {
3579                      "sloc": 1.0,
3580                      "ploc": 1.0,
3581                      "lloc": 1.0,
3582                      "cloc": 0.0,
3583                      "blank": 0.0,
3584                      "sloc_average": 1.0,
3585                      "ploc_average": 1.0,
3586                      "lloc_average": 1.0,
3587                      "cloc_average": 0.0,
3588                      "blank_average": 0.0,
3589                      "sloc_min": 1.0,
3590                      "sloc_max": 1.0,
3591                      "cloc_min": 0.0,
3592                      "cloc_max": 0.0,
3593                      "ploc_min": 1.0,
3594                      "ploc_max": 1.0,
3595                      "lloc_min": 1.0,
3596                      "lloc_max": 1.0,
3597                      "blank_min": 0.0,
3598                      "blank_max": 0.0
3599                    }"###
3600            );
3601        });
3602    }
3603
3604    #[test]
3605    fn java_simple_ploc() {
3606        check_metrics::<JavaParser>(
3607            "for (int i = 0; i < 100; i = i++) {
3608               System.out.println(i);
3609             }",
3610            "foo.java",
3611            |metric| {
3612                // Spaces: 1
3613                insta::assert_json_snapshot!(
3614                    metric.loc,
3615                    @r###"
3616                    {
3617                      "sloc": 3.0,
3618                      "ploc": 3.0,
3619                      "lloc": 2.0,
3620                      "cloc": 0.0,
3621                      "blank": 0.0,
3622                      "sloc_average": 3.0,
3623                      "ploc_average": 3.0,
3624                      "lloc_average": 2.0,
3625                      "cloc_average": 0.0,
3626                      "blank_average": 0.0,
3627                      "sloc_min": 3.0,
3628                      "sloc_max": 3.0,
3629                      "cloc_min": 0.0,
3630                      "cloc_max": 0.0,
3631                      "ploc_min": 3.0,
3632                      "ploc_max": 3.0,
3633                      "lloc_min": 2.0,
3634                      "lloc_max": 2.0,
3635                      "blank_min": 0.0,
3636                      "blank_max": 0.0
3637                    }"###
3638                );
3639            },
3640        );
3641    }
3642
3643    #[test]
3644    fn java_multi_ploc() {
3645        check_metrics::<JavaParser>(
3646            "int x = 1;
3647            for (int i = 0; i < 100; i++) {
3648               System.out.println(i);
3649             }",
3650            "foo.java",
3651            |metric| {
3652                // Spaces: 1
3653                insta::assert_json_snapshot!(
3654                    metric.loc,
3655                    @r###"
3656                    {
3657                      "sloc": 4.0,
3658                      "ploc": 4.0,
3659                      "lloc": 3.0,
3660                      "cloc": 0.0,
3661                      "blank": 0.0,
3662                      "sloc_average": 4.0,
3663                      "ploc_average": 4.0,
3664                      "lloc_average": 3.0,
3665                      "cloc_average": 0.0,
3666                      "blank_average": 0.0,
3667                      "sloc_min": 4.0,
3668                      "sloc_max": 4.0,
3669                      "cloc_min": 0.0,
3670                      "cloc_max": 0.0,
3671                      "ploc_min": 4.0,
3672                      "ploc_max": 4.0,
3673                      "lloc_min": 3.0,
3674                      "lloc_max": 3.0,
3675                      "blank_min": 0.0,
3676                      "blank_max": 0.0
3677                    }"###
3678                );
3679            },
3680        );
3681    }
3682
3683    #[test]
3684    fn java_single_statement_lloc() {
3685        check_metrics::<JavaParser>("int max = 10;", "foo.java", |metric| {
3686            // Spaces: 1
3687            insta::assert_json_snapshot!(
3688                metric.loc,
3689                @r###"
3690                    {
3691                      "sloc": 1.0,
3692                      "ploc": 1.0,
3693                      "lloc": 1.0,
3694                      "cloc": 0.0,
3695                      "blank": 0.0,
3696                      "sloc_average": 1.0,
3697                      "ploc_average": 1.0,
3698                      "lloc_average": 1.0,
3699                      "cloc_average": 0.0,
3700                      "blank_average": 0.0,
3701                      "sloc_min": 1.0,
3702                      "sloc_max": 1.0,
3703                      "cloc_min": 0.0,
3704                      "cloc_max": 0.0,
3705                      "ploc_min": 1.0,
3706                      "ploc_max": 1.0,
3707                      "lloc_min": 1.0,
3708                      "lloc_max": 1.0,
3709                      "blank_min": 0.0,
3710                      "blank_max": 0.0
3711                    }"###
3712            );
3713        });
3714    }
3715
3716    #[test]
3717    fn java_for_lloc() {
3718        check_metrics::<JavaParser>(
3719            "for (int i = 0; i < 100; i++) { // + 1
3720               System.out.println(i); // + 1
3721             }",
3722            "foo.java",
3723            |metric| {
3724                // Spaces: 1
3725                insta::assert_json_snapshot!(
3726                    metric.loc,
3727                    @r###"
3728                    {
3729                      "sloc": 3.0,
3730                      "ploc": 3.0,
3731                      "lloc": 2.0,
3732                      "cloc": 2.0,
3733                      "blank": 0.0,
3734                      "sloc_average": 3.0,
3735                      "ploc_average": 3.0,
3736                      "lloc_average": 2.0,
3737                      "cloc_average": 2.0,
3738                      "blank_average": 0.0,
3739                      "sloc_min": 3.0,
3740                      "sloc_max": 3.0,
3741                      "cloc_min": 2.0,
3742                      "cloc_max": 2.0,
3743                      "ploc_min": 3.0,
3744                      "ploc_max": 3.0,
3745                      "lloc_min": 2.0,
3746                      "lloc_max": 2.0,
3747                      "blank_min": 0.0,
3748                      "blank_max": 0.0
3749                    }"###
3750                );
3751            },
3752        );
3753    }
3754
3755    #[test]
3756    fn java_foreach_lloc() {
3757        check_metrics::<JavaParser>(
3758            "
3759            int arr[]={12,13,14,44}; // +1
3760            for (int i:arr) { // +1
3761               System.out.println(i); // +1
3762             }",
3763            "foo.java",
3764            |metric| {
3765                // Spaces: 1
3766                insta::assert_json_snapshot!(
3767                    metric.loc,
3768                    @r###"
3769                    {
3770                      "sloc": 4.0,
3771                      "ploc": 4.0,
3772                      "lloc": 3.0,
3773                      "cloc": 3.0,
3774                      "blank": 0.0,
3775                      "sloc_average": 4.0,
3776                      "ploc_average": 4.0,
3777                      "lloc_average": 3.0,
3778                      "cloc_average": 3.0,
3779                      "blank_average": 0.0,
3780                      "sloc_min": 4.0,
3781                      "sloc_max": 4.0,
3782                      "cloc_min": 3.0,
3783                      "cloc_max": 3.0,
3784                      "ploc_min": 4.0,
3785                      "ploc_max": 4.0,
3786                      "lloc_min": 3.0,
3787                      "lloc_max": 3.0,
3788                      "blank_min": 0.0,
3789                      "blank_max": 0.0
3790                    }"###
3791                );
3792            },
3793        );
3794    }
3795
3796    #[test]
3797    fn java_while_lloc() {
3798        check_metrics::<JavaParser>(
3799            "
3800            int i=0; // +1
3801            while(i < 10) { // +1
3802                i++; // +1
3803                System.out.println(i); // +1
3804             }",
3805            "foo.java",
3806            |metric| {
3807                // Spaces: 1
3808                insta::assert_json_snapshot!(
3809                    metric.loc,
3810                    @r###"
3811                    {
3812                      "sloc": 5.0,
3813                      "ploc": 5.0,
3814                      "lloc": 4.0,
3815                      "cloc": 4.0,
3816                      "blank": 0.0,
3817                      "sloc_average": 5.0,
3818                      "ploc_average": 5.0,
3819                      "lloc_average": 4.0,
3820                      "cloc_average": 4.0,
3821                      "blank_average": 0.0,
3822                      "sloc_min": 5.0,
3823                      "sloc_max": 5.0,
3824                      "cloc_min": 4.0,
3825                      "cloc_max": 4.0,
3826                      "ploc_min": 5.0,
3827                      "ploc_max": 5.0,
3828                      "lloc_min": 4.0,
3829                      "lloc_max": 4.0,
3830                      "blank_min": 0.0,
3831                      "blank_max": 0.0
3832                    }"###
3833                );
3834            },
3835        );
3836    }
3837
3838    #[test]
3839    fn java_do_while_lloc() {
3840        check_metrics::<JavaParser>(
3841            "
3842            int i=0; // +1
3843            do { // +1
3844                i++; // +1
3845                System.out.println(i); // +1
3846             } while(i < 10)",
3847            "foo.java",
3848            |metric| {
3849                // Spaces: 1
3850                insta::assert_json_snapshot!(
3851                    metric.loc,
3852                    @r###"
3853                    {
3854                      "sloc": 5.0,
3855                      "ploc": 5.0,
3856                      "lloc": 4.0,
3857                      "cloc": 4.0,
3858                      "blank": 0.0,
3859                      "sloc_average": 5.0,
3860                      "ploc_average": 5.0,
3861                      "lloc_average": 4.0,
3862                      "cloc_average": 4.0,
3863                      "blank_average": 0.0,
3864                      "sloc_min": 5.0,
3865                      "sloc_max": 5.0,
3866                      "cloc_min": 4.0,
3867                      "cloc_max": 4.0,
3868                      "ploc_min": 5.0,
3869                      "ploc_max": 5.0,
3870                      "lloc_min": 4.0,
3871                      "lloc_max": 4.0,
3872                      "blank_min": 0.0,
3873                      "blank_max": 0.0
3874                    }"###
3875                );
3876            },
3877        );
3878    }
3879
3880    #[test]
3881    fn java_switch_lloc() {
3882        check_metrics::<JavaParser>(
3883            "switch(grade) { // +1
3884                case 'A' :
3885                   System.out.println(\"Pass with distinction\"); // +1
3886                   break; // +1
3887                case 'B' :
3888                case 'C' :
3889                   System.out.println(\"Pass\"); // +1
3890                   break; // +1
3891                case 'D' :
3892                   System.out.println(\"At risk\"); // +1
3893                case 'F' :
3894                   System.out.println(\"Fail\"); // +1
3895                   break; // +1
3896                default :
3897                   System.out.println(\"Invalid grade\"); // +1
3898             }",
3899            "foo.java",
3900            |metric| {
3901                // Spaces: 1
3902                insta::assert_json_snapshot!(
3903                    metric.loc,
3904                    @r###"
3905                    {
3906                      "sloc": 16.0,
3907                      "ploc": 16.0,
3908                      "lloc": 9.0,
3909                      "cloc": 9.0,
3910                      "blank": 0.0,
3911                      "sloc_average": 16.0,
3912                      "ploc_average": 16.0,
3913                      "lloc_average": 9.0,
3914                      "cloc_average": 9.0,
3915                      "blank_average": 0.0,
3916                      "sloc_min": 16.0,
3917                      "sloc_max": 16.0,
3918                      "cloc_min": 9.0,
3919                      "cloc_max": 9.0,
3920                      "ploc_min": 16.0,
3921                      "ploc_max": 16.0,
3922                      "lloc_min": 9.0,
3923                      "lloc_max": 9.0,
3924                      "blank_min": 0.0,
3925                      "blank_max": 0.0
3926                    }"###
3927                );
3928            },
3929        );
3930    }
3931
3932    #[test]
3933    fn java_continue_lloc() {
3934        check_metrics::<JavaParser>(
3935            "int max = 10; // +1
3936
3937            for (int i = 0; i < max; i++) { // +1
3938                if(i % 2 == 0) { continue;} + 2
3939                System.out.println(i); // +1
3940             }",
3941            "foo.java",
3942            |metric| {
3943                // Spaces: 1
3944                insta::assert_json_snapshot!(
3945                    metric.loc,
3946                    @r###"
3947                    {
3948                      "sloc": 6.0,
3949                      "ploc": 5.0,
3950                      "lloc": 5.0,
3951                      "cloc": 3.0,
3952                      "blank": 1.0,
3953                      "sloc_average": 6.0,
3954                      "ploc_average": 5.0,
3955                      "lloc_average": 5.0,
3956                      "cloc_average": 3.0,
3957                      "blank_average": 1.0,
3958                      "sloc_min": 6.0,
3959                      "sloc_max": 6.0,
3960                      "cloc_min": 3.0,
3961                      "cloc_max": 3.0,
3962                      "ploc_min": 5.0,
3963                      "ploc_max": 5.0,
3964                      "lloc_min": 5.0,
3965                      "lloc_max": 5.0,
3966                      "blank_min": 1.0,
3967                      "blank_max": 1.0
3968                    }"###
3969                );
3970            },
3971        );
3972    }
3973
3974    #[test]
3975    fn java_try_lloc() {
3976        check_metrics::<JavaParser>(
3977            "try { // +1
3978                int[] myNumbers = {1, 2, 3}; // +1
3979                System.out.println(myNumbers[10]); // +1
3980              } catch (Exception e) {
3981                System.out.println(e.getMessage()); // +1
3982                throw e; // +1
3983              }",
3984            "foo.java",
3985            |metric| {
3986                // Spaces: 1
3987                insta::assert_json_snapshot!(
3988                    metric.loc,
3989                    @r###"
3990                    {
3991                      "sloc": 7.0,
3992                      "ploc": 7.0,
3993                      "lloc": 5.0,
3994                      "cloc": 5.0,
3995                      "blank": 0.0,
3996                      "sloc_average": 7.0,
3997                      "ploc_average": 7.0,
3998                      "lloc_average": 5.0,
3999                      "cloc_average": 5.0,
4000                      "blank_average": 0.0,
4001                      "sloc_min": 7.0,
4002                      "sloc_max": 7.0,
4003                      "cloc_min": 5.0,
4004                      "cloc_max": 5.0,
4005                      "ploc_min": 7.0,
4006                      "ploc_max": 7.0,
4007                      "lloc_min": 5.0,
4008                      "lloc_max": 5.0,
4009                      "blank_min": 0.0,
4010                      "blank_max": 0.0
4011                    }"###
4012                );
4013            },
4014        );
4015    }
4016
4017    #[test]
4018    fn java_class_loc() {
4019        check_metrics::<JavaParser>(
4020            "
4021            public class Person {
4022              private String name;
4023              public Person(String name){
4024                this.name = name; // +1
4025              }
4026              public String getName() {
4027                return name; // +1
4028              }
4029            }",
4030            "foo.java",
4031            |metric| {
4032                // Spaces: 4
4033                insta::assert_json_snapshot!(
4034                    metric.loc,
4035                    @r###"
4036                    {
4037                      "sloc": 9.0,
4038                      "ploc": 9.0,
4039                      "lloc": 2.0,
4040                      "cloc": 2.0,
4041                      "blank": 0.0,
4042                      "sloc_average": 2.25,
4043                      "ploc_average": 2.25,
4044                      "lloc_average": 0.5,
4045                      "cloc_average": 0.5,
4046                      "blank_average": 0.0,
4047                      "sloc_min": 9.0,
4048                      "sloc_max": 9.0,
4049                      "cloc_min": 2.0,
4050                      "cloc_max": 2.0,
4051                      "ploc_min": 9.0,
4052                      "ploc_max": 9.0,
4053                      "lloc_min": 2.0,
4054                      "lloc_max": 2.0,
4055                      "blank_min": 0.0,
4056                      "blank_max": 0.0
4057                    }"###
4058                );
4059            },
4060        );
4061    }
4062
4063    #[test]
4064    fn java_expressions_lloc() {
4065        check_metrics::<JavaParser>(
4066            "int x = 10;                                                            // +1 local var declaration
4067            x=+89;                                                                  // +1 expression statement
4068            int y = x * 2;                                                          // +1 local var declaration
4069            IntFunction double = (n) -> n*2;                                        // +1 local var declaration
4070            int y2 = double(x);                                                     // +1 local var declaration
4071            System.out.println(\"double \" + x + \" = \" + y2);                     // +1 expression statement
4072            String message = (x % 2) == 0 ? \"Evenly done.\" : \"Oddly done.\";     // +1 local var declaration
4073            Object done = (Runnable) () -> { System.out.println(\"Done!\"); };      // +2 local var declaration + expression statement
4074            String s = \"string\";                                                  // +1 local var declaration
4075            boolean isS = (s instanceof String);                                    // +1 local var declaration
4076            done.run();                                                             // +1 expression statement
4077            ",
4078            "foo.java",
4079            |metric| {
4080                // Spaces: 1
4081                insta::assert_json_snapshot!(
4082                    metric.loc,
4083                    @r###"
4084                    {
4085                      "sloc": 11.0,
4086                      "ploc": 11.0,
4087                      "lloc": 12.0,
4088                      "cloc": 11.0,
4089                      "blank": 0.0,
4090                      "sloc_average": 11.0,
4091                      "ploc_average": 11.0,
4092                      "lloc_average": 12.0,
4093                      "cloc_average": 11.0,
4094                      "blank_average": 0.0,
4095                      "sloc_min": 11.0,
4096                      "sloc_max": 11.0,
4097                      "cloc_min": 11.0,
4098                      "cloc_max": 11.0,
4099                      "ploc_min": 11.0,
4100                      "ploc_max": 11.0,
4101                      "lloc_min": 12.0,
4102                      "lloc_max": 12.0,
4103                      "blank_min": 0.0,
4104                      "blank_max": 0.0
4105                    }"###
4106                );
4107            },
4108        );
4109    }
4110
4111    #[test]
4112    fn java_statement_inline_loc() {
4113        check_metrics::<JavaParser>(
4114            "for (int i = 0; i < 100; i++) { System.out.println(\"hello\"); }",
4115            "foo.java",
4116            |metric| {
4117                // Spaces: 1
4118                insta::assert_json_snapshot!(
4119                    metric.loc,
4120                    @r###"
4121                    {
4122                      "sloc": 1.0,
4123                      "ploc": 1.0,
4124                      "lloc": 2.0,
4125                      "cloc": 0.0,
4126                      "blank": 0.0,
4127                      "sloc_average": 1.0,
4128                      "ploc_average": 1.0,
4129                      "lloc_average": 2.0,
4130                      "cloc_average": 0.0,
4131                      "blank_average": 0.0,
4132                      "sloc_min": 1.0,
4133                      "sloc_max": 1.0,
4134                      "cloc_min": 0.0,
4135                      "cloc_max": 0.0,
4136                      "ploc_min": 1.0,
4137                      "ploc_max": 1.0,
4138                      "lloc_min": 2.0,
4139                      "lloc_max": 2.0,
4140                      "blank_min": 0.0,
4141                      "blank_max": 0.0
4142                    }"###
4143                );
4144            },
4145        );
4146    }
4147
4148    #[test]
4149    fn java_general_loc() {
4150        check_metrics::<JavaParser>(
4151            "int max = 100;
4152
4153            /*
4154              Loop through and print
4155                from: 0
4156                to: max
4157            */
4158            for (int i = 0; i < max; i++) {
4159               // Print the value
4160               System.out.println(i);
4161             }",
4162            "foo.java",
4163            |metric| {
4164                // Spaces: 1
4165                insta::assert_json_snapshot!(
4166                    metric.loc,
4167                    @r###"
4168                    {
4169                      "sloc": 11.0,
4170                      "ploc": 4.0,
4171                      "lloc": 3.0,
4172                      "cloc": 6.0,
4173                      "blank": 1.0,
4174                      "sloc_average": 11.0,
4175                      "ploc_average": 4.0,
4176                      "lloc_average": 3.0,
4177                      "cloc_average": 6.0,
4178                      "blank_average": 1.0,
4179                      "sloc_min": 11.0,
4180                      "sloc_max": 11.0,
4181                      "cloc_min": 6.0,
4182                      "cloc_max": 6.0,
4183                      "ploc_min": 4.0,
4184                      "ploc_max": 4.0,
4185                      "lloc_min": 3.0,
4186                      "lloc_max": 3.0,
4187                      "blank_min": 1.0,
4188                      "blank_max": 1.0
4189                    }"###
4190                );
4191            },
4192        );
4193    }
4194
4195    #[test]
4196    fn java_main_class_loc() {
4197        check_metrics::<JavaParser>(
4198            "package com.company;
4199             /**
4200             * The HelloWorldApp class implements an application that
4201             * simply prints \"Hello World!\" to standard output.
4202             */
4203
4204            class HelloWorldApp {
4205              public void main(String[] args) {
4206                String message = args.length == 0 ? \"Hello empty world\" : \"Hello world\"; // +1 lloc : 1 var assignment
4207                System.out.println(message); // Display the string. +1 lloc
4208              }
4209            }",
4210            "foo.java",
4211            |metric| {
4212                // Spaces: 3
4213                insta::assert_json_snapshot!(
4214                    metric.loc,
4215                    @r###"
4216                    {
4217                      "sloc": 12.0,
4218                      "ploc": 7.0,
4219                      "lloc": 2.0,
4220                      "cloc": 6.0,
4221                      "blank": 1.0,
4222                      "sloc_average": 4.0,
4223                      "ploc_average": 2.3333333333333335,
4224                      "lloc_average": 0.6666666666666666,
4225                      "cloc_average": 2.0,
4226                      "blank_average": 0.3333333333333333,
4227                      "sloc_min": 6.0,
4228                      "sloc_max": 6.0,
4229                      "cloc_min": 2.0,
4230                      "cloc_max": 2.0,
4231                      "ploc_min": 6.0,
4232                      "ploc_max": 6.0,
4233                      "lloc_min": 2.0,
4234                      "lloc_max": 2.0,
4235                      "blank_min": 0.0,
4236                      "blank_max": 0.0
4237                    }"###
4238                );
4239            },
4240        );
4241    }
4242
4243    #[test]
4244    fn go_general_loc() {
4245        check_metrics::<GoParser>(
4246            "package main
4247
4248            // entrypoint
4249            func main() {
4250                /* loop body */
4251                for i := 0; i < 10; i++ {
4252                    fmt.Println(i)
4253                }
4254            }",
4255            "foo.go",
4256            |metric| {
4257                // Spaces: 2 (unit + main).
4258                // lloc: for_statement (+1), fmt.Println expression (+1).
4259                //       `i := 0` and `i++` inside the for-clause are gated.
4260                // cloc: 2 comments (line + block).
4261                insta::assert_json_snapshot!(
4262                    metric.loc,
4263                    @r###"
4264                    {
4265                      "sloc": 9.0,
4266                      "ploc": 6.0,
4267                      "lloc": 2.0,
4268                      "cloc": 2.0,
4269                      "blank": 1.0,
4270                      "sloc_average": 4.5,
4271                      "ploc_average": 3.0,
4272                      "lloc_average": 1.0,
4273                      "cloc_average": 1.0,
4274                      "blank_average": 0.5,
4275                      "sloc_min": 6.0,
4276                      "sloc_max": 6.0,
4277                      "cloc_min": 1.0,
4278                      "cloc_max": 1.0,
4279                      "ploc_min": 5.0,
4280                      "ploc_max": 5.0,
4281                      "lloc_min": 2.0,
4282                      "lloc_max": 2.0,
4283                      "blank_min": 0.0,
4284                      "blank_max": 0.0
4285                    }"###
4286                );
4287            },
4288        );
4289    }
4290
4291    #[test]
4292    fn go_for_clause_does_not_double_count_lloc() {
4293        // Bare `for` body has only a return; the `for_statement` itself is the
4294        // single logical line. Confirms ShortVarDeclaration in a for-clause
4295        // does not add an extra lloc.
4296        check_metrics::<GoParser>(
4297            "package main
4298            func f(n int) int {
4299                for i := 0; i < n; i++ {
4300                    return i
4301                }
4302                return 0
4303            }",
4304            "foo.go",
4305            |metric| {
4306                // Expected lloc: for (+1), return (+1), return (+1) = 3.
4307                // Without the gate, ShortVarDeclaration would add an extra (+1).
4308                assert_eq!(metric.loc.lloc(), 3.0);
4309            },
4310        );
4311    }
4312
4313    #[test]
4314    fn go_blank() {
4315        check_metrics::<GoParser>(
4316            "package main
4317
4318            func foo() {
4319                x := 1
4320
4321                y := 2
4322            }",
4323            "foo.go",
4324            |metric| {
4325                // Spaces: 2 (unit + foo).
4326                // blank: 2 (lines 2 and 5 are empty).
4327                insta::assert_json_snapshot!(
4328                    metric.loc,
4329                    @r#"
4330                {
4331                  "sloc": 7.0,
4332                  "ploc": 5.0,
4333                  "lloc": 2.0,
4334                  "cloc": 0.0,
4335                  "blank": 2.0,
4336                  "sloc_average": 3.5,
4337                  "ploc_average": 2.5,
4338                  "lloc_average": 1.0,
4339                  "cloc_average": 0.0,
4340                  "blank_average": 1.0,
4341                  "sloc_min": 5.0,
4342                  "sloc_max": 5.0,
4343                  "cloc_min": 0.0,
4344                  "cloc_max": 0.0,
4345                  "ploc_min": 4.0,
4346                  "ploc_max": 4.0,
4347                  "lloc_min": 2.0,
4348                  "lloc_max": 2.0,
4349                  "blank_min": 1.0,
4350                  "blank_max": 1.0
4351                }
4352                "#
4353                );
4354            },
4355        );
4356    }
4357
4358    #[test]
4359    fn go_cloc_line_comments() {
4360        check_metrics::<GoParser>(
4361            "package main
4362
4363            // helper adds two numbers.
4364            // It returns their sum.
4365            func add(a, b int) int {
4366                // compute the result
4367                return a + b
4368            }",
4369            "foo.go",
4370            |metric| {
4371                // Spaces: 2 (unit + add).
4372                // cloc: 3 lines with `//` comments.
4373                insta::assert_json_snapshot!(
4374                    metric.loc,
4375                    @r#"
4376                {
4377                  "sloc": 8.0,
4378                  "ploc": 4.0,
4379                  "lloc": 1.0,
4380                  "cloc": 3.0,
4381                  "blank": 1.0,
4382                  "sloc_average": 4.0,
4383                  "ploc_average": 2.0,
4384                  "lloc_average": 0.5,
4385                  "cloc_average": 1.5,
4386                  "blank_average": 0.5,
4387                  "sloc_min": 4.0,
4388                  "sloc_max": 4.0,
4389                  "cloc_min": 1.0,
4390                  "cloc_max": 1.0,
4391                  "ploc_min": 3.0,
4392                  "ploc_max": 3.0,
4393                  "lloc_min": 1.0,
4394                  "lloc_max": 1.0,
4395                  "blank_min": 0.0,
4396                  "blank_max": 0.0
4397                }
4398                "#
4399                );
4400            },
4401        );
4402    }
4403
4404    #[test]
4405    fn go_cloc_block_comments() {
4406        check_metrics::<GoParser>(
4407            "package main
4408
4409            /* block comment
4410               spanning two lines */
4411            func foo() {
4412                x := 1 /* inline block */
4413            }",
4414            "foo.go",
4415            |metric| {
4416                // Spaces: 2 (unit + foo).
4417                // cloc: 2-line block comment + inline block = 3 comment lines.
4418                insta::assert_json_snapshot!(
4419                    metric.loc,
4420                    @r#"
4421                {
4422                  "sloc": 7.0,
4423                  "ploc": 4.0,
4424                  "lloc": 1.0,
4425                  "cloc": 3.0,
4426                  "blank": 1.0,
4427                  "sloc_average": 3.5,
4428                  "ploc_average": 2.0,
4429                  "lloc_average": 0.5,
4430                  "cloc_average": 1.5,
4431                  "blank_average": 0.5,
4432                  "sloc_min": 3.0,
4433                  "sloc_max": 3.0,
4434                  "cloc_min": 1.0,
4435                  "cloc_max": 1.0,
4436                  "ploc_min": 3.0,
4437                  "ploc_max": 3.0,
4438                  "lloc_min": 1.0,
4439                  "lloc_max": 1.0,
4440                  "blank_min": 0.0,
4441                  "blank_max": 0.0
4442                }
4443                "#
4444                );
4445            },
4446        );
4447    }
4448
4449    #[test]
4450    fn go_lloc_if_for_switch() {
4451        check_metrics::<GoParser>(
4452            "package main
4453
4454            func foo(n int) int {
4455                if n > 0 {
4456                    for i := 0; i < n; i++ {
4457                        switch i {
4458                        }
4459                    }
4460                }
4461                return n
4462            }",
4463            "foo.go",
4464            |metric| {
4465                // Spaces: 2 (unit + foo).
4466                // lloc: if (+1), for (+1), switch (+1), return (+1) = 4.
4467                insta::assert_json_snapshot!(
4468                    metric.loc,
4469                    @r#"
4470                {
4471                  "sloc": 11.0,
4472                  "ploc": 10.0,
4473                  "lloc": 4.0,
4474                  "cloc": 0.0,
4475                  "blank": 1.0,
4476                  "sloc_average": 5.5,
4477                  "ploc_average": 5.0,
4478                  "lloc_average": 2.0,
4479                  "cloc_average": 0.0,
4480                  "blank_average": 0.5,
4481                  "sloc_min": 9.0,
4482                  "sloc_max": 9.0,
4483                  "cloc_min": 0.0,
4484                  "cloc_max": 0.0,
4485                  "ploc_min": 9.0,
4486                  "ploc_max": 9.0,
4487                  "lloc_min": 4.0,
4488                  "lloc_max": 4.0,
4489                  "blank_min": 0.0,
4490                  "blank_max": 0.0
4491                }
4492                "#
4493                );
4494            },
4495        );
4496    }
4497
4498    #[test]
4499    fn go_lloc_go_defer() {
4500        check_metrics::<GoParser>(
4501            "package main
4502
4503            func foo() {
4504                go run()
4505                defer cleanup()
4506            }",
4507            "foo.go",
4508            |metric| {
4509                // Spaces: 2 (unit + foo).
4510                // lloc: go (+1), defer (+1) = 2.
4511                insta::assert_json_snapshot!(
4512                    metric.loc,
4513                    @r#"
4514                {
4515                  "sloc": 6.0,
4516                  "ploc": 5.0,
4517                  "lloc": 2.0,
4518                  "cloc": 0.0,
4519                  "blank": 1.0,
4520                  "sloc_average": 3.0,
4521                  "ploc_average": 2.5,
4522                  "lloc_average": 1.0,
4523                  "cloc_average": 0.0,
4524                  "blank_average": 0.5,
4525                  "sloc_min": 4.0,
4526                  "sloc_max": 4.0,
4527                  "cloc_min": 0.0,
4528                  "cloc_max": 0.0,
4529                  "ploc_min": 4.0,
4530                  "ploc_max": 4.0,
4531                  "lloc_min": 2.0,
4532                  "lloc_max": 2.0,
4533                  "blank_min": 0.0,
4534                  "blank_max": 0.0
4535                }
4536                "#
4537                );
4538            },
4539        );
4540    }
4541
4542    #[test]
4543    fn go_lloc_var_const_declarations() {
4544        check_metrics::<GoParser>(
4545            "package main
4546
4547            func foo() {
4548                var x int
4549                var y = 10
4550                const z = 42
4551                a := 3
4552                a = 4
4553            }",
4554            "foo.go",
4555            |metric| {
4556                // Spaces: 2 (unit + foo).
4557                // lloc: var (+1), var (+1), const (+1),
4558                //       short_var_decl (+1), assignment (+1) = 5.
4559                insta::assert_json_snapshot!(
4560                    metric.loc,
4561                    @r#"
4562                {
4563                  "sloc": 9.0,
4564                  "ploc": 8.0,
4565                  "lloc": 5.0,
4566                  "cloc": 0.0,
4567                  "blank": 1.0,
4568                  "sloc_average": 4.5,
4569                  "ploc_average": 4.0,
4570                  "lloc_average": 2.5,
4571                  "cloc_average": 0.0,
4572                  "blank_average": 0.5,
4573                  "sloc_min": 7.0,
4574                  "sloc_max": 7.0,
4575                  "cloc_min": 0.0,
4576                  "cloc_max": 0.0,
4577                  "ploc_min": 7.0,
4578                  "ploc_max": 7.0,
4579                  "lloc_min": 5.0,
4580                  "lloc_max": 5.0,
4581                  "blank_min": 0.0,
4582                  "blank_max": 0.0
4583                }
4584                "#
4585                );
4586            },
4587        );
4588    }
4589
4590    #[test]
4591    fn go_lloc_select() {
4592        check_metrics::<GoParser>(
4593            "package main
4594
4595            func foo(ch chan int) {
4596                select {
4597                case v := <-ch:
4598                    _ = v
4599                }
4600            }",
4601            "foo.go",
4602            |metric| {
4603                // Spaces: 2 (unit + foo).
4604                // lloc: select (+1), assignment `_ = v` (+1) = 2.
4605                // `case v := <-ch:` is a receive_statement inside a
4606                // communication_case, not a ShortVarDeclaration.
4607                insta::assert_json_snapshot!(
4608                    metric.loc,
4609                    @r#"
4610                {
4611                  "sloc": 8.0,
4612                  "ploc": 7.0,
4613                  "lloc": 2.0,
4614                  "cloc": 0.0,
4615                  "blank": 1.0,
4616                  "sloc_average": 4.0,
4617                  "ploc_average": 3.5,
4618                  "lloc_average": 1.0,
4619                  "cloc_average": 0.0,
4620                  "blank_average": 0.5,
4621                  "sloc_min": 6.0,
4622                  "sloc_max": 6.0,
4623                  "cloc_min": 0.0,
4624                  "cloc_max": 0.0,
4625                  "ploc_min": 6.0,
4626                  "ploc_max": 6.0,
4627                  "lloc_min": 2.0,
4628                  "lloc_max": 2.0,
4629                  "blank_min": 0.0,
4630                  "blank_max": 0.0
4631                }
4632                "#
4633                );
4634            },
4635        );
4636    }
4637
4638    #[test]
4639    fn go_sloc_multiline_function() {
4640        check_metrics::<GoParser>(
4641            "package main
4642
4643            func add(
4644                a int,
4645                b int,
4646            ) int {
4647                return a + b
4648            }",
4649            "foo.go",
4650            |metric| {
4651                // Spaces: 2 (unit + add).
4652                // The multi-line signature should count each line as sloc.
4653                insta::assert_json_snapshot!(
4654                    metric.loc,
4655                    @r#"
4656                {
4657                  "sloc": 8.0,
4658                  "ploc": 7.0,
4659                  "lloc": 1.0,
4660                  "cloc": 0.0,
4661                  "blank": 1.0,
4662                  "sloc_average": 4.0,
4663                  "ploc_average": 3.5,
4664                  "lloc_average": 0.5,
4665                  "cloc_average": 0.0,
4666                  "blank_average": 0.5,
4667                  "sloc_min": 6.0,
4668                  "sloc_max": 6.0,
4669                  "cloc_min": 0.0,
4670                  "cloc_max": 0.0,
4671                  "ploc_min": 6.0,
4672                  "ploc_max": 6.0,
4673                  "lloc_min": 1.0,
4674                  "lloc_max": 1.0,
4675                  "blank_min": 0.0,
4676                  "blank_max": 0.0
4677                }
4678                "#
4679                );
4680            },
4681        );
4682    }
4683
4684    #[test]
4685    fn go_code_comment_same_line() {
4686        check_metrics::<GoParser>(
4687            "package main
4688
4689            func foo() {
4690                x := 1 // initialize x
4691                y := 2 // initialize y
4692            }",
4693            "foo.go",
4694            |metric| {
4695                // Spaces: 2 (unit + foo).
4696                // cloc: 2 (inline comments on code lines).
4697                // blank: 1 (line between package and func).
4698                // The code+comment lines should count for both ploc and cloc.
4699                insta::assert_json_snapshot!(
4700                    metric.loc,
4701                    @r#"
4702                {
4703                  "sloc": 6.0,
4704                  "ploc": 5.0,
4705                  "lloc": 2.0,
4706                  "cloc": 2.0,
4707                  "blank": 1.0,
4708                  "sloc_average": 3.0,
4709                  "ploc_average": 2.5,
4710                  "lloc_average": 1.0,
4711                  "cloc_average": 1.0,
4712                  "blank_average": 0.5,
4713                  "sloc_min": 4.0,
4714                  "sloc_max": 4.0,
4715                  "cloc_min": 2.0,
4716                  "cloc_max": 2.0,
4717                  "ploc_min": 4.0,
4718                  "ploc_max": 4.0,
4719                  "lloc_min": 2.0,
4720                  "lloc_max": 2.0,
4721                  "blank_min": 0.0,
4722                  "blank_max": 0.0
4723                }
4724                "#
4725                );
4726            },
4727        );
4728    }
4729
4730    #[test]
4731    fn perl_grammar_smoke() {
4732        // Pin the contract that tree-sitter-perl 1.1.2 cleanly parses every
4733        // Perl construct exercised by the rest of the `perl_*` test suite.
4734        // If a future grammar bump turns one of these into an error tree,
4735        // the metric assertions might still pass numerically by coincidence;
4736        // this test fails loudly instead.
4737        assert_perl_parses_cleanly(
4738            "use strict;
4739use warnings;
4740
4741# line comment
4742
4743=pod
4744multi-line POD
4745=cut
4746
4747sub factorial {
4748    my ($n) = @_;
4749    return 1 if $n <= 1;
4750    return $n * factorial($n - 1);
4751}
4752
4753my @arr = (1, 2, 3);
4754my %hash = (a => 1, b => 2);
4755my $closure = sub { return $_[0] + 1; };
4756
4757for my $i (1..3) {
4758    if ($i % 2 == 0) {
4759        print \"even\\n\";
4760    } elsif ($i == 1) {
4761        print \"one\\n\";
4762    } else {
4763        print \"odd\\n\";
4764    }
4765}
4766
4767while ($x > 0) {
4768    last if $x == 0;
4769    $x--;
4770}
4771
4772unless ($done) {
4773    next;
4774}
4775
4776my $heredoc = <<END;
4777hello
4778END
4779",
4780        );
4781    }
4782
4783    #[test]
4784    fn perl_blank() {
4785        check_metrics::<PerlParser>(
4786            "
4787
4788my $a = 42;
4789
4790my $b = 43;
4791
4792",
4793            "foo.pl",
4794            |metric| {
4795                insta::assert_json_snapshot!(metric.loc, @r#"
4796                {
4797                  "sloc": 3.0,
4798                  "ploc": 2.0,
4799                  "lloc": 2.0,
4800                  "cloc": 0.0,
4801                  "blank": 1.0,
4802                  "sloc_average": 3.0,
4803                  "ploc_average": 2.0,
4804                  "lloc_average": 2.0,
4805                  "cloc_average": 0.0,
4806                  "blank_average": 1.0,
4807                  "sloc_min": 3.0,
4808                  "sloc_max": 3.0,
4809                  "cloc_min": 0.0,
4810                  "cloc_max": 0.0,
4811                  "ploc_min": 2.0,
4812                  "ploc_max": 2.0,
4813                  "lloc_min": 2.0,
4814                  "lloc_max": 2.0,
4815                  "blank_min": 1.0,
4816                  "blank_max": 1.0
4817                }
4818                "#);
4819            },
4820        );
4821    }
4822
4823    #[test]
4824    fn perl_no_zero_blank() {
4825        // Blank line interleaved with code that carries trailing comments —
4826        // stresses the `blank = sloc - (ploc ∪ cloc lines)` union math.
4827        check_metrics::<PerlParser>(
4828            "my $a = 1;
4829my $b = 2;
4830
4831my $c = 3; # trailing
4832my $d = 4; # trailing
4833my $e = 5;",
4834            "foo.pl",
4835            |metric| {
4836                assert_eq!(metric.loc.sloc(), 6.0);
4837                assert_eq!(metric.loc.ploc(), 5.0);
4838                assert_eq!(metric.loc.cloc(), 2.0);
4839                assert_eq!(metric.loc.blank(), 1.0);
4840                insta::assert_json_snapshot!(metric.loc);
4841            },
4842        );
4843    }
4844
4845    #[test]
4846    fn perl_blank_zero_sanity() {
4847        // Sanity check: blank must report 0, never go negative, when the
4848        // input has no blank lines.
4849        check_metrics::<PerlParser>(
4850            "my $a = 1;
4851my $b = 2;",
4852            "foo.pl",
4853            |metric| {
4854                assert_eq!(metric.loc.sloc(), 2.0);
4855                assert_eq!(metric.loc.ploc(), 2.0);
4856                assert_eq!(metric.loc.lloc(), 2.0);
4857                assert_eq!(metric.loc.cloc(), 0.0);
4858                assert_eq!(metric.loc.blank(), 0.0);
4859            },
4860        );
4861    }
4862
4863    #[test]
4864    fn perl_cloc_line_comments() {
4865        check_metrics::<PerlParser>(
4866            "# top comment
4867my $a = 1; # trailing
4868my $b = 2;",
4869            "foo.pl",
4870            |metric| {
4871                insta::assert_json_snapshot!(metric.loc, @r#"
4872                {
4873                  "sloc": 3.0,
4874                  "ploc": 3.0,
4875                  "lloc": 2.0,
4876                  "cloc": 2.0,
4877                  "blank": 0.0,
4878                  "sloc_average": 3.0,
4879                  "ploc_average": 3.0,
4880                  "lloc_average": 2.0,
4881                  "cloc_average": 2.0,
4882                  "blank_average": 0.0,
4883                  "sloc_min": 3.0,
4884                  "sloc_max": 3.0,
4885                  "cloc_min": 2.0,
4886                  "cloc_max": 2.0,
4887                  "ploc_min": 3.0,
4888                  "ploc_max": 3.0,
4889                  "lloc_min": 2.0,
4890                  "lloc_max": 2.0,
4891                  "blank_min": 0.0,
4892                  "blank_max": 0.0
4893                }
4894                "#);
4895            },
4896        );
4897    }
4898
4899    #[test]
4900    fn perl_cloc_pod_block() {
4901        check_metrics::<PerlParser>(
4902            "my $x = 1;
4903=pod
4904multi-line
4905pod block
4906=cut
4907my $y = 2;",
4908            "foo.pl",
4909            |metric| {
4910                insta::assert_json_snapshot!(metric.loc, @r#"
4911                {
4912                  "sloc": 6.0,
4913                  "ploc": 2.0,
4914                  "lloc": 2.0,
4915                  "cloc": 4.0,
4916                  "blank": 0.0,
4917                  "sloc_average": 6.0,
4918                  "ploc_average": 2.0,
4919                  "lloc_average": 2.0,
4920                  "cloc_average": 4.0,
4921                  "blank_average": 0.0,
4922                  "sloc_min": 6.0,
4923                  "sloc_max": 6.0,
4924                  "cloc_min": 4.0,
4925                  "cloc_max": 4.0,
4926                  "ploc_min": 2.0,
4927                  "ploc_max": 2.0,
4928                  "lloc_min": 2.0,
4929                  "lloc_max": 2.0,
4930                  "blank_min": 0.0,
4931                  "blank_max": 0.0
4932                }
4933                "#);
4934            },
4935        );
4936    }
4937
4938    #[test]
4939    fn perl_lloc_simple_statements() {
4940        check_metrics::<PerlParser>(
4941            "my $a = 1;
4942my $b = 2;
4943my $c = 3;",
4944            "foo.pl",
4945            |metric| {
4946                insta::assert_json_snapshot!(metric.loc, @r#"
4947                {
4948                  "sloc": 3.0,
4949                  "ploc": 3.0,
4950                  "lloc": 3.0,
4951                  "cloc": 0.0,
4952                  "blank": 0.0,
4953                  "sloc_average": 3.0,
4954                  "ploc_average": 3.0,
4955                  "lloc_average": 3.0,
4956                  "cloc_average": 0.0,
4957                  "blank_average": 0.0,
4958                  "sloc_min": 3.0,
4959                  "sloc_max": 3.0,
4960                  "cloc_min": 0.0,
4961                  "cloc_max": 0.0,
4962                  "ploc_min": 3.0,
4963                  "ploc_max": 3.0,
4964                  "lloc_min": 3.0,
4965                  "lloc_max": 3.0,
4966                  "blank_min": 0.0,
4967                  "blank_max": 0.0
4968                }
4969                "#);
4970            },
4971        );
4972    }
4973
4974    #[test]
4975    fn perl_lloc_compound_statements() {
4976        check_metrics::<PerlParser>(
4977            "if ($x) {
4978    print 'a';
4979}
4980while ($n > 0) {
4981    $n--;
4982}",
4983            "foo.pl",
4984            |metric| {
4985                insta::assert_json_snapshot!(metric.loc, @r#"
4986                {
4987                  "sloc": 6.0,
4988                  "ploc": 6.0,
4989                  "lloc": 4.0,
4990                  "cloc": 0.0,
4991                  "blank": 0.0,
4992                  "sloc_average": 6.0,
4993                  "ploc_average": 6.0,
4994                  "lloc_average": 4.0,
4995                  "cloc_average": 0.0,
4996                  "blank_average": 0.0,
4997                  "sloc_min": 6.0,
4998                  "sloc_max": 6.0,
4999                  "cloc_min": 0.0,
5000                  "cloc_max": 0.0,
5001                  "ploc_min": 6.0,
5002                  "ploc_max": 6.0,
5003                  "lloc_min": 4.0,
5004                  "lloc_max": 4.0,
5005                  "blank_min": 0.0,
5006                  "blank_max": 0.0
5007                }
5008                "#);
5009            },
5010        );
5011    }
5012
5013    #[test]
5014    fn perl_lloc_postfix_form_counts_once() {
5015        // `do_thing() if cond;` is one logical line — wrapped in
5016        // single_line_statement; the inner if_simple_statement does not
5017        // add a second LLOC.
5018        check_metrics::<PerlParser>(
5019            "sub f {
5020    return 1 if $_[0];
5021}",
5022            "foo.pl",
5023            |metric| {
5024                assert_eq!(metric.loc.lloc(), 1.0);
5025            },
5026        );
5027    }
5028
5029    #[test]
5030    fn perl_lloc_use_statement() {
5031        check_metrics::<PerlParser>(
5032            "use strict;
5033use warnings;
5034my $x = 1;",
5035            "foo.pl",
5036            |metric| {
5037                insta::assert_json_snapshot!(metric.loc, @r#"
5038                {
5039                  "sloc": 3.0,
5040                  "ploc": 3.0,
5041                  "lloc": 3.0,
5042                  "cloc": 0.0,
5043                  "blank": 0.0,
5044                  "sloc_average": 3.0,
5045                  "ploc_average": 3.0,
5046                  "lloc_average": 3.0,
5047                  "cloc_average": 0.0,
5048                  "blank_average": 0.0,
5049                  "sloc_min": 3.0,
5050                  "sloc_max": 3.0,
5051                  "cloc_min": 0.0,
5052                  "cloc_max": 0.0,
5053                  "ploc_min": 3.0,
5054                  "ploc_max": 3.0,
5055                  "lloc_min": 3.0,
5056                  "lloc_max": 3.0,
5057                  "blank_min": 0.0,
5058                  "blank_max": 0.0
5059                }
5060                "#);
5061            },
5062        );
5063    }
5064
5065    #[test]
5066    fn perl_lloc_for_loop() {
5067        check_metrics::<PerlParser>(
5068            "for my $i (1..3) {
5069    print $i;
5070}",
5071            "foo.pl",
5072            |metric| {
5073                // `for_statement_2` (+1) and `print …;` SEMI in block (+1) → 2
5074                assert_eq!(metric.loc.lloc(), 2.0);
5075            },
5076        );
5077    }
5078
5079    #[test]
5080    fn perl_lloc_loop_control_statement() {
5081        check_metrics::<PerlParser>(
5082            "while (1) {
5083    last if $done;
5084}",
5085            "foo.pl",
5086            |metric| {
5087                // while_statement (+1) + loop_control_statement (+1) = 2
5088                assert_eq!(metric.loc.lloc(), 2.0);
5089            },
5090        );
5091    }
5092
5093    #[test]
5094    fn perl_lloc_no_double_count_inside_single_line_statement() {
5095        // SEMI inside a single_line_statement (postfix form) is a child of
5096        // if_simple_statement, not Block — so it must not add a second LLOC.
5097        check_metrics::<PerlParser>(
5098            "sub f {
5099    print 'a' unless $_[0];
5100}",
5101            "foo.pl",
5102            |metric| {
5103                assert_eq!(metric.loc.lloc(), 1.0);
5104            },
5105        );
5106    }
5107
5108    #[test]
5109    fn perl_lloc_function_definition_not_counted() {
5110        // `sub f { ... }` itself is a function space, not an LLOC; only its
5111        // body statements count.
5112        check_metrics::<PerlParser>(
5113            "sub f {
5114    my $x = 1;
5115}",
5116            "foo.pl",
5117            |metric| {
5118                assert_eq!(metric.loc.lloc(), 1.0);
5119            },
5120        );
5121    }
5122
5123    #[test]
5124    fn perl_lloc_anonymous_function() {
5125        // `my $f = sub { return 1; };` — the assignment is one LLOC at the
5126        // top level (the SEMI after `};`); the `return 1;` inside the
5127        // anonymous function block is a second LLOC inside the closure.
5128        check_metrics::<PerlParser>("my $f = sub { return 1; };", "foo.pl", |metric| {
5129            assert_eq!(metric.loc.lloc(), 2.0);
5130        });
5131    }
5132
5133    #[test]
5134    fn perl_lloc_string_content_excluded_from_ploc() {
5135        // The body of a multi-line double-quoted string is data, not code:
5136        // intermediate rows that contain only string contents should not be
5137        // added to PLOC. Row 0 holds `my $s = "line1`; row 2 holds `line3";`
5138        // (both have code); row 1 is purely string content.
5139        check_metrics::<PerlParser>(
5140            "my $s = \"line1
5141line2
5142line3\";",
5143            "foo.pl",
5144            |metric| {
5145                // PLOC = {row 0, row 2} = 2. Without the gate, row 1 would
5146                // also leak in as a leaf-row of the string body.
5147                assert_eq!(metric.loc.ploc(), 2.0);
5148            },
5149        );
5150    }
5151
5152    #[test]
5153    fn perl_lloc_unless_until() {
5154        check_metrics::<PerlParser>(
5155            "unless ($x) {
5156    print 'a';
5157}
5158until ($n == 0) {
5159    $n--;
5160}",
5161            "foo.pl",
5162            |metric| {
5163                // unless_statement (+1) + print SEMI (+1) + until_statement (+1)
5164                // + $n-- SEMI (+1) = 4
5165                assert_eq!(metric.loc.lloc(), 4.0);
5166            },
5167        );
5168    }
5169
5170    #[test]
5171    fn perl_lloc_heredoc_body_not_counted() {
5172        // Heredoc body content is data, not code: the body lines should not
5173        // contribute LLOC or PLOC.
5174        check_metrics::<PerlParser>(
5175            "my $s = <<END;
5176line1
5177line2
5178END
5179my $x = 1;",
5180            "foo.pl",
5181            |metric| {
5182                // Two top-level statements: the heredoc-using `my $s = …;`
5183                // and `my $x = 1;`.
5184                assert_eq!(metric.loc.lloc(), 2.0);
5185            },
5186        );
5187        // Independent confirmation that the snippet is a valid heredoc and
5188        // not silently parsed as an error tree (which could otherwise yield
5189        // the same `lloc == 2.0` and mask a grammar regression).
5190        assert_perl_parses_cleanly(
5191            "my $s = <<END;
5192line1
5193line2
5194END
5195my $x = 1;",
5196        );
5197    }
5198
5199    #[test]
5200    fn perl_lloc_package_and_require() {
5201        check_metrics::<PerlParser>(
5202            "package Foo;
5203require 5.010;
5204my $x = 1;",
5205            "foo.pl",
5206            |metric| {
5207                insta::assert_json_snapshot!(metric.loc, @r#"
5208                {
5209                  "sloc": 3.0,
5210                  "ploc": 3.0,
5211                  "lloc": 3.0,
5212                  "cloc": 0.0,
5213                  "blank": 0.0,
5214                  "sloc_average": 3.0,
5215                  "ploc_average": 3.0,
5216                  "lloc_average": 3.0,
5217                  "cloc_average": 0.0,
5218                  "blank_average": 0.0,
5219                  "sloc_min": 3.0,
5220                  "sloc_max": 3.0,
5221                  "cloc_min": 0.0,
5222                  "cloc_max": 0.0,
5223                  "ploc_min": 3.0,
5224                  "ploc_max": 3.0,
5225                  "lloc_min": 3.0,
5226                  "lloc_max": 3.0,
5227                  "blank_min": 0.0,
5228                  "blank_max": 0.0
5229                }
5230                 "#);
5231            },
5232        );
5233    }
5234
5235    #[test]
5236    fn lua_blank() {
5237        check_metrics::<LuaParser>(
5238            "local x = 1
5239
5240local y = 2",
5241            "foo.lua",
5242            |metric| {
5243                assert_eq!(metric.loc.sloc(), 3.0);
5244                assert_eq!(metric.loc.ploc(), 2.0);
5245                assert_eq!(metric.loc.lloc(), 2.0);
5246                assert_eq!(metric.loc.cloc(), 0.0);
5247                assert_eq!(metric.loc.blank(), 1.0);
5248                insta::assert_json_snapshot!(metric.loc);
5249            },
5250        );
5251    }
5252
5253    #[test]
5254    fn lua_no_zero_blank() {
5255        // Blank line interleaved with code that carries trailing comments —
5256        // stresses the `blank = sloc - (ploc ∪ cloc lines)` union math.
5257        check_metrics::<LuaParser>(
5258            "local a = 1
5259local b = 2
5260
5261local c = 3 -- trailing
5262local d = 4 -- trailing
5263local e = 5",
5264            "foo.lua",
5265            |metric| {
5266                assert_eq!(metric.loc.sloc(), 6.0);
5267                assert_eq!(metric.loc.ploc(), 5.0);
5268                assert_eq!(metric.loc.cloc(), 2.0);
5269                assert_eq!(metric.loc.blank(), 1.0);
5270                insta::assert_json_snapshot!(metric.loc);
5271            },
5272        );
5273    }
5274
5275    #[test]
5276    fn lua_blank_zero_sanity() {
5277        // Sanity check: blank must report 0, never go negative, when the
5278        // input has no blank lines.
5279        check_metrics::<LuaParser>(
5280            "local x = 1
5281local y = 2",
5282            "foo.lua",
5283            |metric| {
5284                assert_eq!(metric.loc.sloc(), 2.0);
5285                assert_eq!(metric.loc.ploc(), 2.0);
5286                assert_eq!(metric.loc.lloc(), 2.0);
5287                assert_eq!(metric.loc.cloc(), 0.0);
5288                assert_eq!(metric.loc.blank(), 0.0);
5289            },
5290        );
5291    }
5292
5293    #[test]
5294    fn lua_cloc() {
5295        check_metrics::<LuaParser>(
5296            "-- single line comment
5297local x = 1
5298--[[
5299  block comment
5300  second line
5301]]",
5302            "foo.lua",
5303            |metric| {
5304                assert_eq!(metric.loc.sloc(), 6.0);
5305                assert_eq!(metric.loc.ploc(), 1.0);
5306                assert_eq!(metric.loc.lloc(), 1.0);
5307                assert_eq!(metric.loc.cloc(), 5.0);
5308                assert_eq!(metric.loc.blank(), 0.0);
5309                insta::assert_json_snapshot!(metric.loc);
5310            },
5311        );
5312    }
5313
5314    #[test]
5315    fn lua_lloc() {
5316        check_metrics::<LuaParser>(
5317            "local function f(x)
5318  if x > 0 then
5319    local y = x + 1
5320    return y
5321  end
5322  return 0
5323end",
5324            "foo.lua",
5325            |metric| {
5326                assert_eq!(metric.loc.sloc(), 7.0);
5327                assert_eq!(metric.loc.ploc(), 7.0);
5328                assert_eq!(metric.loc.lloc(), 5.0);
5329                assert_eq!(metric.loc.cloc(), 0.0);
5330                assert_eq!(metric.loc.blank(), 0.0);
5331                insta::assert_json_snapshot!(metric.loc);
5332            },
5333        );
5334    }
5335
5336    #[test]
5337    fn lua_no_string_lloc() {
5338        // Long strings spanning multiple lines must not inflate lloc.
5339        check_metrics::<LuaParser>(
5340            "local s = [[
5341  line one
5342  line two
5343]]",
5344            "foo.lua",
5345            |metric| {
5346                assert_eq!(metric.loc.sloc(), 4.0);
5347                assert_eq!(metric.loc.ploc(), 2.0);
5348                assert_eq!(metric.loc.lloc(), 1.0);
5349                assert_eq!(metric.loc.cloc(), 0.0);
5350                assert_eq!(metric.loc.blank(), 2.0);
5351                insta::assert_json_snapshot!(metric.loc);
5352            },
5353        );
5354    }
5355
5356    #[test]
5357    fn lua_no_functiondefinition_lloc() {
5358        // Anonymous function definition is an expression, not a statement.
5359        // The containing variable_declaration counts as lloc; FunctionDefinition must not.
5360        check_metrics::<LuaParser>(
5361            "local f = function(x)
5362  return x + 1
5363end",
5364            "foo.lua",
5365            |metric| {
5366                assert_eq!(metric.loc.sloc(), 3.0);
5367                assert_eq!(metric.loc.ploc(), 3.0);
5368                assert_eq!(metric.loc.lloc(), 2.0);
5369                assert_eq!(metric.loc.cloc(), 0.0);
5370                assert_eq!(metric.loc.blank(), 0.0);
5371                insta::assert_json_snapshot!(metric.loc);
5372            },
5373        );
5374    }
5375
5376    #[test]
5377    fn lua_no_elseif_lloc() {
5378        // elseif_statement must not add lloc; only if_statement does.
5379        check_metrics::<LuaParser>(
5380            "local function f(x)
5381  if x > 0 then
5382    return 1
5383  elseif x < 0 then
5384    return -1
5385  else
5386    return 0
5387  end
5388end",
5389            "foo.lua",
5390            |metric| {
5391                assert_eq!(metric.loc.sloc(), 9.0);
5392                assert_eq!(metric.loc.ploc(), 9.0);
5393                assert_eq!(metric.loc.lloc(), 5.0);
5394                assert_eq!(metric.loc.cloc(), 0.0);
5395                assert_eq!(metric.loc.blank(), 0.0);
5396                insta::assert_json_snapshot!(metric.loc);
5397            },
5398        );
5399    }
5400
5401    #[test]
5402    fn lua_no_else_lloc() {
5403        // else_statement must not add lloc.
5404        check_metrics::<LuaParser>(
5405            "local function f(x)
5406  if x > 0 then
5407    return 1
5408  else
5409    return 0
5410  end
5411end",
5412            "foo.lua",
5413            |metric| {
5414                assert_eq!(metric.loc.sloc(), 7.0);
5415                assert_eq!(metric.loc.ploc(), 7.0);
5416                assert_eq!(metric.loc.lloc(), 4.0);
5417                assert_eq!(metric.loc.cloc(), 0.0);
5418                assert_eq!(metric.loc.blank(), 0.0);
5419                insta::assert_json_snapshot!(metric.loc);
5420            },
5421        );
5422    }
5423
5424    #[test]
5425    fn lua_functiondeclaration_lloc() {
5426        // Named function declaration counts as one lloc.
5427        check_metrics::<LuaParser>(
5428            "function f()
5429  return 1
5430end",
5431            "foo.lua",
5432            |metric| {
5433                assert_eq!(metric.loc.sloc(), 3.0);
5434                assert_eq!(metric.loc.ploc(), 3.0);
5435                assert_eq!(metric.loc.lloc(), 2.0);
5436                assert_eq!(metric.loc.cloc(), 0.0);
5437                assert_eq!(metric.loc.blank(), 0.0);
5438                insta::assert_json_snapshot!(metric.loc);
5439            },
5440        );
5441    }
5442
5443    #[test]
5444    fn lua_local_function_lloc() {
5445        // local function declaration is also a function_declaration node → one lloc.
5446        check_metrics::<LuaParser>(
5447            "local function g()
5448  return 2
5449end",
5450            "foo.lua",
5451            |metric| {
5452                assert_eq!(metric.loc.sloc(), 3.0);
5453                assert_eq!(metric.loc.ploc(), 3.0);
5454                assert_eq!(metric.loc.lloc(), 2.0);
5455                assert_eq!(metric.loc.cloc(), 0.0);
5456                assert_eq!(metric.loc.blank(), 0.0);
5457                insta::assert_json_snapshot!(metric.loc);
5458            },
5459        );
5460    }
5461
5462    #[test]
5463    fn lua_for_numeric_lloc() {
5464        check_metrics::<LuaParser>(
5465            "for i = 1, 10 do
5466  print(i)
5467end",
5468            "foo.lua",
5469            |metric| {
5470                assert_eq!(metric.loc.sloc(), 3.0);
5471                assert_eq!(metric.loc.ploc(), 3.0);
5472                assert_eq!(metric.loc.lloc(), 1.0);
5473                assert_eq!(metric.loc.cloc(), 0.0);
5474                assert_eq!(metric.loc.blank(), 0.0);
5475                insta::assert_json_snapshot!(metric.loc);
5476            },
5477        );
5478    }
5479
5480    #[test]
5481    fn lua_for_generic_lloc() {
5482        check_metrics::<LuaParser>(
5483            "for k, v in pairs(t) do
5484  print(k, v)
5485end",
5486            "foo.lua",
5487            |metric| {
5488                assert_eq!(metric.loc.sloc(), 3.0);
5489                assert_eq!(metric.loc.ploc(), 3.0);
5490                assert_eq!(metric.loc.lloc(), 1.0);
5491                assert_eq!(metric.loc.cloc(), 0.0);
5492                assert_eq!(metric.loc.blank(), 0.0);
5493                insta::assert_json_snapshot!(metric.loc);
5494            },
5495        );
5496    }
5497
5498    #[test]
5499    fn lua_repeat_lloc() {
5500        check_metrics::<LuaParser>(
5501            "local i = 0
5502repeat
5503  i = i + 1
5504until i >= 10",
5505            "foo.lua",
5506            |metric| {
5507                assert_eq!(metric.loc.sloc(), 4.0);
5508                assert_eq!(metric.loc.ploc(), 4.0);
5509                assert_eq!(metric.loc.lloc(), 3.0);
5510                assert_eq!(metric.loc.cloc(), 0.0);
5511                assert_eq!(metric.loc.blank(), 0.0);
5512                insta::assert_json_snapshot!(metric.loc);
5513            },
5514        );
5515    }
5516
5517    #[test]
5518    fn lua_local_decl_lloc() {
5519        check_metrics::<LuaParser>(
5520            "local x = 1
5521local y, z = 2, 3",
5522            "foo.lua",
5523            |metric| {
5524                assert_eq!(metric.loc.sloc(), 2.0);
5525                assert_eq!(metric.loc.ploc(), 2.0);
5526                assert_eq!(metric.loc.lloc(), 2.0);
5527                assert_eq!(metric.loc.cloc(), 0.0);
5528                assert_eq!(metric.loc.blank(), 0.0);
5529                insta::assert_json_snapshot!(metric.loc);
5530            },
5531        );
5532    }
5533
5534    #[test]
5535    fn lua_function_call_lloc() {
5536        // Standalone function calls have no expression_statement wrapper in Lua.
5537        // They fall to the `_` branch → counted as ploc, not lloc.
5538        check_metrics::<LuaParser>(
5539            "print(\"hello\")
5540local x = 1",
5541            "foo.lua",
5542            |metric| {
5543                assert_eq!(metric.loc.sloc(), 2.0);
5544                assert_eq!(metric.loc.ploc(), 2.0);
5545                assert_eq!(metric.loc.lloc(), 1.0);
5546                assert_eq!(metric.loc.cloc(), 0.0);
5547                assert_eq!(metric.loc.blank(), 0.0);
5548                insta::assert_json_snapshot!(metric.loc);
5549            },
5550        );
5551    }
5552
5553    #[test]
5554    fn lua_toplevel_assignment_lloc() {
5555        // Bare `x = 1` at chunk level: parent is Chunk, not VariableDeclaration,
5556        // so the parent-guard correctly counts it as 1 lloc.
5557        check_metrics::<LuaParser>(
5558            "x = 1
5559y, z = 2, 3",
5560            "foo.lua",
5561            |metric| {
5562                assert_eq!(metric.loc.sloc(), 2.0);
5563                assert_eq!(metric.loc.ploc(), 2.0);
5564                assert_eq!(metric.loc.lloc(), 2.0);
5565                assert_eq!(metric.loc.cloc(), 0.0);
5566                assert_eq!(metric.loc.blank(), 0.0);
5567                insta::assert_json_snapshot!(metric.loc);
5568            },
5569        );
5570    }
5571
5572    #[test]
5573    fn tsx_basic_loc() {
5574        check_metrics::<TsxParser>(
5575            "// A simple utility function
5576            function add(a: number, b: number): number {
5577                /* multi-line
5578                   comment */
5579                return a + b;
5580            }
5581
5582            const greet = (name: string) => {
5583                return `Hello, ${name}`;
5584            };",
5585            "foo.tsx",
5586            |metric| {
5587                insta::assert_json_snapshot!(
5588                    metric.loc,
5589                    @r###"
5590                    {
5591                      "sloc": 10.0,
5592                      "ploc": 6.0,
5593                      "lloc": 4.0,
5594                      "cloc": 3.0,
5595                      "blank": 1.0,
5596                      "sloc_average": 3.3333333333333335,
5597                      "ploc_average": 2.0,
5598                      "lloc_average": 1.3333333333333333,
5599                      "cloc_average": 1.0,
5600                      "blank_average": 0.3333333333333333,
5601                      "sloc_min": 3.0,
5602                      "sloc_max": 5.0,
5603                      "cloc_min": 0.0,
5604                      "cloc_max": 2.0,
5605                      "ploc_min": 3.0,
5606                      "ploc_max": 3.0,
5607                      "lloc_min": 2.0,
5608                      "lloc_max": 2.0,
5609                      "blank_min": 0.0,
5610                      "blank_max": 0.0
5611                    }"###
5612                );
5613            },
5614        );
5615    }
5616
5617    #[test]
5618    fn typescript_basic_loc() {
5619        check_metrics::<TypescriptParser>(
5620            "// Line comment
5621            /* Block
5622               comment */
5623            function greet(name: string): string {
5624                return `Hello, ${name}`;
5625            }
5626
5627            const add = (a: number, b: number): number => a + b;",
5628            "foo.ts",
5629            |metric| {
5630                insta::assert_json_snapshot!(
5631                    metric.loc,
5632                    @r###"
5633                    {
5634                      "sloc": 8.0,
5635                      "ploc": 4.0,
5636                      "lloc": 2.0,
5637                      "cloc": 3.0,
5638                      "blank": 1.0,
5639                      "sloc_average": 2.6666666666666665,
5640                      "ploc_average": 1.3333333333333333,
5641                      "lloc_average": 0.6666666666666666,
5642                      "cloc_average": 1.0,
5643                      "blank_average": 0.3333333333333333,
5644                      "sloc_min": 1.0,
5645                      "sloc_max": 3.0,
5646                      "cloc_min": 0.0,
5647                      "cloc_max": 0.0,
5648                      "ploc_min": 1.0,
5649                      "ploc_max": 3.0,
5650                      "lloc_min": 0.0,
5651                      "lloc_max": 2.0,
5652                      "blank_min": 0.0,
5653                      "blank_max": 0.0
5654                    }"###
5655                );
5656            },
5657        );
5658    }
5659
5660    #[test]
5661    fn csharp_comments() {
5662        check_metrics::<CsharpParser>(
5663            "for (int i = 0; i < 100; i++) {
5664               // Print hello
5665               System.Console.WriteLine(\"hello\");
5666               /// XML doc comment
5667               System.Console.WriteLine(\"hello\");
5668             }",
5669            "foo.cs",
5670            |metric| {
5671                assert_eq!(metric.loc.sloc(), 6.0);
5672                assert_eq!(metric.loc.ploc(), 4.0);
5673                assert_eq!(metric.loc.lloc(), 3.0);
5674                assert_eq!(metric.loc.cloc(), 2.0);
5675                assert_eq!(metric.loc.blank(), 0.0);
5676                insta::assert_json_snapshot!(metric.loc);
5677            },
5678        );
5679    }
5680
5681    #[test]
5682    fn csharp_blank() {
5683        check_metrics::<CsharpParser>(
5684            "int x = 1;
5685
5686
5687            int y = 2;",
5688            "foo.cs",
5689            |metric| {
5690                assert_eq!(metric.loc.sloc(), 4.0);
5691                assert_eq!(metric.loc.ploc(), 2.0);
5692                assert_eq!(metric.loc.lloc(), 2.0);
5693                assert_eq!(metric.loc.cloc(), 0.0);
5694                assert_eq!(metric.loc.blank(), 2.0);
5695                insta::assert_json_snapshot!(metric.loc);
5696            },
5697        );
5698    }
5699
5700    #[test]
5701    fn csharp_sloc() {
5702        check_metrics::<CsharpParser>(
5703            "for (int i = 0; i < 100; i++) {
5704               System.Console.WriteLine(i);
5705             }",
5706            "foo.cs",
5707            |metric| {
5708                assert_eq!(metric.loc.sloc(), 3.0);
5709                assert_eq!(metric.loc.ploc(), 3.0);
5710                assert_eq!(metric.loc.lloc(), 2.0);
5711                assert_eq!(metric.loc.cloc(), 0.0);
5712                assert_eq!(metric.loc.blank(), 0.0);
5713                insta::assert_json_snapshot!(metric.loc);
5714            },
5715        );
5716    }
5717
5718    #[test]
5719    fn csharp_module_sloc() {
5720        check_metrics::<CsharpParser>(
5721            "namespace HelloWorld {
5722              class Program { }
5723            }",
5724            "foo.cs",
5725            |metric| {
5726                assert_eq!(metric.loc.sloc(), 3.0);
5727                assert_eq!(metric.loc.ploc(), 3.0);
5728                assert_eq!(metric.loc.lloc(), 0.0);
5729                assert_eq!(metric.loc.cloc(), 0.0);
5730                assert_eq!(metric.loc.blank(), 0.0);
5731                insta::assert_json_snapshot!(metric.loc);
5732            },
5733        );
5734    }
5735
5736    #[test]
5737    fn csharp_single_ploc() {
5738        check_metrics::<CsharpParser>("int x = 1;", "foo.cs", |metric| {
5739            assert_eq!(metric.loc.sloc(), 1.0);
5740            assert_eq!(metric.loc.ploc(), 1.0);
5741            assert_eq!(metric.loc.lloc(), 1.0);
5742            assert_eq!(metric.loc.cloc(), 0.0);
5743            assert_eq!(metric.loc.blank(), 0.0);
5744            insta::assert_json_snapshot!(metric.loc);
5745        });
5746    }
5747
5748    #[test]
5749    fn csharp_simple_ploc() {
5750        check_metrics::<CsharpParser>(
5751            "for (int i = 0; i < 100; i++) {
5752               System.Console.WriteLine(i);
5753             }",
5754            "foo.cs",
5755            |metric| {
5756                assert_eq!(metric.loc.sloc(), 3.0);
5757                assert_eq!(metric.loc.ploc(), 3.0);
5758                assert_eq!(metric.loc.lloc(), 2.0);
5759                assert_eq!(metric.loc.cloc(), 0.0);
5760                assert_eq!(metric.loc.blank(), 0.0);
5761                insta::assert_json_snapshot!(metric.loc);
5762            },
5763        );
5764    }
5765
5766    #[test]
5767    fn csharp_multi_ploc() {
5768        check_metrics::<CsharpParser>(
5769            "int x = 1;
5770            for (int i = 0; i < 100; i++) {
5771               System.Console.WriteLine(i);
5772             }",
5773            "foo.cs",
5774            |metric| {
5775                assert_eq!(metric.loc.sloc(), 4.0);
5776                assert_eq!(metric.loc.ploc(), 4.0);
5777                assert_eq!(metric.loc.lloc(), 3.0);
5778                assert_eq!(metric.loc.cloc(), 0.0);
5779                assert_eq!(metric.loc.blank(), 0.0);
5780                insta::assert_json_snapshot!(metric.loc);
5781            },
5782        );
5783    }
5784
5785    #[test]
5786    fn csharp_single_statement_lloc() {
5787        check_metrics::<CsharpParser>("int max = 10;", "foo.cs", |metric| {
5788            assert_eq!(metric.loc.sloc(), 1.0);
5789            assert_eq!(metric.loc.ploc(), 1.0);
5790            assert_eq!(metric.loc.lloc(), 1.0);
5791            assert_eq!(metric.loc.cloc(), 0.0);
5792            assert_eq!(metric.loc.blank(), 0.0);
5793            insta::assert_json_snapshot!(metric.loc);
5794        });
5795    }
5796
5797    #[test]
5798    fn csharp_for_lloc() {
5799        check_metrics::<CsharpParser>(
5800            "for (int i = 0; i < 10; i++) {
5801                System.Console.WriteLine(i);
5802            }",
5803            "foo.cs",
5804            |metric| {
5805                assert_eq!(metric.loc.sloc(), 3.0);
5806                assert_eq!(metric.loc.ploc(), 3.0);
5807                assert_eq!(metric.loc.lloc(), 2.0);
5808                assert_eq!(metric.loc.cloc(), 0.0);
5809                assert_eq!(metric.loc.blank(), 0.0);
5810                insta::assert_json_snapshot!(metric.loc);
5811            },
5812        );
5813    }
5814
5815    #[test]
5816    fn csharp_foreach_lloc() {
5817        check_metrics::<CsharpParser>(
5818            "foreach (var item in items) {
5819                System.Console.WriteLine(item);
5820            }",
5821            "foo.cs",
5822            |metric| {
5823                assert_eq!(metric.loc.sloc(), 3.0);
5824                assert_eq!(metric.loc.ploc(), 3.0);
5825                assert_eq!(metric.loc.lloc(), 2.0);
5826                assert_eq!(metric.loc.cloc(), 0.0);
5827                assert_eq!(metric.loc.blank(), 0.0);
5828                insta::assert_json_snapshot!(metric.loc);
5829            },
5830        );
5831    }
5832
5833    #[test]
5834    fn csharp_while_lloc() {
5835        check_metrics::<CsharpParser>(
5836            "int i = 0;
5837            while (i < 10) {
5838                i++;
5839            }",
5840            "foo.cs",
5841            |metric| {
5842                assert_eq!(metric.loc.sloc(), 4.0);
5843                assert_eq!(metric.loc.ploc(), 4.0);
5844                assert_eq!(metric.loc.lloc(), 3.0);
5845                assert_eq!(metric.loc.cloc(), 0.0);
5846                assert_eq!(metric.loc.blank(), 0.0);
5847                insta::assert_json_snapshot!(metric.loc);
5848            },
5849        );
5850    }
5851
5852    #[test]
5853    fn csharp_do_while_lloc() {
5854        check_metrics::<CsharpParser>(
5855            "int i = 0;
5856            do {
5857                i++;
5858            } while (i < 10);",
5859            "foo.cs",
5860            |metric| {
5861                assert_eq!(metric.loc.sloc(), 4.0);
5862                assert_eq!(metric.loc.ploc(), 4.0);
5863                assert_eq!(metric.loc.lloc(), 3.0);
5864                assert_eq!(metric.loc.cloc(), 0.0);
5865                assert_eq!(metric.loc.blank(), 0.0);
5866                insta::assert_json_snapshot!(metric.loc);
5867            },
5868        );
5869    }
5870
5871    #[test]
5872    fn csharp_switch_lloc() {
5873        check_metrics::<CsharpParser>(
5874            "switch (x) {
5875                case 1: System.Console.WriteLine(1); break;
5876                case 2: System.Console.WriteLine(2); break;
5877                default: System.Console.WriteLine(0); break;
5878            }
5879            string s = x switch { 1 => \"one\", _ => \"other\" };",
5880            "foo.cs",
5881            |metric| {
5882                assert_eq!(metric.loc.sloc(), 6.0);
5883                assert_eq!(metric.loc.ploc(), 6.0);
5884                assert_eq!(metric.loc.lloc(), 8.0);
5885                assert_eq!(metric.loc.cloc(), 0.0);
5886                assert_eq!(metric.loc.blank(), 0.0);
5887                insta::assert_json_snapshot!(metric.loc);
5888            },
5889        );
5890    }
5891
5892    #[test]
5893    fn csharp_continue_lloc() {
5894        check_metrics::<CsharpParser>(
5895            "for (int i = 0; i < 10; i++) {
5896                if (i == 5) continue;
5897                System.Console.WriteLine(i);
5898            }",
5899            "foo.cs",
5900            |metric| {
5901                assert_eq!(metric.loc.sloc(), 4.0);
5902                assert_eq!(metric.loc.ploc(), 4.0);
5903                assert_eq!(metric.loc.lloc(), 4.0);
5904                assert_eq!(metric.loc.cloc(), 0.0);
5905                assert_eq!(metric.loc.blank(), 0.0);
5906                insta::assert_json_snapshot!(metric.loc);
5907            },
5908        );
5909    }
5910
5911    #[test]
5912    fn csharp_try_lloc() {
5913        check_metrics::<CsharpParser>(
5914            "try {
5915                System.Console.WriteLine(\"try\");
5916            } catch (System.Exception e) {
5917                throw new System.Exception(\"caught\");
5918            } finally {
5919                System.Console.WriteLine(\"done\");
5920            }",
5921            "foo.cs",
5922            |metric| {
5923                assert_eq!(metric.loc.sloc(), 7.0);
5924                assert_eq!(metric.loc.ploc(), 7.0);
5925                assert_eq!(metric.loc.lloc(), 4.0);
5926                assert_eq!(metric.loc.cloc(), 0.0);
5927                assert_eq!(metric.loc.blank(), 0.0);
5928                insta::assert_json_snapshot!(metric.loc);
5929            },
5930        );
5931    }
5932
5933    #[test]
5934    fn csharp_class_loc() {
5935        check_metrics::<CsharpParser>(
5936            "class A {
5937                int x;
5938                public void M() {
5939                    System.Console.WriteLine(x);
5940                }
5941            }",
5942            "foo.cs",
5943            |metric| {
5944                assert_eq!(metric.loc.sloc(), 6.0);
5945                assert_eq!(metric.loc.ploc(), 6.0);
5946                assert_eq!(metric.loc.lloc(), 1.0);
5947                assert_eq!(metric.loc.cloc(), 0.0);
5948                assert_eq!(metric.loc.blank(), 0.0);
5949                insta::assert_json_snapshot!(metric.loc);
5950            },
5951        );
5952    }
5953
5954    #[test]
5955    fn csharp_expressions_lloc() {
5956        check_metrics::<CsharpParser>(
5957            "int a = 1;
5958            int b = 2;
5959            int c = a + b;
5960            System.Console.WriteLine(c);",
5961            "foo.cs",
5962            |metric| {
5963                assert_eq!(metric.loc.sloc(), 4.0);
5964                assert_eq!(metric.loc.ploc(), 4.0);
5965                assert_eq!(metric.loc.lloc(), 4.0);
5966                assert_eq!(metric.loc.cloc(), 0.0);
5967                assert_eq!(metric.loc.blank(), 0.0);
5968                insta::assert_json_snapshot!(metric.loc);
5969            },
5970        );
5971    }
5972
5973    #[test]
5974    fn csharp_statement_inline_loc() {
5975        check_metrics::<CsharpParser>(
5976            "if (x > 0) System.Console.WriteLine(x);",
5977            "foo.cs",
5978            |metric| {
5979                assert_eq!(metric.loc.sloc(), 1.0);
5980                assert_eq!(metric.loc.ploc(), 1.0);
5981                assert_eq!(metric.loc.lloc(), 2.0);
5982                assert_eq!(metric.loc.cloc(), 0.0);
5983                assert_eq!(metric.loc.blank(), 0.0);
5984                insta::assert_json_snapshot!(metric.loc);
5985            },
5986        );
5987    }
5988
5989    #[test]
5990    fn csharp_general_loc() {
5991        check_metrics::<CsharpParser>(
5992            "using System;
5993            namespace Demo {
5994                class A {
5995                    public void M() {
5996                        Console.WriteLine(\"hi\");
5997                    }
5998                }
5999                class B {
6000                    public int N() { return 0; }
6001                }
6002            }",
6003            "foo.cs",
6004            |metric| {
6005                assert_eq!(metric.loc.sloc(), 11.0);
6006                assert_eq!(metric.loc.ploc(), 11.0);
6007                assert_eq!(metric.loc.lloc(), 2.0);
6008                assert_eq!(metric.loc.cloc(), 0.0);
6009                assert_eq!(metric.loc.blank(), 0.0);
6010                insta::assert_json_snapshot!(metric.loc);
6011            },
6012        );
6013    }
6014
6015    #[test]
6016    fn csharp_using_lloc() {
6017        // EC11 — `using_directive` does not bump LLOC; `using_statement`
6018        // (block form) and the C# 8 simple-using local-declaration
6019        // (`using var x = ...;`) both do, the latter via the standard
6020        // `LocalDeclarationStatement` path.
6021        check_metrics::<CsharpParser>(
6022            "using System;
6023            using System.IO;
6024            class A {
6025                public void M() {
6026                    using (var s = File.OpenRead(\"x\")) {
6027                        Console.WriteLine(s);
6028                    }
6029                    using var t = File.OpenRead(\"y\");
6030                    Console.WriteLine(t);
6031                }
6032            }",
6033            "foo.cs",
6034            |metric| {
6035                assert_eq!(metric.loc.sloc(), 11.0);
6036                assert_eq!(metric.loc.ploc(), 11.0);
6037                assert_eq!(metric.loc.lloc(), 4.0);
6038                assert_eq!(metric.loc.cloc(), 0.0);
6039                assert_eq!(metric.loc.blank(), 0.0);
6040                insta::assert_json_snapshot!(metric.loc);
6041            },
6042        );
6043    }
6044
6045    #[test]
6046    fn kotlin_loc_basic() {
6047        check_metrics::<KotlinParser>(
6048            "// A simple function
6049            fun greet(name: String): String {
6050                val greeting = \"Hello, \" + name
6051                if (name.isEmpty()) {
6052                    return \"Hello, World!\"
6053                }
6054                return greeting
6055            }",
6056            "foo.kt",
6057            |metric| {
6058                insta::assert_json_snapshot!(
6059                    metric.loc,
6060                    @r###"
6061                    {
6062                      "sloc": 8.0,
6063                      "ploc": 7.0,
6064                      "lloc": 4.0,
6065                      "cloc": 1.0,
6066                      "blank": 0.0,
6067                      "sloc_average": 4.0,
6068                      "ploc_average": 3.5,
6069                      "lloc_average": 2.0,
6070                      "cloc_average": 0.5,
6071                      "blank_average": 0.0,
6072                      "sloc_min": 7.0,
6073                      "sloc_max": 7.0,
6074                      "cloc_min": 0.0,
6075                      "cloc_max": 0.0,
6076                      "ploc_min": 7.0,
6077                      "ploc_max": 7.0,
6078                      "lloc_min": 4.0,
6079                      "lloc_max": 4.0,
6080                      "blank_min": 0.0,
6081                      "blank_max": 0.0
6082                    }
6083                    "###
6084                );
6085            },
6086        );
6087    }
6088
6089    #[test]
6090    fn kotlin_loc_bare_expression() {
6091        check_metrics::<KotlinParser>(
6092            "fun main() {
6093                val x = 42
6094                println(x)
6095                listOf(1, 2, 3).forEach { println(it) }
6096            }",
6097            "foo.kt",
6098            |metric| {
6099                // lloc should count: val x = 42 (PropertyDeclaration, +1)
6100                // + println(x) (CallExpression, parent=Block, +1)
6101                // + listOf(1, 2, 3).forEach { ... } (CallExpression, parent=Block, +1) = 3
6102                insta::assert_json_snapshot!(
6103                    metric.loc,
6104                    @r#"
6105                {
6106                  "sloc": 5.0,
6107                  "ploc": 5.0,
6108                  "lloc": 3.0,
6109                  "cloc": 0.0,
6110                  "blank": 0.0,
6111                  "sloc_average": 2.5,
6112                  "ploc_average": 2.5,
6113                  "lloc_average": 1.5,
6114                  "cloc_average": 0.0,
6115                  "blank_average": 0.0,
6116                  "sloc_min": 5.0,
6117                  "sloc_max": 5.0,
6118                  "cloc_min": 0.0,
6119                  "cloc_max": 0.0,
6120                  "ploc_min": 5.0,
6121                  "ploc_max": 5.0,
6122                  "lloc_min": 3.0,
6123                  "lloc_max": 3.0,
6124                  "blank_min": 0.0,
6125                  "blank_max": 0.0
6126                }
6127                "#
6128                );
6129            },
6130        );
6131    }
6132
6133    #[test]
6134    fn bash_loc() {
6135        check_metrics::<BashParser>(
6136            "#!/bin/bash
6137# This is a comment
6138f() {
6139    echo 'hello'
6140}
6141
6142# Another comment
6143f",
6144            "foo.sh",
6145            |metric| {
6146                assert_eq!(metric.loc.sloc(), 8.0);
6147                assert_eq!(metric.loc.ploc(), 4.0);
6148                assert_eq!(metric.loc.lloc(), 3.0);
6149                assert_eq!(metric.loc.cloc(), 3.0);
6150                assert_eq!(metric.loc.blank(), 1.0);
6151                insta::assert_json_snapshot!(metric.loc);
6152            },
6153        );
6154    }
6155
6156    // CRLF regression tests: metrics must be identical regardless of line ending style.
6157    // These also serve as canaries for tree-sitter row-counting behaviour with \r bytes.
6158
6159    #[test]
6160    fn python_cloc_crlf_matches_lf() {
6161        check_metrics::<PythonParser>("# comment\nx = 1", "foo.py", |m| {
6162            assert_eq!(m.loc.cloc(), 1.0);
6163            assert_eq!(m.loc.ploc(), 1.0);
6164            assert_eq!(m.loc.sloc(), 2.0);
6165            assert_eq!(m.loc.blank(), 0.0);
6166        });
6167        check_metrics::<PythonParser>("# comment\r\nx = 1", "foo.py", |m| {
6168            assert_eq!(m.loc.cloc(), 1.0);
6169            assert_eq!(m.loc.ploc(), 1.0);
6170            assert_eq!(m.loc.sloc(), 2.0);
6171            assert_eq!(m.loc.blank(), 0.0);
6172        });
6173        // Lone-CR (old Mac line endings) is the true canary: without CR normalisation,
6174        // tree-sitter 0.26.8 only advances its row counter on \n, collapsing all content
6175        // onto row 0 and producing wrong sloc/cloc metrics.
6176        check_metrics::<PythonParser>("# comment\rx = 1", "foo.py", |m| {
6177            assert_eq!(m.loc.cloc(), 1.0);
6178            assert_eq!(m.loc.ploc(), 1.0);
6179            assert_eq!(m.loc.sloc(), 2.0);
6180            assert_eq!(m.loc.blank(), 0.0);
6181        });
6182    }
6183
6184    #[test]
6185    fn python_blank_crlf_matches_lf() {
6186        check_metrics::<PythonParser>("# comment\n\nx = 1", "foo.py", |m| {
6187            assert_eq!(m.loc.blank(), 1.0);
6188        });
6189        check_metrics::<PythonParser>("# comment\r\n\r\nx = 1", "foo.py", |m| {
6190            assert_eq!(m.loc.blank(), 1.0);
6191        });
6192        // Lone-CR: without normalisation the blank \r line stays on row 0 and is not counted.
6193        check_metrics::<PythonParser>("# comment\r\rx = 1", "foo.py", |m| {
6194            assert_eq!(m.loc.blank(), 1.0);
6195        });
6196    }
6197
6198    #[test]
6199    fn rust_cloc_crlf_matches_lf() {
6200        check_metrics::<RustParser>(
6201            "fn f() {\n    // comment\n    let x = 1;\n}",
6202            "foo.rs",
6203            |m| {
6204                assert_eq!(m.loc.cloc(), 1.0);
6205                assert_eq!(m.loc.sloc(), 4.0);
6206            },
6207        );
6208        check_metrics::<RustParser>(
6209            "fn f() {\r\n    // comment\r\n    let x = 1;\r\n}",
6210            "foo.rs",
6211            |m| {
6212                assert_eq!(m.loc.cloc(), 1.0);
6213                assert_eq!(m.loc.sloc(), 4.0);
6214            },
6215        );
6216        // Lone-CR: without normalisation, tree-sitter 0.26.8 only advances its row counter on
6217        // \n, so all content collapses onto row 0 and sloc becomes 1 instead of 4.
6218        check_metrics::<RustParser>(
6219            "fn f() {\r    // comment\r    let x = 1;\r}",
6220            "foo.rs",
6221            |m| {
6222                assert_eq!(m.loc.cloc(), 1.0);
6223                assert_eq!(m.loc.sloc(), 4.0);
6224            },
6225        );
6226    }
6227
6228    #[test]
6229    fn tcl_blank() {
6230        check_metrics::<TclParser>("set x 1\n\nset y 2", "foo.tcl", |metric| {
6231            assert_eq!(metric.loc.sloc(), 3.0);
6232            assert_eq!(metric.loc.ploc(), 2.0);
6233            assert_eq!(metric.loc.lloc(), 2.0);
6234            assert_eq!(metric.loc.cloc(), 0.0);
6235            assert_eq!(metric.loc.blank(), 1.0);
6236            insta::assert_json_snapshot!(metric.loc);
6237        });
6238    }
6239
6240    #[test]
6241    fn tcl_no_zero_blank() {
6242        // Blank line interleaved with code that carries trailing comments —
6243        // ensures the `blank = sloc - (ploc ∪ cloc lines)` union math holds
6244        // when code and comment lines coincide.
6245        check_metrics::<TclParser>(
6246            "set a 1\nset b 2\n\nset c 3 ;# trailing\nset d 4 ;# trailing\nset e 5",
6247            "foo.tcl",
6248            |metric| {
6249                assert_eq!(metric.loc.sloc(), 6.0);
6250                assert_eq!(metric.loc.ploc(), 5.0);
6251                assert_eq!(metric.loc.cloc(), 2.0);
6252                assert_eq!(metric.loc.blank(), 1.0);
6253            },
6254        );
6255    }
6256
6257    #[test]
6258    fn tcl_cloc() {
6259        check_metrics::<TclParser>("# This is a comment\nset x 1", "foo.tcl", |metric| {
6260            assert_eq!(metric.loc.sloc(), 2.0);
6261            assert_eq!(metric.loc.ploc(), 2.0);
6262            assert_eq!(metric.loc.lloc(), 1.0);
6263            assert_eq!(metric.loc.cloc(), 1.0);
6264            assert_eq!(metric.loc.blank(), 0.0);
6265            insta::assert_json_snapshot!(metric.loc);
6266        });
6267    }
6268
6269    #[test]
6270    fn tcl_lloc() {
6271        check_metrics::<TclParser>(
6272            "proc f {x} {
6273    while {$x > 0} {
6274        if {$x > 10} {
6275            set x [expr {$x - 1}]
6276        }
6277    }
6278}",
6279            "foo.tcl",
6280            |metric| {
6281                assert_eq!(metric.loc.sloc(), 7.0);
6282                assert_eq!(metric.loc.ploc(), 7.0);
6283                assert_eq!(metric.loc.lloc(), 4.0);
6284                assert_eq!(metric.loc.cloc(), 0.0);
6285                assert_eq!(metric.loc.blank(), 0.0);
6286                insta::assert_json_snapshot!(metric.loc);
6287            },
6288        );
6289    }
6290
6291    #[test]
6292    fn tcl_no_command_substitution_lloc() {
6293        // `string toupper` inside [...] is a sub-expression; only `puts` is top-level.
6294        check_metrics::<TclParser>("puts [string toupper x]", "foo.tcl", |metric| {
6295            assert_eq!(metric.loc.sloc(), 1.0);
6296            assert_eq!(metric.loc.ploc(), 1.0);
6297            assert_eq!(metric.loc.lloc(), 1.0);
6298            assert_eq!(metric.loc.cloc(), 0.0);
6299            assert_eq!(metric.loc.blank(), 0.0);
6300            insta::assert_json_snapshot!(metric.loc);
6301        });
6302    }
6303
6304    #[test]
6305    fn tcl_procedure_lloc() {
6306        check_metrics::<TclParser>("proc foo {} {\n    puts hello\n}", "foo.tcl", |metric| {
6307            assert_eq!(metric.loc.sloc(), 3.0);
6308            assert_eq!(metric.loc.ploc(), 3.0);
6309            assert_eq!(metric.loc.lloc(), 2.0);
6310            assert_eq!(metric.loc.cloc(), 0.0);
6311            assert_eq!(metric.loc.blank(), 0.0);
6312            insta::assert_json_snapshot!(metric.loc);
6313        });
6314    }
6315
6316    #[test]
6317    fn tcl_if_lloc() {
6318        check_metrics::<TclParser>("if {1} {\n    puts hello\n}", "foo.tcl", |metric| {
6319            assert_eq!(metric.loc.sloc(), 3.0);
6320            assert_eq!(metric.loc.ploc(), 3.0);
6321            assert_eq!(metric.loc.lloc(), 2.0);
6322            assert_eq!(metric.loc.cloc(), 0.0);
6323            assert_eq!(metric.loc.blank(), 0.0);
6324            insta::assert_json_snapshot!(metric.loc);
6325        });
6326    }
6327
6328    #[test]
6329    fn tcl_elseif_lloc() {
6330        // if=1 lloc, elseif=1 lloc, else adds 0 lloc
6331        check_metrics::<TclParser>(
6332            "if {$x > 10} {
6333    puts big
6334} elseif {$x > 5} {
6335    puts medium
6336} else {
6337    puts small
6338}",
6339            "foo.tcl",
6340            |metric| {
6341                assert_eq!(metric.loc.sloc(), 7.0);
6342                assert_eq!(metric.loc.ploc(), 7.0);
6343                assert_eq!(metric.loc.lloc(), 5.0);
6344                assert_eq!(metric.loc.cloc(), 0.0);
6345                assert_eq!(metric.loc.blank(), 0.0);
6346                insta::assert_json_snapshot!(metric.loc);
6347            },
6348        );
6349    }
6350
6351    #[test]
6352    fn tcl_while_lloc() {
6353        check_metrics::<TclParser>(
6354            "while {$x > 0} {\n    set x [expr {$x - 1}]\n}",
6355            "foo.tcl",
6356            |metric| {
6357                assert_eq!(metric.loc.sloc(), 3.0);
6358                assert_eq!(metric.loc.ploc(), 3.0);
6359                assert_eq!(metric.loc.lloc(), 2.0);
6360                assert_eq!(metric.loc.cloc(), 0.0);
6361                assert_eq!(metric.loc.blank(), 0.0);
6362                insta::assert_json_snapshot!(metric.loc);
6363            },
6364        );
6365    }
6366
6367    #[test]
6368    fn tcl_foreach_lloc() {
6369        check_metrics::<TclParser>(
6370            "foreach item {a b c} {\n    puts $item\n}",
6371            "foo.tcl",
6372            |metric| {
6373                assert_eq!(metric.loc.sloc(), 3.0);
6374                assert_eq!(metric.loc.ploc(), 3.0);
6375                assert_eq!(metric.loc.lloc(), 2.0);
6376                assert_eq!(metric.loc.cloc(), 0.0);
6377                assert_eq!(metric.loc.blank(), 0.0);
6378                insta::assert_json_snapshot!(metric.loc);
6379            },
6380        );
6381    }
6382
6383    #[test]
6384    fn tcl_set_lloc() {
6385        check_metrics::<TclParser>("set x 42", "foo.tcl", |metric| {
6386            assert_eq!(metric.loc.sloc(), 1.0);
6387            assert_eq!(metric.loc.ploc(), 1.0);
6388            assert_eq!(metric.loc.lloc(), 1.0);
6389            assert_eq!(metric.loc.cloc(), 0.0);
6390            assert_eq!(metric.loc.blank(), 0.0);
6391            insta::assert_json_snapshot!(metric.loc);
6392        });
6393    }
6394
6395    #[test]
6396    fn tcl_global_lloc() {
6397        check_metrics::<TclParser>("global x", "foo.tcl", |metric| {
6398            assert_eq!(metric.loc.sloc(), 1.0);
6399            assert_eq!(metric.loc.ploc(), 1.0);
6400            assert_eq!(metric.loc.lloc(), 1.0);
6401            assert_eq!(metric.loc.cloc(), 0.0);
6402            assert_eq!(metric.loc.blank(), 0.0);
6403            insta::assert_json_snapshot!(metric.loc);
6404        });
6405    }
6406
6407    #[test]
6408    fn tcl_try_catch_lloc() {
6409        // try=1 lloc; catch command=1 lloc; commands inside bodies count separately
6410        check_metrics::<TclParser>(
6411            "catch {
6412    set x 1
6413} result
6414try {
6415    set y 2
6416} on error {msg} {
6417    puts $msg
6418}",
6419            "foo.tcl",
6420            |metric| {
6421                assert_eq!(metric.loc.sloc(), 8.0);
6422                assert_eq!(metric.loc.ploc(), 8.0);
6423                assert_eq!(metric.loc.lloc(), 5.0);
6424                assert_eq!(metric.loc.cloc(), 0.0);
6425                assert_eq!(metric.loc.blank(), 0.0);
6426                insta::assert_json_snapshot!(metric.loc);
6427            },
6428        );
6429    }
6430
6431    #[test]
6432    fn tcl_namespace_lloc() {
6433        check_metrics::<TclParser>(
6434            "namespace eval myns {\n    set x 1\n}",
6435            "foo.tcl",
6436            |metric| {
6437                assert_eq!(metric.loc.sloc(), 3.0);
6438                assert_eq!(metric.loc.ploc(), 3.0);
6439                assert_eq!(metric.loc.lloc(), 2.0);
6440                assert_eq!(metric.loc.cloc(), 0.0);
6441                assert_eq!(metric.loc.blank(), 0.0);
6442                insta::assert_json_snapshot!(metric.loc);
6443            },
6444        );
6445    }
6446
6447    #[test]
6448    fn tcl_regexp_lloc() {
6449        check_metrics::<TclParser>("regexp {^[0-9]+$} $x", "foo.tcl", |metric| {
6450            assert_eq!(metric.loc.sloc(), 1.0);
6451            assert_eq!(metric.loc.ploc(), 1.0);
6452            assert_eq!(metric.loc.lloc(), 1.0);
6453            assert_eq!(metric.loc.cloc(), 0.0);
6454            assert_eq!(metric.loc.blank(), 0.0);
6455            insta::assert_json_snapshot!(metric.loc);
6456        });
6457    }
6458
6459    #[test]
6460    fn tcl_expr_cmd_lloc() {
6461        check_metrics::<TclParser>("expr {1 + 2}", "foo.tcl", |metric| {
6462            assert_eq!(metric.loc.sloc(), 1.0);
6463            assert_eq!(metric.loc.ploc(), 1.0);
6464            assert_eq!(metric.loc.lloc(), 1.0);
6465            assert_eq!(metric.loc.cloc(), 0.0);
6466            assert_eq!(metric.loc.blank(), 0.0);
6467            insta::assert_json_snapshot!(metric.loc);
6468        });
6469    }
6470
6471    #[test]
6472    fn tcl_no_expr_cmd_substitution_lloc() {
6473        // `expr` inside [...] is a sub-expression, not a statement; only `set` counts.
6474        check_metrics::<TclParser>("set x [expr {1 + 2}]", "foo.tcl", |metric| {
6475            assert_eq!(metric.loc.sloc(), 1.0);
6476            assert_eq!(metric.loc.ploc(), 1.0);
6477            assert_eq!(metric.loc.lloc(), 1.0);
6478            assert_eq!(metric.loc.cloc(), 0.0);
6479            assert_eq!(metric.loc.blank(), 0.0);
6480            insta::assert_json_snapshot!(metric.loc);
6481        });
6482    }
6483
6484    #[test]
6485    fn tcl_nested_commands_lloc() {
6486        // Commands inside proc body are recursively parsed; verify each counts.
6487        check_metrics::<TclParser>(
6488            "proc f {x} {
6489    set y [expr {$x * 2}]
6490    puts $y
6491}",
6492            "foo.tcl",
6493            |metric| {
6494                assert_eq!(metric.loc.sloc(), 4.0);
6495                assert_eq!(metric.loc.ploc(), 4.0);
6496                assert_eq!(metric.loc.lloc(), 3.0);
6497                assert_eq!(metric.loc.cloc(), 0.0);
6498                assert_eq!(metric.loc.blank(), 0.0);
6499                insta::assert_json_snapshot!(metric.loc);
6500            },
6501        );
6502    }
6503
6504    #[test]
6505    fn tcl_command_lloc() {
6506        check_metrics::<TclParser>("puts hello", "foo.tcl", |metric| {
6507            assert_eq!(metric.loc.sloc(), 1.0);
6508            assert_eq!(metric.loc.ploc(), 1.0);
6509            assert_eq!(metric.loc.lloc(), 1.0);
6510            assert_eq!(metric.loc.cloc(), 0.0);
6511            assert_eq!(metric.loc.blank(), 0.0);
6512            insta::assert_json_snapshot!(metric.loc);
6513        });
6514    }
6515
6516    #[test]
6517    fn tcl_no_else_lloc() {
6518        // `else` block does not add a logical line.
6519        check_metrics::<TclParser>(
6520            "if {1} {\n    puts yes\n} else {\n    puts no\n}",
6521            "foo.tcl",
6522            |metric| {
6523                assert_eq!(metric.loc.sloc(), 5.0);
6524                assert_eq!(metric.loc.ploc(), 5.0);
6525                assert_eq!(metric.loc.lloc(), 3.0);
6526                assert_eq!(metric.loc.cloc(), 0.0);
6527                assert_eq!(metric.loc.blank(), 0.0);
6528                insta::assert_json_snapshot!(metric.loc);
6529            },
6530        );
6531    }
6532
6533    #[test]
6534    fn tcl_no_finally_lloc() {
6535        // `finally` block, like `else`, does not add a logical line.
6536        // proc(1) + try(1) + puts_hi(1) + puts_done(1) + finally(0) = 4.
6537        check_metrics::<TclParser>(
6538            "proc f {} {\n    try {\n        puts hi\n    } finally {\n        puts done\n    }\n}",
6539            "foo.tcl",
6540            |metric| {
6541                assert_eq!(
6542                    metric.loc.lloc(),
6543                    4.0,
6544                    "finally adds 0 lloc; would be 5 if finally counted"
6545                );
6546            },
6547        );
6548    }
6549
6550    #[test]
6551    fn tcl_multiline_block() {
6552        check_metrics::<TclParser>(
6553            "proc f {x} {
6554    set a 1
6555
6556    set b 2
6557    return [expr {$a + $b}]
6558}",
6559            "foo.tcl",
6560            |metric| {
6561                assert_eq!(metric.loc.sloc(), 6.0);
6562                assert_eq!(metric.loc.ploc(), 5.0);
6563                assert_eq!(metric.loc.lloc(), 4.0);
6564                assert_eq!(metric.loc.cloc(), 0.0);
6565                assert_eq!(metric.loc.blank(), 1.0);
6566                insta::assert_json_snapshot!(metric.loc);
6567            },
6568        );
6569    }
6570
6571    #[test]
6572    fn tcl_no_string_lloc() {
6573        // Multi-line double-quoted strings must not inflate lloc — only the
6574        // surrounding command should count. Mirrors lua_no_string_lloc and
6575        // elixir_no_string_content_lloc; pins the heredoc-shaped invariant
6576        // for Tcl quoted_word bodies.
6577        check_metrics::<TclParser>(
6578            "set s \"line one\nline two\nline three\"",
6579            "foo.tcl",
6580            |metric| {
6581                assert_eq!(metric.loc.sloc(), 3.0);
6582                assert_eq!(metric.loc.ploc(), 2.0);
6583                assert_eq!(metric.loc.lloc(), 1.0);
6584                assert_eq!(metric.loc.cloc(), 0.0);
6585                assert_eq!(metric.loc.blank(), 1.0);
6586                insta::assert_json_snapshot!(metric.loc);
6587            },
6588        );
6589    }
6590
6591    #[test]
6592    fn javascript_blank() {
6593        check_metrics::<JavascriptParser>(
6594            "// header comment
6595        function f() {
6596
6597            var x = 1;
6598
6599            var y = 2;
6600        }",
6601            "foo.js",
6602            |metric| {
6603                assert_eq!(metric.loc.sloc(), 7.0);
6604                assert_eq!(metric.loc.ploc(), 4.0);
6605                assert_eq!(metric.loc.lloc(), 1.0);
6606                assert_eq!(metric.loc.cloc(), 1.0);
6607                assert_eq!(metric.loc.blank(), 2.0);
6608                insta::assert_json_snapshot!(metric.loc);
6609            },
6610        );
6611    }
6612
6613    #[test]
6614    fn javascript_cloc() {
6615        check_metrics::<JavascriptParser>(
6616            "// line comment
6617        /* block
6618           comment */
6619        function f() {
6620            return 1; // inline
6621        }",
6622            "foo.js",
6623            |metric| {
6624                assert_eq!(metric.loc.sloc(), 6.0);
6625                assert_eq!(metric.loc.ploc(), 3.0);
6626                assert_eq!(metric.loc.lloc(), 2.0);
6627                assert_eq!(metric.loc.cloc(), 4.0);
6628                assert_eq!(metric.loc.blank(), 0.0);
6629                insta::assert_json_snapshot!(metric.loc);
6630            },
6631        );
6632    }
6633
6634    #[test]
6635    fn mozjs_blank() {
6636        check_metrics::<MozjsParser>(
6637            "function f() {
6638
6639            var x = 1;
6640
6641        }",
6642            "foo.js",
6643            |metric| {
6644                assert_eq!(metric.loc.sloc(), 5.0);
6645                assert_eq!(metric.loc.ploc(), 3.0);
6646                assert_eq!(metric.loc.lloc(), 1.0);
6647                assert_eq!(metric.loc.cloc(), 0.0);
6648                assert_eq!(metric.loc.blank(), 2.0);
6649                insta::assert_json_snapshot!(metric.loc);
6650            },
6651        );
6652    }
6653
6654    #[test]
6655    fn mozjs_cloc() {
6656        check_metrics::<MozjsParser>(
6657            "// header
6658        /* block comment */
6659        function f() {
6660            return 42;
6661        }",
6662            "foo.js",
6663            |metric| {
6664                assert_eq!(metric.loc.sloc(), 5.0);
6665                assert_eq!(metric.loc.ploc(), 3.0);
6666                assert_eq!(metric.loc.lloc(), 2.0);
6667                assert_eq!(metric.loc.cloc(), 2.0);
6668                assert_eq!(metric.loc.blank(), 0.0);
6669                insta::assert_json_snapshot!(metric.loc);
6670            },
6671        );
6672    }
6673
6674    #[test]
6675    fn mozjs_no_zero_blank() {
6676        // Blank line interleaved with code that carries trailing comments —
6677        // stresses the `blank = sloc - (ploc ∪ cloc lines)` union math.
6678        check_metrics::<MozjsParser>(
6679            "function f() {
6680  var a = 1;
6681
6682  var b = 2; // trailing
6683  var c = 3; // trailing
6684}",
6685            "foo.js",
6686            |metric| {
6687                assert_eq!(metric.loc.sloc(), 6.0);
6688                assert_eq!(metric.loc.ploc(), 5.0);
6689                assert_eq!(metric.loc.cloc(), 2.0);
6690                assert_eq!(metric.loc.blank(), 1.0);
6691                insta::assert_json_snapshot!(metric.loc);
6692            },
6693        );
6694    }
6695
6696    #[test]
6697    fn mozjs_arrow_function_loc() {
6698        check_metrics::<MozjsParser>(
6699            "const add = (a, b) => a + b;
6700        const greet = name => {
6701            return 'Hello ' + name;
6702        };",
6703            "foo.js",
6704            |metric| {
6705                assert_eq!(metric.loc.sloc(), 4.0);
6706                assert_eq!(metric.loc.ploc(), 4.0);
6707                assert_eq!(metric.loc.lloc(), 2.0);
6708                assert_eq!(metric.loc.cloc(), 0.0);
6709                assert_eq!(metric.loc.blank(), 0.0);
6710                insta::assert_json_snapshot!(metric.loc);
6711            },
6712        );
6713    }
6714
6715    #[test]
6716    fn mozjs_multiple_functions_loc() {
6717        check_metrics::<MozjsParser>(
6718            "function f() {
6719            return 1;
6720        }
6721        function g() {
6722            return 2;
6723        }",
6724            "foo.js",
6725            |metric| {
6726                assert_eq!(metric.loc.sloc(), 6.0);
6727                assert_eq!(metric.loc.ploc(), 6.0);
6728                assert_eq!(metric.loc.lloc(), 4.0);
6729                assert_eq!(metric.loc.cloc(), 0.0);
6730                assert_eq!(metric.loc.blank(), 0.0);
6731                insta::assert_json_snapshot!(metric.loc);
6732            },
6733        );
6734    }
6735
6736    #[test]
6737    fn mozjs_nested_function_loc() {
6738        check_metrics::<MozjsParser>(
6739            "function outer() {
6740            function inner() {
6741                return 1;
6742            }
6743            return inner();
6744        }",
6745            "foo.js",
6746            |metric| {
6747                assert_eq!(metric.loc.sloc(), 6.0);
6748                assert_eq!(metric.loc.ploc(), 6.0);
6749                assert_eq!(metric.loc.lloc(), 4.0);
6750                assert_eq!(metric.loc.cloc(), 0.0);
6751                assert_eq!(metric.loc.blank(), 0.0);
6752                insta::assert_json_snapshot!(metric.loc);
6753            },
6754        );
6755    }
6756
6757    #[test]
6758    fn mozjs_if_lloc() {
6759        check_metrics::<MozjsParser>(
6760            "function f(x) {
6761            if (x > 0) {
6762                return 1;
6763            } else {
6764                return -1;
6765            }
6766        }",
6767            "foo.js",
6768            |metric| {
6769                assert_eq!(metric.loc.sloc(), 7.0);
6770                assert_eq!(metric.loc.ploc(), 7.0);
6771                assert_eq!(metric.loc.lloc(), 6.0);
6772                assert_eq!(metric.loc.cloc(), 0.0);
6773                assert_eq!(metric.loc.blank(), 0.0);
6774                insta::assert_json_snapshot!(metric.loc);
6775            },
6776        );
6777    }
6778
6779    #[test]
6780    fn mozjs_for_lloc() {
6781        check_metrics::<MozjsParser>(
6782            "function f(n) {
6783            var s = 0;
6784            for (var i = 0; i < n; i++) {
6785                s += i;
6786            }
6787            return s;
6788        }",
6789            "foo.js",
6790            |metric| {
6791                assert_eq!(metric.loc.sloc(), 7.0);
6792                assert_eq!(metric.loc.ploc(), 7.0);
6793                assert_eq!(metric.loc.lloc(), 5.0);
6794                assert_eq!(metric.loc.cloc(), 0.0);
6795                assert_eq!(metric.loc.blank(), 0.0);
6796                insta::assert_json_snapshot!(metric.loc);
6797            },
6798        );
6799    }
6800
6801    #[test]
6802    fn bash_blank() {
6803        check_metrics::<BashParser>(
6804            "#!/bin/bash
6805
6806        f() {
6807
6808            echo hello
6809
6810        }",
6811            "foo.sh",
6812            |metric| {
6813                assert_eq!(metric.loc.sloc(), 7.0);
6814                assert_eq!(metric.loc.ploc(), 3.0);
6815                assert_eq!(metric.loc.lloc(), 2.0);
6816                assert_eq!(metric.loc.cloc(), 1.0);
6817                assert_eq!(metric.loc.blank(), 3.0);
6818                insta::assert_json_snapshot!(metric.loc);
6819            },
6820        );
6821    }
6822
6823    #[test]
6824    fn bash_cloc() {
6825        check_metrics::<BashParser>(
6826            "# header comment
6827        f() {
6828            # body comment
6829            echo hello
6830        }",
6831            "foo.sh",
6832            |metric| {
6833                assert_eq!(metric.loc.sloc(), 5.0);
6834                assert_eq!(metric.loc.ploc(), 3.0);
6835                assert_eq!(metric.loc.lloc(), 2.0);
6836                assert_eq!(metric.loc.cloc(), 2.0);
6837                assert_eq!(metric.loc.blank(), 0.0);
6838                insta::assert_json_snapshot!(metric.loc);
6839            },
6840        );
6841    }
6842
6843    #[test]
6844    fn bash_no_zero_blank() {
6845        // Blank line interleaved with code that carries trailing comments —
6846        // stresses the `blank = sloc - (ploc ∪ cloc lines)` union math.
6847        check_metrics::<BashParser>(
6848            "f() {
6849  echo a
6850
6851  echo b # trailing
6852  echo c # trailing
6853}",
6854            "foo.sh",
6855            |metric| {
6856                assert_eq!(metric.loc.sloc(), 6.0);
6857                assert_eq!(metric.loc.ploc(), 5.0);
6858                assert_eq!(metric.loc.cloc(), 2.0);
6859                assert_eq!(metric.loc.blank(), 1.0);
6860                insta::assert_json_snapshot!(metric.loc);
6861            },
6862        );
6863    }
6864
6865    #[test]
6866    fn bash_if_lloc() {
6867        check_metrics::<BashParser>(
6868            "f() {
6869            if [ $1 -gt 0 ]; then
6870                echo positive
6871            else
6872                echo negative
6873            fi
6874        }",
6875            "foo.sh",
6876            |metric| {
6877                assert_eq!(metric.loc.sloc(), 7.0);
6878                assert_eq!(metric.loc.ploc(), 7.0);
6879                assert_eq!(metric.loc.lloc(), 4.0);
6880                assert_eq!(metric.loc.cloc(), 0.0);
6881                assert_eq!(metric.loc.blank(), 0.0);
6882                insta::assert_json_snapshot!(metric.loc);
6883            },
6884        );
6885    }
6886
6887    #[test]
6888    fn bash_for_lloc() {
6889        check_metrics::<BashParser>(
6890            "f() {
6891            for i in 1 2 3; do
6892                echo $i
6893            done
6894        }",
6895            "foo.sh",
6896            |metric| {
6897                assert_eq!(metric.loc.sloc(), 5.0);
6898                assert_eq!(metric.loc.ploc(), 5.0);
6899                assert_eq!(metric.loc.lloc(), 3.0);
6900                assert_eq!(metric.loc.cloc(), 0.0);
6901                assert_eq!(metric.loc.blank(), 0.0);
6902                insta::assert_json_snapshot!(metric.loc);
6903            },
6904        );
6905    }
6906
6907    #[test]
6908    fn bash_while_lloc() {
6909        check_metrics::<BashParser>(
6910            "f() {
6911            local n=5
6912            while [ $n -gt 0 ]; do
6913                echo $n
6914                n=$((n - 1))
6915            done
6916        }",
6917            "foo.sh",
6918            |metric| {
6919                assert_eq!(metric.loc.sloc(), 7.0);
6920                assert_eq!(metric.loc.ploc(), 7.0);
6921                assert_eq!(metric.loc.lloc(), 4.0);
6922                assert_eq!(metric.loc.cloc(), 0.0);
6923                assert_eq!(metric.loc.blank(), 0.0);
6924                insta::assert_json_snapshot!(metric.loc);
6925            },
6926        );
6927    }
6928
6929    #[test]
6930    fn bash_case_lloc() {
6931        check_metrics::<BashParser>(
6932            "f() {
6933            case $1 in
6934                start) echo starting ;;
6935                stop)  echo stopping ;;
6936                *)     echo unknown  ;;
6937            esac
6938        }",
6939            "foo.sh",
6940            |metric| {
6941                assert_eq!(metric.loc.sloc(), 7.0);
6942                assert_eq!(metric.loc.ploc(), 7.0);
6943                assert_eq!(metric.loc.lloc(), 5.0);
6944                assert_eq!(metric.loc.cloc(), 0.0);
6945                assert_eq!(metric.loc.blank(), 0.0);
6946                insta::assert_json_snapshot!(metric.loc);
6947            },
6948        );
6949    }
6950
6951    #[test]
6952    fn bash_multiple_functions_loc() {
6953        check_metrics::<BashParser>(
6954            "f() {
6955            echo hello
6956        }
6957        g() {
6958            echo world
6959        }",
6960            "foo.sh",
6961            |metric| {
6962                assert_eq!(metric.loc.sloc(), 6.0);
6963                assert_eq!(metric.loc.ploc(), 6.0);
6964                assert_eq!(metric.loc.lloc(), 4.0);
6965                assert_eq!(metric.loc.cloc(), 0.0);
6966                assert_eq!(metric.loc.blank(), 0.0);
6967                insta::assert_json_snapshot!(metric.loc);
6968            },
6969        );
6970    }
6971
6972    #[test]
6973    fn bash_nested_function_loc() {
6974        check_metrics::<BashParser>(
6975            "outer() {
6976            inner() {
6977                echo inner
6978            }
6979            inner
6980            echo outer
6981        }",
6982            "foo.sh",
6983            |metric| {
6984                assert_eq!(metric.loc.sloc(), 7.0);
6985                assert_eq!(metric.loc.ploc(), 7.0);
6986                assert_eq!(metric.loc.lloc(), 5.0);
6987                assert_eq!(metric.loc.cloc(), 0.0);
6988                assert_eq!(metric.loc.blank(), 0.0);
6989                insta::assert_json_snapshot!(metric.loc);
6990            },
6991        );
6992    }
6993
6994    #[test]
6995    fn bash_heredoc_loc() {
6996        check_metrics::<BashParser>(
6997            "f() {
6998            cat <<EOF
6999line1
7000line2
7001EOF
7002        }",
7003            "foo.sh",
7004            |metric| {
7005                assert_eq!(metric.loc.sloc(), 6.0);
7006                assert_eq!(metric.loc.ploc(), 5.0);
7007                assert_eq!(metric.loc.lloc(), 2.0);
7008                assert_eq!(metric.loc.cloc(), 0.0);
7009                assert_eq!(metric.loc.blank(), 1.0);
7010                insta::assert_json_snapshot!(metric.loc);
7011            },
7012        );
7013    }
7014
7015    #[test]
7016    fn kotlin_loc_blank() {
7017        check_metrics::<KotlinParser>(
7018            "fun f(): Int {
7019
7020            val x = 1
7021
7022            return x
7023        }",
7024            "foo.kt",
7025            |metric| {
7026                assert_eq!(metric.loc.sloc(), 6.0);
7027                assert_eq!(metric.loc.ploc(), 4.0);
7028                assert_eq!(metric.loc.lloc(), 2.0);
7029                assert_eq!(metric.loc.cloc(), 0.0);
7030                assert_eq!(metric.loc.blank(), 2.0);
7031                insta::assert_json_snapshot!(metric.loc);
7032            },
7033        );
7034    }
7035
7036    #[test]
7037    fn kotlin_loc_cloc() {
7038        check_metrics::<KotlinParser>(
7039            "// header comment
7040        /* block
7041           comment */
7042        fun f(): Int {
7043            return 42 // inline
7044        }",
7045            "foo.kt",
7046            |metric| {
7047                assert_eq!(metric.loc.sloc(), 6.0);
7048                assert_eq!(metric.loc.ploc(), 3.0);
7049                assert_eq!(metric.loc.lloc(), 1.0);
7050                assert_eq!(metric.loc.cloc(), 4.0);
7051                assert_eq!(metric.loc.blank(), 0.0);
7052                insta::assert_json_snapshot!(metric.loc);
7053            },
7054        );
7055    }
7056
7057    #[test]
7058    fn kotlin_loc_no_zero_blank() {
7059        // Checks that the blank metric is not equal to 0 when there are some
7060        // comments next to code lines. Mirrors rust_no_zero_blank.
7061        check_metrics::<KotlinParser>(
7062            "fun connectToUpdateServer() {
7063              val pool = 0
7064
7065              val updateServer = -42
7066              val isConnected = false
7067              val currTry = 0
7068              val numRetries = 10  // Number of IPC connection retries before
7069                                    // giving up.
7070              val numTries = 20    // Number of IPC connection tries before
7071                                    // giving up.
7072            }",
7073            "foo.kt",
7074            |metric| {
7075                // Anchor the headline integer values; in particular
7076                // `blank() > 0` is the contract this test's name advertises.
7077                assert_eq!(metric.loc.sloc(), 11.0);
7078                assert_eq!(metric.loc.ploc(), 8.0);
7079                assert_eq!(metric.loc.cloc(), 4.0);
7080                assert_eq!(metric.loc.blank(), 1.0);
7081                insta::assert_json_snapshot!(
7082                    metric.loc,
7083                    @r###"
7084                    {
7085                      "sloc": 11.0,
7086                      "ploc": 8.0,
7087                      "lloc": 6.0,
7088                      "cloc": 4.0,
7089                      "blank": 1.0,
7090                      "sloc_average": 5.5,
7091                      "ploc_average": 4.0,
7092                      "lloc_average": 3.0,
7093                      "cloc_average": 2.0,
7094                      "blank_average": 0.5,
7095                      "sloc_min": 11.0,
7096                      "sloc_max": 11.0,
7097                      "cloc_min": 4.0,
7098                      "cloc_max": 4.0,
7099                      "ploc_min": 8.0,
7100                      "ploc_max": 8.0,
7101                      "lloc_min": 6.0,
7102                      "lloc_max": 6.0,
7103                      "blank_min": 1.0,
7104                      "blank_max": 1.0
7105                    }"###
7106                );
7107            },
7108        );
7109    }
7110
7111    #[test]
7112    fn kotlin_loc_blank_zero_sanity() {
7113        // Sanity: when the source has no blank lines, blank() must be 0.
7114        // Preserves the no-blank coverage previously held by
7115        // kotlin_loc_no_zero_blank before it was rewritten to assert the
7116        // positive case its name advertises.
7117        check_metrics::<KotlinParser>(
7118            "fun f(): Int {
7119            val x = 1 // x
7120            val y = 2 // y
7121            return x + y
7122        }",
7123            "foo.kt",
7124            |metric| {
7125                assert_eq!(metric.loc.sloc(), 5.0);
7126                assert_eq!(metric.loc.ploc(), 5.0);
7127                assert_eq!(metric.loc.lloc(), 3.0);
7128                assert_eq!(metric.loc.cloc(), 2.0);
7129                assert_eq!(metric.loc.blank(), 0.0);
7130            },
7131        );
7132    }
7133
7134    #[test]
7135    fn kotlin_loc_if_lloc() {
7136        check_metrics::<KotlinParser>(
7137            "fun classify(n: Int): String {
7138            if (n > 0) {
7139                return \"positive\"
7140            } else if (n < 0) {
7141                return \"negative\"
7142            }
7143            return \"zero\"
7144        }",
7145            "foo.kt",
7146            |metric| {
7147                assert_eq!(metric.loc.sloc(), 8.0);
7148                assert_eq!(metric.loc.ploc(), 8.0);
7149                assert_eq!(metric.loc.lloc(), 5.0);
7150                assert_eq!(metric.loc.cloc(), 0.0);
7151                assert_eq!(metric.loc.blank(), 0.0);
7152                insta::assert_json_snapshot!(metric.loc);
7153            },
7154        );
7155    }
7156
7157    #[test]
7158    fn kotlin_loc_for_lloc() {
7159        check_metrics::<KotlinParser>(
7160            "fun sum(n: Int): Int {
7161            var s = 0
7162            for (i in 1..n) {
7163                s += i
7164            }
7165            return s
7166        }",
7167            "foo.kt",
7168            |metric| {
7169                assert_eq!(metric.loc.sloc(), 7.0);
7170                assert_eq!(metric.loc.ploc(), 7.0);
7171                assert_eq!(metric.loc.lloc(), 4.0);
7172                assert_eq!(metric.loc.cloc(), 0.0);
7173                assert_eq!(metric.loc.blank(), 0.0);
7174                insta::assert_json_snapshot!(metric.loc);
7175            },
7176        );
7177    }
7178
7179    #[test]
7180    fn kotlin_loc_when_lloc() {
7181        check_metrics::<KotlinParser>(
7182            "fun describe(x: Int): String {
7183            return when (x) {
7184                1 -> \"one\"
7185                2 -> \"two\"
7186                else -> \"other\"
7187            }
7188        }",
7189            "foo.kt",
7190            |metric| {
7191                assert_eq!(metric.loc.sloc(), 7.0);
7192                assert_eq!(metric.loc.ploc(), 7.0);
7193                assert_eq!(metric.loc.lloc(), 2.0);
7194                assert_eq!(metric.loc.cloc(), 0.0);
7195                assert_eq!(metric.loc.blank(), 0.0);
7196                insta::assert_json_snapshot!(metric.loc);
7197            },
7198        );
7199    }
7200
7201    #[test]
7202    fn kotlin_loc_lambda_lloc() {
7203        check_metrics::<KotlinParser>(
7204            "fun f(list: List<Int>): List<Int> {
7205            return list.filter { it > 0 }
7206                       .map { it * 2 }
7207        }",
7208            "foo.kt",
7209            |metric| {
7210                assert_eq!(metric.loc.sloc(), 4.0);
7211                assert_eq!(metric.loc.ploc(), 4.0);
7212                assert_eq!(metric.loc.lloc(), 1.0);
7213                assert_eq!(metric.loc.cloc(), 0.0);
7214                assert_eq!(metric.loc.blank(), 0.0);
7215                insta::assert_json_snapshot!(metric.loc);
7216            },
7217        );
7218    }
7219
7220    #[test]
7221    fn kotlin_loc_class_loc() {
7222        check_metrics::<KotlinParser>(
7223            "class Counter {
7224            private var count = 0
7225            fun increment() { count++ }
7226            fun get(): Int = count
7227        }",
7228            "foo.kt",
7229            |metric| {
7230                assert_eq!(metric.loc.sloc(), 5.0);
7231                assert_eq!(metric.loc.ploc(), 5.0);
7232                assert_eq!(metric.loc.lloc(), 1.0);
7233                assert_eq!(metric.loc.cloc(), 0.0);
7234                assert_eq!(metric.loc.blank(), 0.0);
7235                insta::assert_json_snapshot!(metric.loc);
7236            },
7237        );
7238    }
7239
7240    #[test]
7241    fn kotlin_loc_multiple_functions_loc() {
7242        check_metrics::<KotlinParser>(
7243            "fun f(): Int {
7244            return 1
7245        }
7246        fun g(): Int {
7247            return 2
7248        }",
7249            "foo.kt",
7250            |metric| {
7251                assert_eq!(metric.loc.sloc(), 6.0);
7252                assert_eq!(metric.loc.ploc(), 6.0);
7253                assert_eq!(metric.loc.lloc(), 2.0);
7254                assert_eq!(metric.loc.cloc(), 0.0);
7255                assert_eq!(metric.loc.blank(), 0.0);
7256                insta::assert_json_snapshot!(metric.loc);
7257            },
7258        );
7259    }
7260
7261    #[test]
7262    fn kotlin_loc_while_lloc() {
7263        check_metrics::<KotlinParser>(
7264            "fun countdown(n: Int) {
7265            var i = n
7266            while (i > 0) {
7267                println(i)
7268                i--
7269            }
7270        }",
7271            "foo.kt",
7272            |metric| {
7273                assert_eq!(metric.loc.sloc(), 7.0);
7274                assert_eq!(metric.loc.ploc(), 7.0);
7275                assert_eq!(metric.loc.lloc(), 3.0);
7276                assert_eq!(metric.loc.cloc(), 0.0);
7277                assert_eq!(metric.loc.blank(), 0.0);
7278                insta::assert_json_snapshot!(metric.loc);
7279            },
7280        );
7281    }
7282
7283    #[test]
7284    fn typescript_blank() {
7285        check_metrics::<TypescriptParser>(
7286            "function f(): void {
7287
7288            const x = 1;
7289
7290        }",
7291            "foo.ts",
7292            |metric| {
7293                assert_eq!(metric.loc.sloc(), 5.0);
7294                assert_eq!(metric.loc.ploc(), 3.0);
7295                assert_eq!(metric.loc.lloc(), 1.0);
7296                assert_eq!(metric.loc.cloc(), 0.0);
7297                assert_eq!(metric.loc.blank(), 2.0);
7298                insta::assert_json_snapshot!(metric.loc);
7299            },
7300        );
7301    }
7302
7303    #[test]
7304    fn typescript_cloc() {
7305        check_metrics::<TypescriptParser>(
7306            "// header
7307        /* block
7308           comment */
7309        function f(): number {
7310            return 42; // inline
7311        }",
7312            "foo.ts",
7313            |metric| {
7314                assert_eq!(metric.loc.sloc(), 6.0);
7315                assert_eq!(metric.loc.ploc(), 3.0);
7316                assert_eq!(metric.loc.lloc(), 2.0);
7317                assert_eq!(metric.loc.cloc(), 4.0);
7318                assert_eq!(metric.loc.blank(), 0.0);
7319                insta::assert_json_snapshot!(metric.loc);
7320            },
7321        );
7322    }
7323
7324    #[test]
7325    fn typescript_no_zero_blank() {
7326        // Blank line interleaved with code that carries trailing comments —
7327        // stresses the `blank = sloc - (ploc ∪ cloc lines)` union math.
7328        check_metrics::<TypescriptParser>(
7329            "function f(): void {
7330  const a = 1;
7331
7332  const b = 2; // trailing
7333  const c = 3; // trailing
7334}",
7335            "foo.ts",
7336            |metric| {
7337                assert_eq!(metric.loc.sloc(), 6.0);
7338                assert_eq!(metric.loc.ploc(), 5.0);
7339                assert_eq!(metric.loc.cloc(), 2.0);
7340                assert_eq!(metric.loc.blank(), 1.0);
7341                insta::assert_json_snapshot!(metric.loc);
7342            },
7343        );
7344    }
7345
7346    #[test]
7347    fn typescript_if_lloc() {
7348        check_metrics::<TypescriptParser>(
7349            "function classify(n: number): string {
7350            if (n > 0) {
7351                return 'positive';
7352            } else {
7353                return 'non-positive';
7354            }
7355        }",
7356            "foo.ts",
7357            |metric| {
7358                assert_eq!(metric.loc.sloc(), 7.0);
7359                assert_eq!(metric.loc.ploc(), 7.0);
7360                assert_eq!(metric.loc.lloc(), 6.0);
7361                assert_eq!(metric.loc.cloc(), 0.0);
7362                assert_eq!(metric.loc.blank(), 0.0);
7363                insta::assert_json_snapshot!(metric.loc);
7364            },
7365        );
7366    }
7367
7368    #[test]
7369    fn typescript_for_lloc() {
7370        check_metrics::<TypescriptParser>(
7371            "function sum(n: number): number {
7372            let s = 0;
7373            for (let i = 0; i < n; i++) {
7374                s += i;
7375            }
7376            return s;
7377        }",
7378            "foo.ts",
7379            |metric| {
7380                assert_eq!(metric.loc.sloc(), 7.0);
7381                assert_eq!(metric.loc.ploc(), 7.0);
7382                assert_eq!(metric.loc.lloc(), 5.0);
7383                assert_eq!(metric.loc.cloc(), 0.0);
7384                assert_eq!(metric.loc.blank(), 0.0);
7385                insta::assert_json_snapshot!(metric.loc);
7386            },
7387        );
7388    }
7389
7390    #[test]
7391    fn typescript_while_lloc() {
7392        check_metrics::<TypescriptParser>(
7393            "function countdown(n: number): void {
7394            let i = n;
7395            while (i > 0) {
7396                console.log(i);
7397                i--;
7398            }
7399        }",
7400            "foo.ts",
7401            |metric| {
7402                assert_eq!(metric.loc.sloc(), 7.0);
7403                assert_eq!(metric.loc.ploc(), 7.0);
7404                assert_eq!(metric.loc.lloc(), 5.0);
7405                assert_eq!(metric.loc.cloc(), 0.0);
7406                assert_eq!(metric.loc.blank(), 0.0);
7407                insta::assert_json_snapshot!(metric.loc);
7408            },
7409        );
7410    }
7411
7412    #[test]
7413    fn typescript_switch_lloc() {
7414        check_metrics::<TypescriptParser>(
7415            "function describe(x: number): string {
7416            switch (x) {
7417                case 1: return 'one';
7418                case 2: return 'two';
7419                default: return 'other';
7420            }
7421        }",
7422            "foo.ts",
7423            |metric| {
7424                assert_eq!(metric.loc.sloc(), 7.0);
7425                assert_eq!(metric.loc.ploc(), 7.0);
7426                assert_eq!(metric.loc.lloc(), 5.0);
7427                assert_eq!(metric.loc.cloc(), 0.0);
7428                assert_eq!(metric.loc.blank(), 0.0);
7429                insta::assert_json_snapshot!(metric.loc);
7430            },
7431        );
7432    }
7433
7434    #[test]
7435    fn typescript_class_loc() {
7436        check_metrics::<TypescriptParser>(
7437            "class Counter {
7438            private count: number = 0;
7439            increment(): void { this.count++; }
7440            get(): number { return this.count; }
7441        }",
7442            "foo.ts",
7443            |metric| {
7444                assert_eq!(metric.loc.sloc(), 5.0);
7445                assert_eq!(metric.loc.ploc(), 5.0);
7446                assert_eq!(metric.loc.lloc(), 4.0);
7447                assert_eq!(metric.loc.cloc(), 0.0);
7448                assert_eq!(metric.loc.blank(), 0.0);
7449                insta::assert_json_snapshot!(metric.loc);
7450            },
7451        );
7452    }
7453
7454    #[test]
7455    fn typescript_arrow_function_loc() {
7456        check_metrics::<TypescriptParser>(
7457            "const add = (a: number, b: number): number => a + b;
7458        const greet = (name: string): string => {
7459            return `Hello, ${name}`;
7460        };",
7461            "foo.ts",
7462            |metric| {
7463                assert_eq!(metric.loc.sloc(), 4.0);
7464                assert_eq!(metric.loc.ploc(), 4.0);
7465                assert_eq!(metric.loc.lloc(), 2.0);
7466                assert_eq!(metric.loc.cloc(), 0.0);
7467                assert_eq!(metric.loc.blank(), 0.0);
7468                insta::assert_json_snapshot!(metric.loc);
7469            },
7470        );
7471    }
7472
7473    #[test]
7474    fn typescript_interface_loc() {
7475        check_metrics::<TypescriptParser>(
7476            "interface Shape {
7477            area(): number;
7478            perimeter(): number;
7479        }
7480        function describe(s: Shape): string {
7481            return `area=${s.area()}`;
7482        }",
7483            "foo.ts",
7484            |metric| {
7485                assert_eq!(metric.loc.sloc(), 7.0);
7486                assert_eq!(metric.loc.ploc(), 7.0);
7487                assert_eq!(metric.loc.lloc(), 2.0);
7488                assert_eq!(metric.loc.cloc(), 0.0);
7489                assert_eq!(metric.loc.blank(), 0.0);
7490                insta::assert_json_snapshot!(metric.loc);
7491            },
7492        );
7493    }
7494
7495    #[test]
7496    fn typescript_multiple_functions_loc() {
7497        check_metrics::<TypescriptParser>(
7498            "function f(): number {
7499            return 1;
7500        }
7501        function g(): number {
7502            return 2;
7503        }
7504        function h(): number {
7505            return 3;
7506        }",
7507            "foo.ts",
7508            |metric| {
7509                assert_eq!(metric.loc.sloc(), 9.0);
7510                assert_eq!(metric.loc.ploc(), 9.0);
7511                assert_eq!(metric.loc.lloc(), 6.0);
7512                assert_eq!(metric.loc.cloc(), 0.0);
7513                assert_eq!(metric.loc.blank(), 0.0);
7514                insta::assert_json_snapshot!(metric.loc);
7515            },
7516        );
7517    }
7518
7519    #[test]
7520    fn typescript_try_catch_lloc() {
7521        check_metrics::<TypescriptParser>(
7522            "function safe(x: number): number {
7523            try {
7524                return 1 / x;
7525            } catch (e) {
7526                return 0;
7527            }
7528        }",
7529            "foo.ts",
7530            |metric| {
7531                assert_eq!(metric.loc.sloc(), 7.0);
7532                assert_eq!(metric.loc.ploc(), 7.0);
7533                assert_eq!(metric.loc.lloc(), 6.0);
7534                assert_eq!(metric.loc.cloc(), 0.0);
7535                assert_eq!(metric.loc.blank(), 0.0);
7536                insta::assert_json_snapshot!(metric.loc);
7537            },
7538        );
7539    }
7540
7541    #[test]
7542    fn typescript_nested_functions_loc() {
7543        check_metrics::<TypescriptParser>(
7544            "function outer(x: number): number {
7545            function inner(y: number): number {
7546                return y * 2;
7547            }
7548            return inner(x) + 1;
7549        }",
7550            "foo.ts",
7551            |metric| {
7552                assert_eq!(metric.loc.sloc(), 6.0);
7553                assert_eq!(metric.loc.ploc(), 6.0);
7554                assert_eq!(metric.loc.lloc(), 4.0);
7555                assert_eq!(metric.loc.cloc(), 0.0);
7556                assert_eq!(metric.loc.blank(), 0.0);
7557                insta::assert_json_snapshot!(metric.loc);
7558            },
7559        );
7560    }
7561
7562    #[test]
7563    fn typescript_generic_function_loc() {
7564        check_metrics::<TypescriptParser>(
7565            "function identity<T>(value: T): T {
7566            return value;
7567        }
7568        function first<T>(arr: T[]): T | undefined {
7569            return arr[0];
7570        }",
7571            "foo.ts",
7572            |metric| {
7573                assert_eq!(metric.loc.sloc(), 6.0);
7574                assert_eq!(metric.loc.ploc(), 6.0);
7575                assert_eq!(metric.loc.lloc(), 4.0);
7576                assert_eq!(metric.loc.cloc(), 0.0);
7577                assert_eq!(metric.loc.blank(), 0.0);
7578                insta::assert_json_snapshot!(metric.loc);
7579            },
7580        );
7581    }
7582
7583    #[test]
7584    fn tsx_blank() {
7585        check_metrics::<TsxParser>(
7586            "function f(): void {
7587
7588            const x = 1;
7589
7590        }",
7591            "foo.tsx",
7592            |metric| {
7593                assert_eq!(metric.loc.sloc(), 5.0);
7594                assert_eq!(metric.loc.ploc(), 3.0);
7595                assert_eq!(metric.loc.lloc(), 1.0);
7596                assert_eq!(metric.loc.cloc(), 0.0);
7597                assert_eq!(metric.loc.blank(), 2.0);
7598                insta::assert_json_snapshot!(metric.loc);
7599            },
7600        );
7601    }
7602
7603    #[test]
7604    fn tsx_cloc() {
7605        check_metrics::<TsxParser>(
7606            "// header
7607        /* block
7608           comment */
7609        function f(): number {
7610            return 42; // inline
7611        }",
7612            "foo.tsx",
7613            |metric| {
7614                assert_eq!(metric.loc.sloc(), 6.0);
7615                assert_eq!(metric.loc.ploc(), 3.0);
7616                assert_eq!(metric.loc.lloc(), 2.0);
7617                assert_eq!(metric.loc.cloc(), 4.0);
7618                assert_eq!(metric.loc.blank(), 0.0);
7619                insta::assert_json_snapshot!(metric.loc);
7620            },
7621        );
7622    }
7623
7624    #[test]
7625    fn tsx_no_zero_blank() {
7626        // Blank line interleaved with code that carries trailing comments —
7627        // stresses the `blank = sloc - (ploc ∪ cloc lines)` union math.
7628        check_metrics::<TsxParser>(
7629            "function f(): void {
7630  const a = 1;
7631
7632  const b = 2; // trailing
7633  const c = 3; // trailing
7634}",
7635            "foo.tsx",
7636            |metric| {
7637                assert_eq!(metric.loc.sloc(), 6.0);
7638                assert_eq!(metric.loc.ploc(), 5.0);
7639                assert_eq!(metric.loc.cloc(), 2.0);
7640                assert_eq!(metric.loc.blank(), 1.0);
7641                insta::assert_json_snapshot!(metric.loc);
7642            },
7643        );
7644    }
7645
7646    #[test]
7647    fn tsx_if_lloc() {
7648        check_metrics::<TsxParser>(
7649            "function classify(n: number): string {
7650            if (n > 0) {
7651                return 'positive';
7652            } else {
7653                return 'non-positive';
7654            }
7655        }",
7656            "foo.tsx",
7657            |metric| {
7658                assert_eq!(metric.loc.sloc(), 7.0);
7659                assert_eq!(metric.loc.ploc(), 7.0);
7660                assert_eq!(metric.loc.lloc(), 6.0);
7661                assert_eq!(metric.loc.cloc(), 0.0);
7662                assert_eq!(metric.loc.blank(), 0.0);
7663                insta::assert_json_snapshot!(metric.loc);
7664            },
7665        );
7666    }
7667
7668    #[test]
7669    fn tsx_for_lloc() {
7670        check_metrics::<TsxParser>(
7671            "function sum(n: number): number {
7672            let s = 0;
7673            for (let i = 0; i < n; i++) {
7674                s += i;
7675            }
7676            return s;
7677        }",
7678            "foo.tsx",
7679            |metric| {
7680                assert_eq!(metric.loc.sloc(), 7.0);
7681                assert_eq!(metric.loc.ploc(), 7.0);
7682                assert_eq!(metric.loc.lloc(), 5.0);
7683                assert_eq!(metric.loc.cloc(), 0.0);
7684                assert_eq!(metric.loc.blank(), 0.0);
7685                insta::assert_json_snapshot!(metric.loc);
7686            },
7687        );
7688    }
7689
7690    #[test]
7691    fn tsx_while_lloc() {
7692        check_metrics::<TsxParser>(
7693            "function countdown(n: number): void {
7694            let i = n;
7695            while (i > 0) {
7696                console.log(i);
7697                i--;
7698            }
7699        }",
7700            "foo.tsx",
7701            |metric| {
7702                assert_eq!(metric.loc.sloc(), 7.0);
7703                assert_eq!(metric.loc.ploc(), 7.0);
7704                assert_eq!(metric.loc.lloc(), 5.0);
7705                assert_eq!(metric.loc.cloc(), 0.0);
7706                assert_eq!(metric.loc.blank(), 0.0);
7707                insta::assert_json_snapshot!(metric.loc);
7708            },
7709        );
7710    }
7711
7712    #[test]
7713    fn tsx_switch_lloc() {
7714        check_metrics::<TsxParser>(
7715            "function describe(x: number): string {
7716            switch (x) {
7717                case 1: return 'one';
7718                case 2: return 'two';
7719                default: return 'other';
7720            }
7721        }",
7722            "foo.tsx",
7723            |metric| {
7724                assert_eq!(metric.loc.sloc(), 7.0);
7725                assert_eq!(metric.loc.ploc(), 7.0);
7726                assert_eq!(metric.loc.lloc(), 5.0);
7727                assert_eq!(metric.loc.cloc(), 0.0);
7728                assert_eq!(metric.loc.blank(), 0.0);
7729                insta::assert_json_snapshot!(metric.loc);
7730            },
7731        );
7732    }
7733
7734    #[test]
7735    fn tsx_class_loc() {
7736        check_metrics::<TsxParser>(
7737            "class Counter {
7738            private count: number = 0;
7739            increment(): void { this.count++; }
7740            get(): number { return this.count; }
7741        }",
7742            "foo.tsx",
7743            |metric| {
7744                assert_eq!(metric.loc.sloc(), 5.0);
7745                assert_eq!(metric.loc.ploc(), 5.0);
7746                assert_eq!(metric.loc.lloc(), 4.0);
7747                assert_eq!(metric.loc.cloc(), 0.0);
7748                assert_eq!(metric.loc.blank(), 0.0);
7749                insta::assert_json_snapshot!(metric.loc);
7750            },
7751        );
7752    }
7753
7754    #[test]
7755    fn tsx_arrow_function_loc() {
7756        check_metrics::<TsxParser>(
7757            "const add = (a: number, b: number): number => a + b;
7758        const greet = (name: string): string => {
7759            return `Hello, ${name}`;
7760        };",
7761            "foo.tsx",
7762            |metric| {
7763                assert_eq!(metric.loc.sloc(), 4.0);
7764                assert_eq!(metric.loc.ploc(), 4.0);
7765                assert_eq!(metric.loc.lloc(), 2.0);
7766                assert_eq!(metric.loc.cloc(), 0.0);
7767                assert_eq!(metric.loc.blank(), 0.0);
7768                insta::assert_json_snapshot!(metric.loc);
7769            },
7770        );
7771    }
7772
7773    #[test]
7774    fn tsx_multiple_functions_loc() {
7775        check_metrics::<TsxParser>(
7776            "function f(): number {
7777            return 1;
7778        }
7779        function g(): number {
7780            return 2;
7781        }
7782        function h(): number {
7783            return 3;
7784        }",
7785            "foo.tsx",
7786            |metric| {
7787                assert_eq!(metric.loc.sloc(), 9.0);
7788                assert_eq!(metric.loc.ploc(), 9.0);
7789                assert_eq!(metric.loc.lloc(), 6.0);
7790                assert_eq!(metric.loc.cloc(), 0.0);
7791                assert_eq!(metric.loc.blank(), 0.0);
7792                insta::assert_json_snapshot!(metric.loc);
7793            },
7794        );
7795    }
7796
7797    #[test]
7798    fn tsx_try_catch_lloc() {
7799        check_metrics::<TsxParser>(
7800            "function safe(x: number): number {
7801            try {
7802                return 1 / x;
7803            } catch (e) {
7804                return 0;
7805            }
7806        }",
7807            "foo.tsx",
7808            |metric| {
7809                assert_eq!(metric.loc.sloc(), 7.0);
7810                assert_eq!(metric.loc.ploc(), 7.0);
7811                assert_eq!(metric.loc.lloc(), 6.0);
7812                assert_eq!(metric.loc.cloc(), 0.0);
7813                assert_eq!(metric.loc.blank(), 0.0);
7814                insta::assert_json_snapshot!(metric.loc);
7815            },
7816        );
7817    }
7818
7819    #[test]
7820    fn tsx_nested_functions_loc() {
7821        check_metrics::<TsxParser>(
7822            "function outer(x: number): number {
7823            function inner(y: number): number {
7824                return y * 2;
7825            }
7826            return inner(x) + 1;
7827        }",
7828            "foo.tsx",
7829            |metric| {
7830                assert_eq!(metric.loc.sloc(), 6.0);
7831                assert_eq!(metric.loc.ploc(), 6.0);
7832                assert_eq!(metric.loc.lloc(), 4.0);
7833                assert_eq!(metric.loc.cloc(), 0.0);
7834                assert_eq!(metric.loc.blank(), 0.0);
7835                insta::assert_json_snapshot!(metric.loc);
7836            },
7837        );
7838    }
7839
7840    #[test]
7841    fn tsx_interface_loc() {
7842        check_metrics::<TsxParser>(
7843            "interface Shape {
7844            area(): number;
7845            perimeter(): number;
7846        }
7847        function describe(s: Shape): string {
7848            return `area=${s.area()}`;
7849        }",
7850            "foo.tsx",
7851            |metric| {
7852                assert_eq!(metric.loc.sloc(), 7.0);
7853                assert_eq!(metric.loc.ploc(), 7.0);
7854                assert_eq!(metric.loc.lloc(), 2.0);
7855                assert_eq!(metric.loc.cloc(), 0.0);
7856                assert_eq!(metric.loc.blank(), 0.0);
7857                insta::assert_json_snapshot!(metric.loc);
7858            },
7859        );
7860    }
7861
7862    #[test]
7863    fn tsx_generic_function_loc() {
7864        check_metrics::<TsxParser>(
7865            "function identity<T>(value: T): T {
7866            return value;
7867        }
7868        function first<T>(arr: T[]): T | undefined {
7869            return arr[0];
7870        }",
7871            "foo.tsx",
7872            |metric| {
7873                assert_eq!(metric.loc.sloc(), 6.0);
7874                assert_eq!(metric.loc.ploc(), 6.0);
7875                assert_eq!(metric.loc.lloc(), 4.0);
7876                assert_eq!(metric.loc.cloc(), 0.0);
7877                assert_eq!(metric.loc.blank(), 0.0);
7878                insta::assert_json_snapshot!(metric.loc);
7879            },
7880        );
7881    }
7882
7883    #[test]
7884    fn php_blank() {
7885        check_metrics::<PhpParser>(
7886            "<?php
7887
7888$a = 1;
7889
7890$b = 2;
7891
7892",
7893            "foo.php",
7894            |metric| {
7895                assert_eq!(metric.loc.sloc(), 5.0);
7896                assert_eq!(metric.loc.ploc(), 3.0);
7897                assert_eq!(metric.loc.lloc(), 2.0);
7898                assert_eq!(metric.loc.cloc(), 0.0);
7899                assert_eq!(metric.loc.blank(), 2.0);
7900                insta::assert_json_snapshot!(metric.loc);
7901            },
7902        );
7903    }
7904
7905    #[test]
7906    fn php_no_zero_blank() {
7907        // Blank line interleaved with code that carries trailing comments —
7908        // stresses the `blank = sloc - (ploc ∪ cloc lines)` union math.
7909        check_metrics::<PhpParser>(
7910            "<?php
7911$a = 1;
7912
7913$b = 2; // trailing
7914$c = 3; // trailing
7915",
7916            "foo.php",
7917            |metric| {
7918                assert_eq!(metric.loc.sloc(), 5.0);
7919                assert_eq!(metric.loc.ploc(), 4.0);
7920                assert_eq!(metric.loc.cloc(), 2.0);
7921                assert_eq!(metric.loc.blank(), 1.0);
7922                insta::assert_json_snapshot!(metric.loc);
7923            },
7924        );
7925    }
7926
7927    #[test]
7928    fn php_cloc_double_slash() {
7929        check_metrics::<PhpParser>(
7930            "<?php
7931// first
7932// second
7933$a = 1; // trailing",
7934            "foo.php",
7935            |metric| {
7936                assert_eq!(metric.loc.sloc(), 4.0);
7937                assert_eq!(metric.loc.ploc(), 2.0);
7938                assert_eq!(metric.loc.lloc(), 1.0);
7939                assert_eq!(metric.loc.cloc(), 3.0);
7940                assert_eq!(metric.loc.blank(), 0.0);
7941                insta::assert_json_snapshot!(metric.loc);
7942            },
7943        );
7944    }
7945
7946    #[test]
7947    fn php_cloc_hash() {
7948        check_metrics::<PhpParser>(
7949            "<?php
7950# first
7951# second
7952$a = 1;",
7953            "foo.php",
7954            |metric| {
7955                assert_eq!(metric.loc.sloc(), 4.0);
7956                assert_eq!(metric.loc.ploc(), 2.0);
7957                assert_eq!(metric.loc.lloc(), 1.0);
7958                assert_eq!(metric.loc.cloc(), 2.0);
7959                assert_eq!(metric.loc.blank(), 0.0);
7960                insta::assert_json_snapshot!(metric.loc);
7961            },
7962        );
7963    }
7964
7965    #[test]
7966    fn php_cloc_block() {
7967        check_metrics::<PhpParser>(
7968            "<?php
7969/*
7970 * block
7971 * comment
7972 */
7973$a = 1;",
7974            "foo.php",
7975            |metric| {
7976                assert_eq!(metric.loc.sloc(), 6.0);
7977                assert_eq!(metric.loc.ploc(), 2.0);
7978                assert_eq!(metric.loc.lloc(), 1.0);
7979                assert_eq!(metric.loc.cloc(), 4.0);
7980                assert_eq!(metric.loc.blank(), 0.0);
7981                insta::assert_json_snapshot!(metric.loc);
7982            },
7983        );
7984    }
7985
7986    #[test]
7987    fn php_lloc() {
7988        // Three statements: assignment, if (with body), echo.
7989        check_metrics::<PhpParser>(
7990            "<?php
7991$a = 1;
7992if ($a > 0) {
7993    echo $a;
7994}",
7995            "foo.php",
7996            |metric| {
7997                assert_eq!(metric.loc.sloc(), 5.0);
7998                assert_eq!(metric.loc.ploc(), 5.0);
7999                assert_eq!(metric.loc.lloc(), 3.0);
8000                assert_eq!(metric.loc.cloc(), 0.0);
8001                assert_eq!(metric.loc.blank(), 0.0);
8002                insta::assert_json_snapshot!(metric.loc);
8003            },
8004        );
8005    }
8006
8007    #[test]
8008    fn php_no_parenthesized_expression_lloc() {
8009        // Parenthesized expression should not add an extra LLOC over the
8010        // surrounding expression_statement.
8011        check_metrics::<PhpParser>(
8012            "<?php
8013$a = (1 + 2);",
8014            "foo.php",
8015            |metric| {
8016                assert_eq!(metric.loc.sloc(), 2.0);
8017                assert_eq!(metric.loc.ploc(), 2.0);
8018                assert_eq!(metric.loc.lloc(), 1.0);
8019                assert_eq!(metric.loc.cloc(), 0.0);
8020                assert_eq!(metric.loc.blank(), 0.0);
8021                insta::assert_json_snapshot!(metric.loc);
8022            },
8023        );
8024    }
8025
8026    #[test]
8027    fn php_no_compound_statement_lloc() {
8028        // Block wrappers (`{ … }`) are not LLOC themselves.
8029        check_metrics::<PhpParser>(
8030            "<?php
8031function f(): void {
8032    $a = 1;
8033}",
8034            "foo.php",
8035            |metric| {
8036                assert_eq!(metric.loc.sloc(), 4.0);
8037                assert_eq!(metric.loc.ploc(), 4.0);
8038                assert_eq!(metric.loc.lloc(), 1.0);
8039                assert_eq!(metric.loc.cloc(), 0.0);
8040                assert_eq!(metric.loc.blank(), 0.0);
8041                insta::assert_json_snapshot!(metric.loc);
8042            },
8043        );
8044    }
8045
8046    #[test]
8047    fn php_no_colon_block_lloc() {
8048        // Alternative syntax (`if: … endif;`) uses ColonBlock instead of
8049        // CompoundStatement; it is also not LLOC.
8050        check_metrics::<PhpParser>(
8051            "<?php
8052if (true):
8053    $a = 1;
8054endif;",
8055            "foo.php",
8056            |metric| {
8057                assert_eq!(metric.loc.sloc(), 4.0);
8058                assert_eq!(metric.loc.ploc(), 4.0);
8059                assert_eq!(metric.loc.lloc(), 2.0);
8060                assert_eq!(metric.loc.cloc(), 0.0);
8061                assert_eq!(metric.loc.blank(), 0.0);
8062                insta::assert_json_snapshot!(metric.loc);
8063            },
8064        );
8065    }
8066
8067    #[test]
8068    fn php_no_else_clause_lloc() {
8069        // ElseClause and ElseIfClause are sub-parts of IfStatement.
8070        check_metrics::<PhpParser>(
8071            "<?php
8072if ($x) {
8073    $a = 1;
8074} elseif ($y) {
8075    $a = 2;
8076} else {
8077    $a = 3;
8078}",
8079            "foo.php",
8080            |metric| {
8081                assert_eq!(metric.loc.sloc(), 8.0);
8082                assert_eq!(metric.loc.ploc(), 8.0);
8083                assert_eq!(metric.loc.lloc(), 4.0);
8084                assert_eq!(metric.loc.cloc(), 0.0);
8085                assert_eq!(metric.loc.blank(), 0.0);
8086                insta::assert_json_snapshot!(metric.loc);
8087            },
8088        );
8089    }
8090
8091    #[test]
8092    fn php_no_case_statement_lloc() {
8093        // CaseStatement / DefaultStatement are switch arms, not separate
8094        // statements.
8095        check_metrics::<PhpParser>(
8096            "<?php
8097switch ($x) {
8098    case 1:
8099        $a = 1;
8100        break;
8101    case 2:
8102        $a = 2;
8103        break;
8104    default:
8105        $a = 0;
8106}",
8107            "foo.php",
8108            |metric| {
8109                assert_eq!(metric.loc.sloc(), 11.0);
8110                assert_eq!(metric.loc.ploc(), 11.0);
8111                assert_eq!(metric.loc.lloc(), 6.0);
8112                assert_eq!(metric.loc.cloc(), 0.0);
8113                assert_eq!(metric.loc.blank(), 0.0);
8114                insta::assert_json_snapshot!(metric.loc);
8115            },
8116        );
8117    }
8118
8119    #[test]
8120    fn php_no_match_arm_lloc() {
8121        // MatchConditionalExpression / MatchDefaultExpression are arms;
8122        // only the surrounding expression_statement counts.
8123        check_metrics::<PhpParser>(
8124            "<?php
8125$a = match ($x) {
8126    1 => 'one',
8127    2 => 'two',
8128    default => 'other',
8129};",
8130            "foo.php",
8131            |metric| {
8132                assert_eq!(metric.loc.sloc(), 6.0);
8133                assert_eq!(metric.loc.ploc(), 6.0);
8134                assert_eq!(metric.loc.lloc(), 1.0);
8135                assert_eq!(metric.loc.cloc(), 0.0);
8136                assert_eq!(metric.loc.blank(), 0.0);
8137                insta::assert_json_snapshot!(metric.loc);
8138            },
8139        );
8140    }
8141
8142    #[test]
8143    fn php_no_throw_in_expression_lloc() {
8144        // PHP 8 `throw` as expression: only the surrounding statement
8145        // counts (the `??` in this example), not the throw_expression.
8146        check_metrics::<PhpParser>(
8147            "<?php
8148$x = $y ?? throw new \\Exception('nope');",
8149            "foo.php",
8150            |metric| {
8151                assert_eq!(metric.loc.sloc(), 2.0);
8152                assert_eq!(metric.loc.ploc(), 2.0);
8153                assert_eq!(metric.loc.lloc(), 1.0);
8154                assert_eq!(metric.loc.cloc(), 0.0);
8155                assert_eq!(metric.loc.blank(), 0.0);
8156                insta::assert_json_snapshot!(metric.loc);
8157            },
8158        );
8159    }
8160
8161    #[test]
8162    fn php_no_closure_in_assignment_lloc() {
8163        // Anonymous function as RHS does not add an LLOC; only the
8164        // expression_statement counts. The closure body's statements are
8165        // counted in its own FuncSpace.
8166        check_metrics::<PhpParser>(
8167            "<?php
8168$f = function (): int {
8169    return 42;
8170};",
8171            "foo.php",
8172            |metric| {
8173                assert_eq!(metric.loc.sloc(), 4.0);
8174                assert_eq!(metric.loc.ploc(), 4.0);
8175                assert_eq!(metric.loc.lloc(), 2.0);
8176                assert_eq!(metric.loc.cloc(), 0.0);
8177                assert_eq!(metric.loc.blank(), 0.0);
8178                insta::assert_json_snapshot!(metric.loc);
8179            },
8180        );
8181    }
8182
8183    #[test]
8184    fn php_for_lloc() {
8185        // The for_statement contributes 1 LLOC; init/cond/update are NOT
8186        // separate statements in PHP's grammar.
8187        check_metrics::<PhpParser>(
8188            "<?php
8189for ($i = 0; $i < 10; $i++) {
8190    echo $i;
8191}",
8192            "foo.php",
8193            |metric| {
8194                assert_eq!(metric.loc.sloc(), 4.0);
8195                assert_eq!(metric.loc.ploc(), 4.0);
8196                assert_eq!(metric.loc.lloc(), 2.0);
8197                assert_eq!(metric.loc.cloc(), 0.0);
8198                assert_eq!(metric.loc.blank(), 0.0);
8199                insta::assert_json_snapshot!(metric.loc);
8200            },
8201        );
8202    }
8203
8204    #[test]
8205    fn php_foreach_lloc() {
8206        check_metrics::<PhpParser>(
8207            "<?php
8208foreach ($items as $k => $v) {
8209    echo $v;
8210}",
8211            "foo.php",
8212            |metric| {
8213                assert_eq!(metric.loc.sloc(), 4.0);
8214                assert_eq!(metric.loc.ploc(), 4.0);
8215                assert_eq!(metric.loc.lloc(), 2.0);
8216                assert_eq!(metric.loc.cloc(), 0.0);
8217                assert_eq!(metric.loc.blank(), 0.0);
8218                insta::assert_json_snapshot!(metric.loc);
8219            },
8220        );
8221    }
8222
8223    #[test]
8224    fn php_try_lloc() {
8225        check_metrics::<PhpParser>(
8226            "<?php
8227try {
8228    $a = 1;
8229} catch (\\Exception $e) {
8230    $a = 0;
8231} finally {
8232    $b = 2;
8233}",
8234            "foo.php",
8235            |metric| {
8236                assert_eq!(metric.loc.sloc(), 8.0);
8237                assert_eq!(metric.loc.ploc(), 8.0);
8238                assert_eq!(metric.loc.lloc(), 4.0);
8239                assert_eq!(metric.loc.cloc(), 0.0);
8240                assert_eq!(metric.loc.blank(), 0.0);
8241                insta::assert_json_snapshot!(metric.loc);
8242            },
8243        );
8244    }
8245
8246    #[test]
8247    fn php_class_loc() {
8248        check_metrics::<PhpParser>(
8249            "<?php
8250class A {
8251    public int $x = 0;
8252    private const Y = 1;
8253    public function f(): int {
8254        return $this->x;
8255    }
8256}",
8257            "foo.php",
8258            |metric| {
8259                assert_eq!(metric.loc.sloc(), 8.0);
8260                assert_eq!(metric.loc.ploc(), 8.0);
8261                assert_eq!(metric.loc.lloc(), 3.0);
8262                assert_eq!(metric.loc.cloc(), 0.0);
8263                assert_eq!(metric.loc.blank(), 0.0);
8264                insta::assert_json_snapshot!(metric.loc);
8265            },
8266        );
8267    }
8268
8269    #[test]
8270    fn php_namespace_use_lloc() {
8271        check_metrics::<PhpParser>(
8272            "<?php
8273namespace App;
8274use App\\Foo;
8275use App\\Bar;
8276$a = 1;",
8277            "foo.php",
8278            |metric| {
8279                assert_eq!(metric.loc.sloc(), 5.0);
8280                assert_eq!(metric.loc.ploc(), 5.0);
8281                assert_eq!(metric.loc.lloc(), 3.0);
8282                assert_eq!(metric.loc.cloc(), 0.0);
8283                assert_eq!(metric.loc.blank(), 0.0);
8284                insta::assert_json_snapshot!(metric.loc);
8285            },
8286        );
8287    }
8288
8289    #[test]
8290    fn php_general_loc() {
8291        check_metrics::<PhpParser>(
8292            "<?php
8293// header
8294namespace App;
8295use App\\Foo;
8296
8297class Bar {
8298    public int $n = 0;
8299
8300    public function add(int $x): int {
8301        if ($x > 0) {
8302            return $this->n + $x;
8303        }
8304        return $this->n;
8305    }
8306}",
8307            "foo.php",
8308            |metric| {
8309                assert_eq!(metric.loc.sloc(), 15.0);
8310                assert_eq!(metric.loc.ploc(), 12.0);
8311                assert_eq!(metric.loc.lloc(), 5.0);
8312                assert_eq!(metric.loc.cloc(), 1.0);
8313                assert_eq!(metric.loc.blank(), 2.0);
8314                insta::assert_json_snapshot!(metric.loc);
8315            },
8316        );
8317    }
8318
8319    #[test]
8320    fn php_match_in_expression_lloc() {
8321        // Match inside another expression (e.g. assignment RHS) — the
8322        // outer expression_statement counts, the inner match arms do not.
8323        check_metrics::<PhpParser>(
8324            "<?php
8325$y = 10 + match ($x) { 1 => 2, default => 0 };",
8326            "foo.php",
8327            |metric| {
8328                assert_eq!(metric.loc.sloc(), 2.0);
8329                assert_eq!(metric.loc.ploc(), 2.0);
8330                assert_eq!(metric.loc.lloc(), 1.0);
8331                assert_eq!(metric.loc.cloc(), 0.0);
8332                assert_eq!(metric.loc.blank(), 0.0);
8333                insta::assert_json_snapshot!(metric.loc);
8334            },
8335        );
8336    }
8337
8338    #[test]
8339    fn php_html_island_ploc() {
8340        // Embedded HTML between PHP tags ("text interpolation"). HTML
8341        // rows must contribute to PLOC (they are not blank and not a
8342        // PHP comment); this test locks that behavior so a future
8343        // grammar bump or impl tweak that excludes `text` nodes from
8344        // the default PLOC branch is caught.
8345        check_metrics::<PhpParser>(
8346            "<?php if ($cond): ?>
8347<div>hello</div>
8348<p>world</p>
8349<?php endif; ?>",
8350            "foo.php",
8351            |metric| {
8352                assert_eq!(metric.loc.sloc(), 4.0);
8353                assert_eq!(metric.loc.ploc(), 3.0);
8354                assert_eq!(metric.loc.lloc(), 1.0);
8355                assert_eq!(metric.loc.cloc(), 0.0);
8356                assert_eq!(metric.loc.blank(), 1.0);
8357                insta::assert_json_snapshot!(metric.loc);
8358            },
8359        );
8360    }
8361
8362    #[test]
8363    fn php_short_echo_tag_ploc() {
8364        // `<?=` is the same `php_tag` kind as `<?php` per
8365        // tree-sitter-php 0.24.2. A regression that re-classified `<?=`
8366        // would shift PLOC; this test pins the current behavior.
8367        check_metrics::<PhpParser>("<p><?= $name ?></p>", "foo.php", |metric| {
8368            assert_eq!(metric.loc.sloc(), 1.0);
8369            assert_eq!(metric.loc.ploc(), 1.0);
8370            assert_eq!(metric.loc.lloc(), 1.0);
8371            assert_eq!(metric.loc.cloc(), 0.0);
8372            assert_eq!(metric.loc.blank(), 0.0);
8373            insta::assert_json_snapshot!(metric.loc);
8374        });
8375    }
8376
8377    #[test]
8378    fn elixir_blank() {
8379        // Two blank lines separate three top-level expressions.
8380        check_metrics::<ElixirParser>(
8381            "defmodule Foo do\n\n  def a, do: :a\n\n  def b, do: :b\nend\n",
8382            "foo.ex",
8383            |metric| {
8384                assert_eq!(metric.loc.sloc(), 6.0);
8385                assert_eq!(metric.loc.ploc(), 4.0);
8386                assert_eq!(metric.loc.lloc(), 3.0);
8387                assert_eq!(metric.loc.cloc(), 0.0);
8388                assert_eq!(metric.loc.blank(), 2.0);
8389                insta::assert_json_snapshot!(
8390                    metric.loc,
8391                    @r#"
8392                {
8393                  "sloc": 6.0,
8394                  "ploc": 4.0,
8395                  "lloc": 3.0,
8396                  "cloc": 0.0,
8397                  "blank": 2.0,
8398                  "sloc_average": 1.5,
8399                  "ploc_average": 1.0,
8400                  "lloc_average": 0.75,
8401                  "cloc_average": 0.0,
8402                  "blank_average": 0.5,
8403                  "sloc_min": 6.0,
8404                  "sloc_max": 6.0,
8405                  "cloc_min": 0.0,
8406                  "cloc_max": 0.0,
8407                  "ploc_min": 4.0,
8408                  "ploc_max": 4.0,
8409                  "lloc_min": 3.0,
8410                  "lloc_max": 3.0,
8411                  "blank_min": 2.0,
8412                  "blank_max": 2.0
8413                }
8414                "#
8415                );
8416            },
8417        );
8418    }
8419
8420    #[test]
8421    fn elixir_no_zero_blank() {
8422        // Blank line interleaved with code that carries trailing comments —
8423        // stresses the `blank = sloc - (ploc ∪ cloc lines)` union math.
8424        check_metrics::<ElixirParser>(
8425            "defmodule Foo do\n  def f, do: :ok\n\n  def g, do: :ok # trailing\n  def h, do: :ok # trailing\nend\n",
8426            "foo.ex",
8427            |metric| {
8428                assert_eq!(metric.loc.sloc(), 6.0);
8429                assert_eq!(metric.loc.ploc(), 5.0);
8430                assert_eq!(metric.loc.cloc(), 2.0);
8431                assert_eq!(metric.loc.blank(), 1.0);
8432            },
8433        );
8434    }
8435
8436    #[test]
8437    fn elixir_blank_zero_sanity() {
8438        // Sanity check: blank must report 0, never go negative, when the
8439        // input has no blank lines.
8440        check_metrics::<ElixirParser>(
8441            "defmodule Foo do\n  def f, do: :ok\nend\n",
8442            "foo.ex",
8443            |metric| {
8444                assert_eq!(metric.loc.blank(), 0.0);
8445            },
8446        );
8447    }
8448
8449    #[test]
8450    fn elixir_cloc() {
8451        // Mix of standalone comments and a comment on the same line as
8452        // code. Elixir has no block comment syntax — only `#` lines.
8453        check_metrics::<ElixirParser>(
8454            "# top\ndefmodule Foo do\n  # body\n  def f, do: :ok # trailing\nend\n",
8455            "foo.ex",
8456            |metric| {
8457                assert_eq!(metric.loc.cloc(), 3.0);
8458            },
8459        );
8460    }
8461
8462    #[test]
8463    fn elixir_lloc() {
8464        // Two statements at the top level of the module body — the
8465        // `defmodule` call itself counts as one statement (since its
8466        // parent is `Source`), and each `def` inside its `do_block`
8467        // counts too: 1 + 2 = 3.
8468        check_metrics::<ElixirParser>(
8469            "defmodule Foo do\n  def a, do: 1\n  def b, do: 2\nend\n",
8470            "foo.ex",
8471            |metric| {
8472                assert_eq!(metric.loc.lloc(), 3.0);
8473            },
8474        );
8475    }
8476
8477    #[test]
8478    fn elixir_no_nested_call_lloc() {
8479        // Calls nested inside another call's arguments are NOT direct
8480        // children of a statement container, so they do not bump LLOC.
8481        // Three syntactic calls (`defmodule`, `def`, `IO.puts`) → 3.
8482        check_metrics::<ElixirParser>(
8483            "defmodule Foo do\n  def f do\n    IO.puts(Enum.join([1, 2, 3], \", \"))\n  end\nend\n",
8484            "foo.ex",
8485            |metric| {
8486                assert_eq!(metric.loc.lloc(), 3.0);
8487            },
8488        );
8489    }
8490
8491    #[test]
8492    fn elixir_no_binary_operator_inside_call_lloc() {
8493        // Binary operators inside call arguments are sub-expressions,
8494        // not statements. A single `def` body containing `IO.puts(a + b)`
8495        // produces 3 LLOC (defmodule, def, IO.puts) — the `a + b`
8496        // binary_operator is not a direct child of any statement
8497        // container.
8498        check_metrics::<ElixirParser>(
8499            "defmodule Foo do\n  def f(a, b) do\n    IO.puts(a + b)\n  end\nend\n",
8500            "foo.ex",
8501            |metric| {
8502                assert_eq!(metric.loc.lloc(), 3.0);
8503            },
8504        );
8505    }
8506
8507    #[test]
8508    fn elixir_stab_clause_counts_lloc() {
8509        // Each `stab_clause` arm in a `case do ... end` is a direct
8510        // child of the inner `do_block`, so each one is its own LLOC.
8511        // defmodule + def + case + 3 arms = 6 logical lines.
8512        check_metrics::<ElixirParser>(
8513            "defmodule Foo do\n  def f(x) do\n    case x do\n      1 -> :a\n      2 -> :b\n      _ -> :c\n    end\n  end\nend\n",
8514            "foo.ex",
8515            |metric| {
8516                assert_eq!(metric.loc.lloc(), 6.0);
8517            },
8518        );
8519    }
8520
8521    #[test]
8522    fn elixir_no_comment_lloc() {
8523        // Comments are direct children of a statement container but
8524        // are routed through the dedicated `Comment` arm in `compute`,
8525        // so they MUST NOT bump LLOC. Only `defmodule` and `def`
8526        // contribute LLOC here.
8527        check_metrics::<ElixirParser>(
8528            "# leading\ndefmodule Foo do\n  # inside\n  def f, do: :ok\n  # trailing\nend\n",
8529            "foo.ex",
8530            |metric| {
8531                assert_eq!(metric.loc.lloc(), 2.0);
8532            },
8533        );
8534    }
8535
8536    #[test]
8537    fn elixir_no_do_token_lloc() {
8538        // The `do` and `end` keyword tokens are unnamed leaves inside a
8539        // `do_block`; they must not be counted as statements. A body
8540        // with one expression produces exactly 2 LLOC (defmodule and
8541        // the inner expression).
8542        check_metrics::<ElixirParser>("defmodule Foo do\n  :ok\nend\n", "foo.ex", |metric| {
8543            // `:ok` is an `Atom` whose parent is the module-call's
8544            // `do_block`; that counts. Plus the `defmodule` call.
8545            assert_eq!(metric.loc.lloc(), 2.0);
8546        });
8547    }
8548
8549    #[test]
8550    fn elixir_no_keyword_pair_lloc() {
8551        // `key: value` keyword pairs inside an argument list (`def f,
8552        // do: :ok`) are children of an `arguments` / `keywords` node,
8553        // not a statement container, so they don't bump LLOC.
8554        check_metrics::<ElixirParser>(
8555            "defmodule Foo do\n  def add(a, b), do: a + b\nend\n",
8556            "foo.ex",
8557            |metric| {
8558                // defmodule (1) + def (1) = 2
8559                assert_eq!(metric.loc.lloc(), 2.0);
8560            },
8561        );
8562    }
8563
8564    #[test]
8565    fn elixir_no_string_content_lloc() {
8566        // `quoted_content` chunks inside a heredoc / regular string are
8567        // structural and don't represent statements. A `@moduledoc`
8568        // attribute call with a multi-line string contributes exactly
8569        // one LLOC (the `@moduledoc` call), not one per content line.
8570        check_metrics::<ElixirParser>(
8571            "defmodule Foo do\n  @moduledoc \"\"\"\n  line one\n  line two\n  \"\"\"\n  def f, do: :ok\nend\n",
8572            "foo.ex",
8573            |metric| {
8574                // defmodule + @moduledoc + def = 3
8575                assert_eq!(metric.loc.lloc(), 3.0);
8576            },
8577        );
8578    }
8579
8580    #[test]
8581    fn elixir_rescue_arm_counts_lloc() {
8582        // Each rescue arm's body has a single expression (e.g. `:bad`)
8583        // that counts as one LLOC; the `stab_clause` header itself is
8584        // skipped. The rescue_block named node is also a direct child
8585        // of try's do_block, so it contributes one LLOC too.
8586        // Total: defmodule + def + try + do_it() + rescue_block
8587        //        + 2 arm bodies = 7.
8588        check_metrics::<ElixirParser>(
8589            "defmodule Foo do\n  def safe do\n    try do\n      do_it()\n    rescue\n      ArgumentError -> :bad\n      RuntimeError -> :worse\n    end\n  end\nend\n",
8590            "foo.ex",
8591            |metric| {
8592                assert_eq!(metric.loc.lloc(), 7.0);
8593            },
8594        );
8595    }
8596
8597    #[test]
8598    fn elixir_no_arg_punctuation_lloc() {
8599        // Function-call arguments (`a, b` inside `def add(a, b)`) are
8600        // children of an `arguments` node, not of a statement container.
8601        // They MUST NOT inflate LLOC.
8602        check_metrics::<ElixirParser>(
8603            "defmodule Foo do\n  def add(a, b, c, d) do\n    a + b + c + d\n  end\nend\n",
8604            "foo.ex",
8605            |metric| {
8606                // defmodule + def + (a+b+c+d) = 3
8607                assert_eq!(metric.loc.lloc(), 3.0);
8608            },
8609        );
8610    }
8611
8612    #[test]
8613    fn elixir_no_list_element_lloc() {
8614        // List literal elements live under a `list` node, not a
8615        // statement container — they must not bump LLOC.
8616        check_metrics::<ElixirParser>(
8617            "defmodule Foo do\n  def f do\n    [:a, :b, :c, :d]\n  end\nend\n",
8618            "foo.ex",
8619            |metric| {
8620                // defmodule + def + the list expression = 3
8621                assert_eq!(metric.loc.lloc(), 3.0);
8622            },
8623        );
8624    }
8625
8626    #[test]
8627    fn elixir_no_map_field_lloc() {
8628        // Map `pair`s live under `map`, not a statement container.
8629        check_metrics::<ElixirParser>(
8630            "defmodule Foo do\n  def f do\n    %{a: 1, b: 2, c: 3}\n  end\nend\n",
8631            "foo.ex",
8632            |metric| {
8633                assert_eq!(metric.loc.lloc(), 3.0);
8634            },
8635        );
8636    }
8637
8638    #[test]
8639    fn elixir_anonymous_fn_body_lloc() {
8640        // `lloc()` on the Unit space returns the aggregate (own +
8641        // nested-space) count. Even though the anonymous_function is
8642        // its own function space, the merge step pulls its `lloc` back
8643        // into the parent. Counts:
8644        //   Unit own: defmodule, def, `add = fn ...`, final `add` = 4
8645        //   anon-fn:  `x + 1` body expression                       = 1
8646        //   aggregated total                                        = 5
8647        check_metrics::<ElixirParser>(
8648            "defmodule Foo do\n  def f do\n    add = fn x -> x + 1 end\n    add\n  end\nend\n",
8649            "foo.ex",
8650            |metric| {
8651                assert_eq!(metric.loc.lloc(), 5.0);
8652            },
8653        );
8654    }
8655
8656    #[test]
8657    fn ruby_blank() {
8658        // The parser's root span starts at the first non-blank line, so
8659        // a blank line must sit BETWEEN code lines to be counted.
8660        // expected: line 3 is blank → blank = 1.
8661        check_metrics::<RubyParser>("def foo\n  a = 1\n\n  a + 1\nend\n", "foo.rb", |metric| {
8662            assert_eq!(metric.loc.blank(), 1.0);
8663        });
8664    }
8665
8666    #[test]
8667    fn ruby_no_zero_blank() {
8668        // Mirrors `rust_no_zero_blank`: the blank counter must stay
8669        // non-zero when blank lines sit between code lines that carry
8670        // trailing comments. Catches regressions in the SLOC −
8671        // (PLOC ∪ CLOC) union math when PLOC and CLOC line-sets
8672        // overlap.
8673        check_metrics::<RubyParser>(
8674            "def foo  # entry\n  pool = 0\n\n  server = -42  # negative\n\n  ok = false\nend\n",
8675            "foo.rb",
8676            |metric| {
8677                assert_eq!(metric.loc.blank(), 2.0);
8678            },
8679        );
8680    }
8681
8682    #[test]
8683    fn ruby_cloc() {
8684        // 3 comment lines.
8685        check_metrics::<RubyParser>(
8686            "# one\n# two\n# three\ndef foo\nend\n",
8687            "foo.rb",
8688            |metric| {
8689                assert_eq!(metric.loc.cloc(), 3.0);
8690            },
8691        );
8692    }
8693
8694    #[test]
8695    fn ruby_lloc() {
8696        // expected: 3 logical lines = `def` (Method) + `if` (If) +
8697        // `while` (While). Bare expression-statements (assignments,
8698        // calls) are intentionally NOT counted.
8699        check_metrics::<RubyParser>(
8700            "def foo(a)\n  if a\n    a += 1\n  end\n  while a > 0\n    a -= 1\n  end\nend\n",
8701            "foo.rb",
8702            |metric| {
8703                assert_eq!(metric.loc.lloc(), 3.0);
8704            },
8705        );
8706    }
8707
8708    #[test]
8709    fn ruby_no_call_lloc() {
8710        // expected: 1 logical line (the surrounding `def`). The bare
8711        // method calls `puts 'hello'` and `puts 'world'` are
8712        // intentionally NOT counted — there is no expression_statement
8713        // wrapper to disambiguate them from sub-expressions.
8714        check_metrics::<RubyParser>(
8715            "def foo\n  puts 'hello'\n  puts 'world'\nend\n",
8716            "foo.rb",
8717            |metric| {
8718                assert_eq!(metric.loc.lloc(), 1.0);
8719            },
8720        );
8721    }
8722
8723    #[test]
8724    fn ruby_no_assignment_lloc() {
8725        // Same rationale as `ruby_no_call_lloc`. expected: 1 lloc
8726        // (the `def`); raw assignments aren't counted.
8727        check_metrics::<RubyParser>(
8728            "def foo\n  a = 1\n  b = 2\n  c = a + b\nend\n",
8729            "foo.rb",
8730            |metric| {
8731                assert_eq!(metric.loc.lloc(), 1.0);
8732            },
8733        );
8734    }
8735
8736    #[test]
8737    fn ruby_modifier_lloc() {
8738        // Postfix modifier forms each count as one logical line. A
8739        // `return … if …` parses as an `IfModifier` wrapping a `Return`;
8740        // both fire the LLOC arm so the modifier line contributes +2.
8741        // expected: def(1) + if_modifier(1) + inner return(1)
8742        // + while_modifier(1) + rescue_modifier(1) = 5.
8743        check_metrics::<RubyParser>(
8744            "def foo(a)\n  return a if a.nil?\n  a -= 1 while a > 0\n  parse(a) rescue nil\nend\n",
8745            "foo.rb",
8746            |metric| {
8747                assert_eq!(metric.loc.lloc(), 5.0);
8748            },
8749        );
8750    }
8751
8752    #[test]
8753    fn ruby_class_lloc() {
8754        // expected: 1 class + 1 module + 2 methods = 4.
8755        check_metrics::<RubyParser>(
8756            "module M\n  class C\n    def foo\n    end\n    def bar\n    end\n  end\nend\n",
8757            "foo.rb",
8758            |metric| {
8759                assert_eq!(metric.loc.lloc(), 4.0);
8760            },
8761        );
8762    }
8763
8764    #[test]
8765    fn ruby_begin_rescue_lloc() {
8766        // expected: 1 def + 1 begin = 2. Rescue clauses are part of
8767        // the begin construct and not separately counted; the bare
8768        // expression body lines are not statements.
8769        check_metrics::<RubyParser>(
8770            "def foo\n  begin\n    risky\n  rescue StandardError\n    nil\n  end\nend\n",
8771            "foo.rb",
8772            |metric| {
8773                assert_eq!(metric.loc.lloc(), 2.0);
8774            },
8775        );
8776    }
8777
8778    #[test]
8779    fn ruby_nested_defs_lloc() {
8780        // Each `Method` declaration contributes one logical line.
8781        // expected: outer `def` + inner `def` = 2.
8782        check_metrics::<RubyParser>(
8783            "def outer\n  def inner\n    1\n  end\nend\n",
8784            "foo.rb",
8785            |metric| {
8786                assert_eq!(metric.loc.lloc(), 2.0);
8787            },
8788        );
8789    }
8790
8791    #[test]
8792    fn ruby_no_block_body_lloc() {
8793        // A top-level `[1,2,3].each do |x| puts x end` produces zero
8794        // logical lines: the surrounding `.each` is a `Call` (not in
8795        // the LLOC arm), the `DoBlock` is a closure (also not a
8796        // statement), and the `puts x` inside is another call. This
8797        // pins the documented expression-statement exclusion.
8798        check_metrics::<RubyParser>(
8799            "[1, 2, 3].each do |x|\n  puts x\nend\n",
8800            "foo.rb",
8801            |metric| {
8802                assert_eq!(metric.loc.lloc(), 0.0);
8803            },
8804        );
8805    }
8806
8807    #[test]
8808    fn ruby_no_lambda_body_lloc() {
8809        // `add = ->(a, b) { a + b }` produces zero logical lines for
8810        // the same reason as `ruby_no_block_body_lloc`: assignments,
8811        // calls, and lambda bodies are intentionally not statements
8812        // in this impl.
8813        check_metrics::<RubyParser>("add = ->(a, b) {\n  a + b\n}\n", "foo.rb", |metric| {
8814            assert_eq!(metric.loc.lloc(), 0.0);
8815        });
8816    }
8817
8818    #[test]
8819    fn ruby_heredoc_lloc_and_blank() {
8820        // A `<<~TXT` heredoc contributes: SLOC = every line in the
8821        // file (including heredoc body); PLOC = the def header,
8822        // assignment, heredoc-end marker, trailing identifier, and
8823        // closing `end`; LLOC = just the surrounding `def`. The
8824        // heredoc-body lines are counted as `blank` (they have no
8825        // grammar-visible non-comment tokens past the literal-content
8826        // marker).
8827        // expected: sloc = 7, ploc = 5, lloc = 1, blank = 2.
8828        check_metrics::<RubyParser>(
8829            "def foo\n  msg = <<~TXT\n    one\n    two\n  TXT\n  msg\nend\n",
8830            "foo.rb",
8831            |metric| {
8832                assert_eq!(metric.loc.sloc(), 7.0);
8833                assert_eq!(metric.loc.ploc(), 5.0);
8834                assert_eq!(metric.loc.lloc(), 1.0);
8835                assert_eq!(metric.loc.blank(), 2.0);
8836            },
8837        );
8838    }
8839
8840    #[test]
8841    fn ruby_semicolon_multistatement_lloc_undercount() {
8842        // Documented limitation: Ruby has no `expression_statement`
8843        // wrapper, so `;`-separated multi-statement lines collapse to
8844        // a single LLOC bump (the surrounding `def`). A future
8845        // statement-counter that walks BlockBody children would
8846        // change this — pin the current behaviour so the regression
8847        // is visible.
8848        check_metrics::<RubyParser>(
8849            "def foo\n  a = 1; b = 2; a + b\nend\n",
8850            "foo.rb",
8851            |metric| {
8852                assert_eq!(metric.loc.lloc(), 1.0);
8853            },
8854        );
8855    }
8856
8857    #[test]
8858    fn ruby_ploc_skips_comments_and_blanks() {
8859        // PLOC counts physical instruction lines: code-bearing lines
8860        // only. Comments and blanks are excluded.
8861        check_metrics::<RubyParser>("# header\n\ndef foo\n  a = 1\nend\n", "foo.rb", |metric| {
8862            assert_eq!(metric.loc.ploc(), 3.0);
8863            assert_eq!(metric.loc.cloc(), 1.0);
8864            assert_eq!(metric.loc.blank(), 1.0);
8865        });
8866    }
8867
8868    // -----------------------------------------------------------------
8869    // Issue #195: nested-function/closure LLOC tests for 11 languages.
8870    // Mirrors the prior art for Rust (`rust_function_in_loop_lloc`,
8871    // `rust_closure_expression_lloc`), Mozjs (`mozjs_nested_function_loc`),
8872    // Bash (`bash_nested_function_loc`), and TypeScript
8873    // (`typescript_nested_functions_loc`, `tsx_nested_functions_loc`).
8874    // -----------------------------------------------------------------
8875
8876    #[test]
8877    fn python_nested_def_lloc() {
8878        // Nested `def`: the inner function declaration plus the outer
8879        // body's `return inner()` are both LLOC; the outer `def` header
8880        // and the inner `return 1` belong to their own function spaces.
8881        check_metrics::<PythonParser>(
8882            "def outer():\n    def inner():\n        return 1\n    return inner()\n",
8883            "foo.py",
8884            |metric| {
8885                assert_eq!(metric.loc.sloc(), 4.0);
8886                assert_eq!(metric.loc.ploc(), 4.0);
8887                assert_eq!(metric.loc.lloc(), 2.0);
8888                assert_eq!(metric.loc.cloc(), 0.0);
8889                assert_eq!(metric.loc.blank(), 0.0);
8890                insta::assert_json_snapshot!(metric.loc);
8891            },
8892        );
8893    }
8894
8895    #[test]
8896    fn python_lambda_in_def_lloc() {
8897        // `lambda x: x + 1` is an expression, not a Python `function_definition`,
8898        // so it does not start a new function space. The two LLOC come from
8899        // the assignment `f = lambda ...` and the `return f(2)` statement.
8900        check_metrics::<PythonParser>(
8901            "def outer():\n    f = lambda x: x + 1\n    return f(2)\n",
8902            "foo.py",
8903            |metric| {
8904                assert_eq!(metric.loc.sloc(), 3.0);
8905                assert_eq!(metric.loc.ploc(), 3.0);
8906                assert_eq!(metric.loc.lloc(), 2.0);
8907                assert_eq!(metric.loc.cloc(), 0.0);
8908                assert_eq!(metric.loc.blank(), 0.0);
8909                insta::assert_json_snapshot!(metric.loc);
8910            },
8911        );
8912    }
8913
8914    #[test]
8915    fn java_local_class_in_method_lloc() {
8916        // A `class` declared inside a method body produces its own function
8917        // space, so the outer method's LLOC only sees `return new Local().v();`
8918        // and the body of `v()` contributes the second LLOC.
8919        check_metrics::<JavaParser>(
8920            "class Foo {\n    int bar() {\n        class Local {\n            int v() { return 1; }\n        }\n        return new Local().v();\n    }\n}\n",
8921            "foo.java",
8922            |metric| {
8923                assert_eq!(metric.loc.sloc(), 8.0);
8924                assert_eq!(metric.loc.ploc(), 8.0);
8925                assert_eq!(metric.loc.lloc(), 2.0);
8926                assert_eq!(metric.loc.cloc(), 0.0);
8927                assert_eq!(metric.loc.blank(), 0.0);
8928                insta::assert_json_snapshot!(metric.loc);
8929            },
8930        );
8931    }
8932
8933    #[test]
8934    fn java_lambda_in_method_lloc() {
8935        // Java lambdas are expressions; the two LLOC come from the
8936        // `IntUnaryOperator f = x -> x + 1;` declaration and the
8937        // `f.applyAsInt(3);` expression statement.
8938        check_metrics::<JavaParser>(
8939            "class Foo {\n    void bar() {\n        java.util.function.IntUnaryOperator f = x -> x + 1;\n        f.applyAsInt(3);\n    }\n}\n",
8940            "foo.java",
8941            |metric| {
8942                assert_eq!(metric.loc.sloc(), 6.0);
8943                assert_eq!(metric.loc.ploc(), 6.0);
8944                assert_eq!(metric.loc.lloc(), 2.0);
8945                assert_eq!(metric.loc.cloc(), 0.0);
8946                assert_eq!(metric.loc.blank(), 0.0);
8947                insta::assert_json_snapshot!(metric.loc);
8948            },
8949        );
8950    }
8951
8952    #[test]
8953    fn groovy_blank() {
8954        // Blank lines + simple statements. Newlines act as the
8955        // statement terminator; PLOC counts the two declaration lines.
8956        check_metrics::<GroovyParser>("int x = 1\n\n\nint y = 2", "foo.groovy", |metric| {
8957            assert_eq!(metric.loc.sloc(), 4.0);
8958            assert_eq!(metric.loc.ploc(), 2.0);
8959            assert_eq!(metric.loc.lloc(), 2.0);
8960            assert_eq!(metric.loc.blank(), 2.0);
8961        });
8962    }
8963
8964    #[test]
8965    fn groovy_no_zero_blank() {
8966        // A single line with no blanks: blank() == 0.
8967        check_metrics::<GroovyParser>("int x = 1", "foo.groovy", |metric| {
8968            assert_eq!(metric.loc.sloc(), 1.0);
8969            assert_eq!(metric.loc.blank(), 0.0);
8970        });
8971    }
8972
8973    #[test]
8974    fn groovy_cloc_line_comments() {
8975        check_metrics::<GroovyParser>(
8976            "// first comment
8977            int x = 1
8978            // second comment
8979            int y = 2",
8980            "foo.groovy",
8981            |metric| {
8982                assert_eq!(metric.loc.cloc(), 2.0);
8983                assert_eq!(metric.loc.ploc(), 2.0);
8984            },
8985        );
8986    }
8987
8988    #[test]
8989    fn groovy_cloc_block_comment() {
8990        check_metrics::<GroovyParser>(
8991            "/* multi
8992               line
8993               comment */
8994            int x = 1",
8995            "foo.groovy",
8996            |metric| {
8997                // Block comment spans 3 lines → cloc == 3.
8998                assert_eq!(metric.loc.cloc(), 3.0);
8999            },
9000        );
9001    }
9002
9003    #[test]
9004    fn groovy_simple_lloc() {
9005        // One LLOC per simple expression statement.
9006        check_metrics::<GroovyParser>(
9007            "int a = 1
9008            int b = 2
9009            int c = 3",
9010            "foo.groovy",
9011            |metric| {
9012                assert_eq!(metric.loc.lloc(), 3.0);
9013            },
9014        );
9015    }
9016
9017    #[test]
9018    fn groovy_no_local_variable_declaration_in_for_lloc() {
9019        // The variable declaration inside a classic `for` init slot
9020        // does NOT count as an LLOC (it's an expression part of the
9021        // for-loop). Same gating as Java's `java_for_lloc`.
9022        check_metrics::<GroovyParser>(
9023            "for (int i = 0; i < 10; i++) {
9024                println(i)
9025            }",
9026            "foo.groovy",
9027            |metric| {
9028                // for-statement (1) + expression-statement `println(i)` (1) = 2
9029                assert_eq!(metric.loc.lloc(), 2.0);
9030            },
9031        );
9032    }
9033
9034    #[test]
9035    fn groovy_lambda_in_method_lloc() {
9036        // Closures contain a statement list — the dekobon grammar wraps
9037        // a single-expression body in `expression_statement` rather than
9038        // emitting the expression directly (as Java's `lambda_expression`
9039        // does), so a one-line closure body counts as its own LLOC.
9040        // Declaration `def f = …` (1) + closure body `x + 1` (1) +
9041        // call `f(3)` (1) = 3.
9042        check_metrics::<GroovyParser>(
9043            "class Foo {
9044                void bar() {
9045                    def f = { x -> x + 1 }
9046                    f(3)
9047                }
9048            }",
9049            "foo.groovy",
9050            |metric| {
9051                assert_eq!(metric.loc.lloc(), 3.0);
9052            },
9053        );
9054    }
9055
9056    #[test]
9057    fn groovy_try_lloc() {
9058        // try-statement counts as one LLOC; the catch body's
9059        // statements count separately.
9060        check_metrics::<GroovyParser>(
9061            "void f() {
9062                try {
9063                    risky()
9064                } catch (Exception e) {
9065                    handle(e)
9066                }
9067            }",
9068            "foo.groovy",
9069            |metric| {
9070                // try(1) + risky() expr-stmt(1) + handle() expr-stmt(1) = 3
9071                assert_eq!(metric.loc.lloc(), 3.0);
9072            },
9073        );
9074    }
9075
9076    #[test]
9077    fn groovy_class_loc() {
9078        // Source-file-level totals across multiple methods.
9079        check_metrics::<GroovyParser>(
9080            "class A {
9081                void f() {
9082                    int x = 1
9083                }
9084                void g() {
9085                    int y = 2
9086                }
9087            }",
9088            "foo.groovy",
9089            |metric| {
9090                // 8 lines of non-comment content: `class A {`, two
9091                // `void` headers, two `int … = …` body statements,
9092                // three closing braces.
9093                assert_eq!(metric.loc.ploc(), 8.0);
9094                assert_eq!(metric.loc.cloc(), 0.0);
9095                // Two expression-statement LLOCs (`int x = 1`,
9096                // `int y = 2`).
9097                assert_eq!(metric.loc.lloc(), 2.0);
9098            },
9099        );
9100    }
9101
9102    #[test]
9103    fn groovy_partial_parse_recovers_unit() {
9104        // Malformed input parses with ERROR but still emits a Unit
9105        // root via `spaces.rs` fallback (lesson 9). The single
9106        // source line is counted as SLOC even when the parse fails
9107        // mid-expression.
9108        check_metrics::<GroovyParser>("def x = (((", "foo.groovy", |metric| {
9109            assert_eq!(metric.loc.sloc(), 1.0);
9110            assert_eq!(metric.loc.blank(), 0.0);
9111        });
9112    }
9113
9114    #[test]
9115    fn groovy_sloc() {
9116        // Mirrors `java_sloc`: basic per-line count across a mix of
9117        // statements and a blank line.
9118        check_metrics::<GroovyParser>(
9119            "int a = 1
9120            int b = 2
9121
9122            int c = 3",
9123            "foo.groovy",
9124            |metric| {
9125                assert_eq!(metric.loc.sloc(), 4.0);
9126                assert_eq!(metric.loc.ploc(), 3.0);
9127                assert_eq!(metric.loc.blank(), 1.0);
9128            },
9129        );
9130    }
9131
9132    #[test]
9133    fn groovy_single_ploc() {
9134        // Mirrors `java_single_ploc`: one non-blank, non-comment
9135        // line of code => ploc == 1.
9136        check_metrics::<GroovyParser>("int x = 42", "foo.groovy", |metric| {
9137            assert_eq!(metric.loc.ploc(), 1.0);
9138            assert_eq!(metric.loc.cloc(), 0.0);
9139        });
9140    }
9141
9142    #[test]
9143    fn groovy_multi_ploc() {
9144        // Multiple statements on separate lines all contribute to
9145        // PLOC. Mirrors `java_multi_ploc`.
9146        check_metrics::<GroovyParser>(
9147            "int a = 1
9148            int b = 2
9149            int c = 3
9150            int d = 4",
9151            "foo.groovy",
9152            |metric| {
9153                assert_eq!(metric.loc.ploc(), 4.0);
9154                assert_eq!(metric.loc.lloc(), 4.0);
9155            },
9156        );
9157    }
9158
9159    #[test]
9160    fn groovy_single_statement_lloc() {
9161        // A single expression statement contributes one LLOC.
9162        // Mirrors `java_single_statement_lloc`.
9163        check_metrics::<GroovyParser>("println 'hi'", "foo.groovy", |metric| {
9164            assert_eq!(metric.loc.lloc(), 1.0);
9165        });
9166    }
9167
9168    #[test]
9169    fn groovy_for_lloc() {
9170        // The classical `for` statement itself counts as one LLOC;
9171        // the body's `println(i)` adds another. The init-slot
9172        // var-decl is suppressed by the LocalVariableDeclaration
9173        // ancestor-check (same rule as `java_for_lloc`).
9174        check_metrics::<GroovyParser>(
9175            "for (int i = 0; i < 100; i++) {
9176                println(i)
9177            }",
9178            "foo.groovy",
9179            |metric| {
9180                // ForStatement(1) + println-expr(1) = 2
9181                assert_eq!(metric.loc.lloc(), 2.0);
9182            },
9183        );
9184    }
9185
9186    #[test]
9187    fn groovy_foreach_lloc() {
9188        // `for (item in list)` parses as `enhanced_for_statement` —
9189        // counts as one LLOC.
9190        check_metrics::<GroovyParser>(
9191            "for (item in items) {
9192                println(item)
9193            }",
9194            "foo.groovy",
9195            |metric| {
9196                // EnhancedForStatement(1) + println(1) = 2
9197                assert_eq!(metric.loc.lloc(), 2.0);
9198            },
9199        );
9200    }
9201
9202    #[test]
9203    fn groovy_while_lloc() {
9204        // `while` itself is one LLOC; each body statement adds
9205        // another. Mirrors `java_while_lloc`.
9206        check_metrics::<GroovyParser>(
9207            "int i = 0
9208            while (i < 10) {
9209                i++
9210                println(i)
9211            }",
9212            "foo.groovy",
9213            |metric| {
9214                // int i = 0 (1) + while (1) + i++ (1) + println (1) = 4
9215                assert_eq!(metric.loc.lloc(), 4.0);
9216            },
9217        );
9218    }
9219
9220    #[test]
9221    fn groovy_do_while_lloc() {
9222        // `do…while` is one LLOC plus its body. Mirrors
9223        // `java_do_while_lloc`.
9224        check_metrics::<GroovyParser>(
9225            "int i = 0
9226            do {
9227                i++
9228            } while (i < 5)",
9229            "foo.groovy",
9230            |metric| {
9231                // int i = 0 (1) + do (1) + i++ (1) = 3
9232                assert_eq!(metric.loc.lloc(), 3.0);
9233            },
9234        );
9235    }
9236
9237    #[test]
9238    fn groovy_continue_lloc() {
9239        // `continue` is an LLOC. Same gating as `java_continue_lloc`.
9240        check_metrics::<GroovyParser>(
9241            "for (int i = 0; i < 10; i++) {
9242                if (i == 5) {
9243                    continue
9244                }
9245                println(i)
9246            }",
9247            "foo.groovy",
9248            |metric| {
9249                // for(1) + if(1) + continue(1) + println(1) = 4
9250                assert_eq!(metric.loc.lloc(), 4.0);
9251            },
9252        );
9253    }
9254
9255    #[test]
9256    fn groovy_expressions_lloc() {
9257        // A bag of expression statements: each independent
9258        // expr-stmt is one LLOC. Mirrors `java_expressions_lloc`.
9259        check_metrics::<GroovyParser>(
9260            "int a = 1
9261            a = 2
9262            a += 3
9263            println(a)
9264            doSomething()",
9265            "foo.groovy",
9266            |metric| {
9267                // 5 expression-statement lines.
9268                assert_eq!(metric.loc.lloc(), 5.0);
9269            },
9270        );
9271    }
9272
9273    #[test]
9274    fn groovy_throw_lloc() {
9275        // `throw` is one LLOC via the `ThrowStatement` arm.
9276        check_metrics::<GroovyParser>(
9277            "throw new RuntimeException('bad')",
9278            "foo.groovy",
9279            |metric| {
9280                assert_eq!(metric.loc.lloc(), 1.0);
9281            },
9282        );
9283    }
9284
9285    #[test]
9286    fn groovy_general_loc() {
9287        // Comprehensive mix: class + method + control flow.
9288        // Mirrors `java_general_loc`'s coverage shape.
9289        //
9290        // LLOC = 4, fully attributable:
9291        //   IfStatement (the outer if/else):     +1
9292        //   `println(x)`     (JuxtFunctionCall):  +1
9293        //   `println 'neg'` (JuxtFunctionCall):  +1
9294        //   `return`        (ReturnStatement):   +1
9295        // The else-branch's `expression_statement (closure)`
9296        // wrapper does NOT count — see the bare-Closure carve-out
9297        // in `impl Loc for GroovyCode::compute`.
9298        check_metrics::<GroovyParser>(
9299            "class A {
9300                void f(int x) {
9301                    if (x > 0) {
9302                        println(x)
9303                    } else {
9304                        println 'neg'
9305                    }
9306                    return
9307                }
9308            }",
9309            "foo.groovy",
9310            |metric| {
9311                assert_eq!(metric.loc.lloc(), 4.0);
9312                assert_eq!(metric.loc.cloc(), 0.0);
9313            },
9314        );
9315    }
9316
9317    #[test]
9318    fn csharp_local_function_in_method_lloc() {
9319        // C# local functions (`int Inner(int x) { ... }` inside `Bar()`)
9320        // open their own function space, so the outer method sees only
9321        // `return Inner(2);` plus the inner body's `return x + 1;`.
9322        check_metrics::<CsharpParser>(
9323            "class Foo {\n    int Bar() {\n        int Inner(int x) { return x + 1; }\n        return Inner(2);\n    }\n}\n",
9324            "foo.cs",
9325            |metric| {
9326                assert_eq!(metric.loc.sloc(), 6.0);
9327                assert_eq!(metric.loc.ploc(), 6.0);
9328                assert_eq!(metric.loc.lloc(), 2.0);
9329                assert_eq!(metric.loc.cloc(), 0.0);
9330                assert_eq!(metric.loc.blank(), 0.0);
9331                insta::assert_json_snapshot!(metric.loc);
9332            },
9333        );
9334    }
9335
9336    #[test]
9337    fn csharp_lambda_in_method_lloc() {
9338        // C# lambdas are expressions: the two LLOC come from the
9339        // `Func<int,int> f = x => x + 1;` declaration and the `f(3);` call.
9340        check_metrics::<CsharpParser>(
9341            "class Foo {\n    void Bar() {\n        System.Func<int, int> f = x => x + 1;\n        f(3);\n    }\n}\n",
9342            "foo.cs",
9343            |metric| {
9344                assert_eq!(metric.loc.sloc(), 6.0);
9345                assert_eq!(metric.loc.ploc(), 6.0);
9346                assert_eq!(metric.loc.lloc(), 2.0);
9347                assert_eq!(metric.loc.cloc(), 0.0);
9348                assert_eq!(metric.loc.blank(), 0.0);
9349                insta::assert_json_snapshot!(metric.loc);
9350            },
9351        );
9352    }
9353
9354    #[test]
9355    fn cpp_lambda_in_function_lloc() {
9356        // C++11 lambdas are expressions. The outer function `bar()` produces
9357        // two LLOC for the body: `auto f = [](int x) { return x + 1; };` and
9358        // `return f(2);`. The lambda's inner `return x + 1;` is part of the
9359        // lambda body inside the same function space (lambdas do not open a
9360        // new FuncSpace in this implementation), so it adds a third LLOC.
9361        // Closes the parity gap with #195 (which covered 11 other
9362        // languages but omitted C++).
9363        check_metrics::<CppParser>(
9364            "int bar() {\n    auto f = [](int x) { return x + 1; };\n    return f(2);\n}\n",
9365            "foo.cpp",
9366            |metric| {
9367                assert_eq!(metric.loc.sloc(), 4.0);
9368                assert_eq!(metric.loc.ploc(), 4.0);
9369                assert_eq!(metric.loc.lloc(), 3.0);
9370                assert_eq!(metric.loc.cloc(), 0.0);
9371                assert_eq!(metric.loc.blank(), 0.0);
9372                insta::assert_json_snapshot!(metric.loc);
9373            },
9374        );
9375    }
9376
9377    #[test]
9378    fn javascript_nested_function_lloc() {
9379        // Nested function_declaration: 4 LLOC = outer's `return inner();`,
9380        // inner's `return 1;`, plus the two function declarations
9381        // themselves (the JS Checker counts function declarations as LLOC).
9382        check_metrics::<JavascriptParser>(
9383            "function outer() {\n    function inner() {\n        return 1;\n    }\n    return inner();\n}\n",
9384            "foo.js",
9385            |metric| {
9386                assert_eq!(metric.loc.sloc(), 6.0);
9387                assert_eq!(metric.loc.ploc(), 6.0);
9388                assert_eq!(metric.loc.lloc(), 4.0);
9389                assert_eq!(metric.loc.cloc(), 0.0);
9390                assert_eq!(metric.loc.blank(), 0.0);
9391                insta::assert_json_snapshot!(metric.loc);
9392            },
9393        );
9394    }
9395
9396    #[test]
9397    fn javascript_arrow_function_lloc() {
9398        // The arrow function `(x) => x + 1` is an expression: the LLOC
9399        // come from `const inner = ...;` and `return inner(2);`.
9400        check_metrics::<JavascriptParser>(
9401            "function outer() {\n    const inner = (x) => x + 1;\n    return inner(2);\n}\n",
9402            "foo.js",
9403            |metric| {
9404                assert_eq!(metric.loc.sloc(), 4.0);
9405                assert_eq!(metric.loc.ploc(), 4.0);
9406                assert_eq!(metric.loc.lloc(), 2.0);
9407                assert_eq!(metric.loc.cloc(), 0.0);
9408                assert_eq!(metric.loc.blank(), 0.0);
9409                insta::assert_json_snapshot!(metric.loc);
9410            },
9411        );
9412    }
9413
9414    #[test]
9415    fn kotlin_lambda_literal_in_fun_lloc() {
9416        // A lambda literal (`{ x -> x + 1 }`) assigned to a `val` plus the
9417        // following call yields two LLOC at the outer function.
9418        check_metrics::<KotlinParser>(
9419            "fun outer() {\n    val f: (Int) -> Int = { x -> x + 1 }\n    f(3)\n}\n",
9420            "foo.kt",
9421            |metric| {
9422                assert_eq!(metric.loc.sloc(), 4.0);
9423                assert_eq!(metric.loc.ploc(), 4.0);
9424                assert_eq!(metric.loc.lloc(), 2.0);
9425                assert_eq!(metric.loc.cloc(), 0.0);
9426                assert_eq!(metric.loc.blank(), 0.0);
9427                insta::assert_json_snapshot!(metric.loc);
9428            },
9429        );
9430    }
9431
9432    #[test]
9433    fn kotlin_local_fun_in_fun_lloc() {
9434        // Kotlin's local `fun inner(...)` is also a function_declaration,
9435        // so it opens its own space; the outer LLOC reduces to `inner(3)`,
9436        // and the inner body contributes the second LLOC.
9437        check_metrics::<KotlinParser>(
9438            "fun outer() {\n    fun inner(x: Int): Int { return x + 1 }\n    inner(3)\n}\n",
9439            "foo.kt",
9440            |metric| {
9441                assert_eq!(metric.loc.sloc(), 4.0);
9442                assert_eq!(metric.loc.ploc(), 4.0);
9443                assert_eq!(metric.loc.lloc(), 2.0);
9444                assert_eq!(metric.loc.cloc(), 0.0);
9445                assert_eq!(metric.loc.blank(), 0.0);
9446                insta::assert_json_snapshot!(metric.loc);
9447            },
9448        );
9449    }
9450
9451    #[test]
9452    fn kotlin_object_expression_in_fun_lloc() {
9453        // An `object : Runnable { ... }` expression with an overridden
9454        // method whose body invokes `println("hi")`. LLOC: `val r = ...`,
9455        // the override's body call, and the outer `r.run()` call = 3.
9456        check_metrics::<KotlinParser>(
9457            "fun outer() {\n    val r = object : Runnable { override fun run() { println(\"hi\") } }\n    r.run()\n}\n",
9458            "foo.kt",
9459            |metric| {
9460                assert_eq!(metric.loc.sloc(), 4.0);
9461                assert_eq!(metric.loc.ploc(), 4.0);
9462                assert_eq!(metric.loc.lloc(), 3.0);
9463                assert_eq!(metric.loc.cloc(), 0.0);
9464                assert_eq!(metric.loc.blank(), 0.0);
9465                insta::assert_json_snapshot!(metric.loc);
9466            },
9467        );
9468    }
9469
9470    #[test]
9471    fn go_function_literal_initializer_lloc() {
9472        // `inner := func(x int) int { return x + 1 }` — the function
9473        // literal opens its own space; LLOC visible on the outer space:
9474        // the assignment + `return inner(2)` = 2, plus the literal's
9475        // `return x + 1` body = 3 aggregated.
9476        check_metrics::<GoParser>(
9477            "package main\nfunc outer() int {\n    inner := func(x int) int { return x + 1 }\n    return inner(2)\n}\n",
9478            "foo.go",
9479            |metric| {
9480                assert_eq!(metric.loc.sloc(), 5.0);
9481                assert_eq!(metric.loc.ploc(), 5.0);
9482                assert_eq!(metric.loc.lloc(), 3.0);
9483                assert_eq!(metric.loc.cloc(), 0.0);
9484                assert_eq!(metric.loc.blank(), 0.0);
9485                insta::assert_json_snapshot!(metric.loc);
9486            },
9487        );
9488    }
9489
9490    #[test]
9491    fn php_anonymous_function_in_function_lloc() {
9492        // Anonymous function `function ($x) { return $x + 1; }`: outer
9493        // sees the assignment + `return $f(2);`, the closure body adds
9494        // `return $x + 1;` for 3 LLOC aggregated.
9495        check_metrics::<PhpParser>(
9496            "<?php\nfunction outer() {\n    $f = function ($x) { return $x + 1; };\n    return $f(2);\n}\n",
9497            "foo.php",
9498            |metric| {
9499                assert_eq!(metric.loc.sloc(), 5.0);
9500                assert_eq!(metric.loc.ploc(), 5.0);
9501                assert_eq!(metric.loc.lloc(), 3.0);
9502                assert_eq!(metric.loc.cloc(), 0.0);
9503                assert_eq!(metric.loc.blank(), 0.0);
9504                insta::assert_json_snapshot!(metric.loc);
9505            },
9506        );
9507    }
9508
9509    #[test]
9510    fn php_arrow_function_in_function_lloc() {
9511        // The `fn ($x) => $x + 1` arrow function is an expression; the
9512        // outer function sees only its assignment and the `return $f(2);`.
9513        check_metrics::<PhpParser>(
9514            "<?php\nfunction outer() {\n    $f = fn ($x) => $x + 1;\n    return $f(2);\n}\n",
9515            "foo.php",
9516            |metric| {
9517                assert_eq!(metric.loc.sloc(), 5.0);
9518                assert_eq!(metric.loc.ploc(), 5.0);
9519                assert_eq!(metric.loc.lloc(), 2.0);
9520                assert_eq!(metric.loc.cloc(), 0.0);
9521                assert_eq!(metric.loc.blank(), 0.0);
9522                insta::assert_json_snapshot!(metric.loc);
9523            },
9524        );
9525    }
9526
9527    #[test]
9528    fn lua_nested_local_function_lloc() {
9529        // Two nested `local function` declarations: outer + inner both
9530        // count as `function_declaration` LLOC, plus the two `return`
9531        // statements = 4 aggregated.
9532        check_metrics::<LuaParser>(
9533            "local function outer()\n    local function inner()\n        return 1\n    end\n    return inner()\nend\n",
9534            "foo.lua",
9535            |metric| {
9536                assert_eq!(metric.loc.sloc(), 6.0);
9537                assert_eq!(metric.loc.ploc(), 6.0);
9538                assert_eq!(metric.loc.lloc(), 4.0);
9539                assert_eq!(metric.loc.cloc(), 0.0);
9540                assert_eq!(metric.loc.blank(), 0.0);
9541                insta::assert_json_snapshot!(metric.loc);
9542            },
9543        );
9544    }
9545
9546    #[test]
9547    fn lua_function_expression_in_local_decl_lloc() {
9548        // `local f = function (x) return x + 1 end` — the function
9549        // expression is its own space; aggregated LLOC: outer
9550        // declaration, the inner expression's declaration, the inner
9551        // `return x + 1`, and the outer `return f(2)` = 4.
9552        check_metrics::<LuaParser>(
9553            "local function outer()\n    local f = function (x) return x + 1 end\n    return f(2)\nend\n",
9554            "foo.lua",
9555            |metric| {
9556                assert_eq!(metric.loc.sloc(), 4.0);
9557                assert_eq!(metric.loc.ploc(), 4.0);
9558                assert_eq!(metric.loc.lloc(), 4.0);
9559                assert_eq!(metric.loc.cloc(), 0.0);
9560                assert_eq!(metric.loc.blank(), 0.0);
9561                insta::assert_json_snapshot!(metric.loc);
9562            },
9563        );
9564    }
9565
9566    #[test]
9567    fn tcl_apply_closure_lloc() {
9568        // `apply $f 2` is a regular Tcl command, not a separate function
9569        // space — tree-sitter-tcl does not model `apply { ... }` as a
9570        // closure construct distinct from any other command. We assert
9571        // the observed LLOC (proc, set, apply, plus the nested `expr`
9572        // command substitution inside the lambda body) so any future
9573        // change to lambda-body counting is caught here.
9574        check_metrics::<TclParser>(
9575            "proc outer {} {\n    set f [list x {return [expr {$x + 1}]}]\n    apply $f 2\n}\n",
9576            "foo.tcl",
9577            |metric| {
9578                assert_eq!(metric.loc.sloc(), 4.0);
9579                assert_eq!(metric.loc.ploc(), 4.0);
9580                assert_eq!(metric.loc.lloc(), 4.0);
9581                assert_eq!(metric.loc.cloc(), 0.0);
9582                assert_eq!(metric.loc.blank(), 0.0);
9583                insta::assert_json_snapshot!(metric.loc);
9584            },
9585        );
9586    }
9587
9588    #[test]
9589    fn perl_anonymous_sub_in_sub_lloc() {
9590        // Anonymous sub `sub { ... }` opens its own function space; the
9591        // outer LLOC counts the `my $f = ...;` declaration plus
9592        // `return $f->(2);`, and the anonymous sub contributes
9593        // `return $_[0] + 1;` for 2 LLOC.
9594        //
9595        // NOTE: a prior LLOC for this construct exists as
9596        // `perl_lloc_anonymous_function` (top-level form) — this test
9597        // asserts the same shape *inside* another sub, exercising space
9598        // nesting.
9599        check_metrics::<PerlParser>(
9600            "sub outer {\n    my $f = sub { return $_[0] + 1 };\n    return $f->(2);\n}\n",
9601            "foo.pl",
9602            |metric| {
9603                assert_eq!(metric.loc.sloc(), 4.0);
9604                assert_eq!(metric.loc.ploc(), 4.0);
9605                assert_eq!(metric.loc.lloc(), 2.0);
9606                assert_eq!(metric.loc.cloc(), 0.0);
9607                assert_eq!(metric.loc.blank(), 0.0);
9608                insta::assert_json_snapshot!(metric.loc);
9609            },
9610        );
9611    }
9612
9613    #[test]
9614    fn perl_named_sub_in_sub_lloc() {
9615        // Perl `sub` declarations are not LLOC (see
9616        // `perl_lloc_function_definition_not_counted`); inside `outer`,
9617        // only `return inner();` is LLOC, and `inner`'s `return 1` is in
9618        // its own space contributing one more aggregated LLOC.
9619        // Total aggregated LLOC: 1.
9620        //
9621        // Observation: lloc=1, not 2. Perl LLOC is anchored on `;`
9622        // tokens whose parent is `SourceFile` or `Block` (see
9623        // `PerlCode::compute` in this file). The bare `return 1` inside
9624        // `sub inner { ... }` has no trailing `;`, so it does not bump
9625        // LLOC. The outer `return inner();` carries the only SEMI.
9626        // This is intentional Perl behaviour and not a bug — Perl
9627        // requires `;` between statements; a single trailing statement
9628        // before `}` is syntactically optional. Asserted as-is.
9629        check_metrics::<PerlParser>(
9630            "sub outer {\n    sub inner { return 1 }\n    return inner();\n}\n",
9631            "foo.pl",
9632            |metric| {
9633                assert_eq!(metric.loc.sloc(), 4.0);
9634                assert_eq!(metric.loc.ploc(), 4.0);
9635                assert_eq!(metric.loc.lloc(), 1.0);
9636                assert_eq!(metric.loc.cloc(), 0.0);
9637                assert_eq!(metric.loc.blank(), 0.0);
9638                insta::assert_json_snapshot!(metric.loc);
9639            },
9640        );
9641    }
9642
9643    #[test]
9644    fn elixir_fn_inside_def_lloc() {
9645        // `fn x -> x + 1 end` inside a `def`: defmodule + def +
9646        // `f = fn ...` + `f.(2)` = 4 own LLOC for the Unit space, plus
9647        // the anonymous fn body `x + 1` = 1 nested, aggregated 5.
9648        check_metrics::<ElixirParser>(
9649            "defmodule Foo do\n  def outer do\n    f = fn x -> x + 1 end\n    f.(2)\n  end\nend\n",
9650            "foo.ex",
9651            |metric| {
9652                assert_eq!(metric.loc.sloc(), 6.0);
9653                assert_eq!(metric.loc.ploc(), 6.0);
9654                assert_eq!(metric.loc.lloc(), 5.0);
9655                assert_eq!(metric.loc.cloc(), 0.0);
9656                assert_eq!(metric.loc.blank(), 0.0);
9657                insta::assert_json_snapshot!(metric.loc);
9658            },
9659        );
9660    }
9661}