1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
//! Cross-language `Loc` helper functions shared by the per-language
//! submodules.
//!
//! The public metric `Stats` (and `Sloc` / `Ploc` / `Cloc` / `Lloc`),
//! the `Loc` trait, and `min_or_zero` stay in the parent module; only
//! the cross-language line-counting helpers live here so the parent
//! clears the 800-SLOC self-scan limit (#976).
//!
//! # The `Loc` catch-all arm is fail-*open*, unlike every sibling metric
//!
//! `Cognitive`, `Cyclomatic`, and `Abc` end their per-language matches
//! with `_ => {}`, so a token the author never considered costs
//! nothing. Every `Loc` implementation instead ends with
//!
//! ```ignore
//! _ => {
//! check_comment_ends_on_code_line(stats, start);
//! stats.ploc.lines.insert(start);
//! }
//! ```
//!
//! which asserts *this row is code*. The default is inverted from the
//! one the neighbouring metric modules train you to expect: an
//! unrecognised token does not fall through harmlessly, it adds a
//! physical line of code — and via [`check_comment_ends_on_code_line`]
//! can reclassify a comment-only row as code-and-comment.
//!
//! Two shipped defects came from exactly this. Tcl and iRules are the
//! only grammars that surface the row terminator as a token child of
//! the root; its start row is the row it *terminates*, so every
//! comment-only and whitespace-only row was credited to PLOC — a
//! realistic fourteen-row Tcl file reported `ploc 13` against a true
//! `7` (#1135). Perl's `#` sits *inside* its `comments` node and did
//! the same to every Perl file carrying a comment row (#1137).
//!
//! When adding a language, enumerate the tokens its grammar emits that
//! are not code — row terminators, comment punctuation, string-internal
//! tokens — and give them an explicit no-op arm. `bca dump` over a
//! small fixture is the fastest way to see them. The parent module's
//! `a_comment_row_is_never_counted_as_code` and
//! `whitespace_only_input_is_uniform_across_grammars` sweep every
//! language for both shapes.
use *;
use crate;
pub
// Discriminates among the comments that are *after* a code line and
// the ones that are on an independent line.
// This difference is necessary in order to avoid having
// a wrong count for the blank metric.
//
// Requires `end >= start`. Every caller passes the row span returned by
// `init`, and tree-sitter guarantees a node's end row is never before its
// start row — so the only way to violate this is for a caller to adjust
// `end` downwards itself, as Rust's `LineComment` arm does for doc
// comments (#1051).
//
// Scope of the assert, deliberately narrow: it fires only for
// `end < start`, and only in debug — the release profile disables both
// `debug-assertions` and `overflow-checks`. It cannot catch a caller
// whose own arithmetic already wrapped, because `(0, usize::MAX)`
// satisfies `end >= start`. The real guard against that is refusing to
// underflow in the first place, at the site that adjusts the span.
//
// The `saturating_sub` below narrows what release does with an
// inverted span, and the effect is smaller than it looks: `comment_diff`
// feeds only the branch tests, never a row index. `0` routes to the
// `== 0` arm rather than the block-comment arm a wrapped `usize::MAX`
// selected, but the two differ only by an `add_only_comment_lines(start
// + 1, end)` that `LineSet::insert_range` already rejects as inverted —
// so this buys a truthful classification, not an observable metric
// change (#1152).
// Records a span of comment rows exactly as `add_cloc_lines` does and,
// when they are documentation, the same rows in the `dloc` set that
// `loc.dloc` reports, so `dloc() <= cloc()` holds by construction
// (#1597). The callers are `add_comment_lines` below, Rust's
// line-comment arm and Python's bare-string arms. The `dloc` set's two
// other writers, `add_leading_doc_run` and Python's documentation-string
// and Sphinx-comment runs, only re-bill rows already billed here.
pub
// Documentation by position rather than by marker (Go, Ruby, Lua): bills
// the comment run `node` ends as `dloc` when it sits directly above a
// declaration the language documents, or its first member is a language's
// run opener (LDoc `---`, RDoc `##`, YARD `@!`). Called
// from the comment arm after `add_comment_lines`, so every row it credits
// is one a run member's own arm already billed to this space's cloc sets,
// and `dloc() <= cloc()` still holds (#1597).
pub
// Bills a comment node's rows, as documentation when `is_documentation`
// says so. Every language's comment arm routes here — Rust's comment
// arm bills through `add_comment_rows` itself, since its
// `Checker::is_doc_comment` answer also sizes its row span — so "which
// comments are documentation" is answered in one place per language, in
// the `Checker` impl, and never drifts from `is_comment` (#1597).
//
// The rows are the comment node's own, exactly as `init` hands them to
// every comment arm; reading them here keeps each call site to the node
// and its context.
pub
// Whether a comment is documentation by where it sits: spelled as a doc
// comment (`Checker::is_doc_comment`), not one a `Checker::DOC_BINDING`
// attaches forward (`documents_unbound`; `add_bound_doc_run` bills
// those), and, per `documents_where_it_sits`, not a working note in a
// function body (#1597, #1613).
// The placement half of `is_documentation`, for a caller that already
// holds the `Checker::is_doc_comment` answer (Rust's comment arm).
pub
// Bills as `dloc` the doc comments a bound language's tool attaches to
// the declaration after them, once `node` ends their stretch
// (`bound_doc_run`, #1613). Each member's rows are the ones its own
// comment arm billed to `cloc`, so `dloc() <= cloc()` still holds; every
// member is a sibling of `node`, so they were billed to this space's
// `stats`. Rust's arm, whose `///` rows are not its node's, calls
// `bound_doc_run` itself.
// Detects the comments that are on a code line but *before* the code part.
// This difference is necessary in order to avoid having
// a wrong count for the blank metric.
pub
// Records a physical line carrying both code and comment. Backed by a
// per-line set so several inline block comments on one code line
// (`f(int /*a*/, int /*b*/)`) yield a single comment line, not one per
// comment node (issue #461). Mirrors `Ploc`'s per-line de-duplication.
pub
// Records the inclusive row range `start..=end` as comment-only lines.
// Backed by a per-line set so two standalone block comments on one
// physical line (`/*a*/ /*b*/`) count once, while each distinct row of
// a genuine multi-line block comment still counts (issue #461).
pub
// Adds every physical row spanned by a multi-line string literal to PLOC.
//
// Interior rows of a multi-line string hold real source text, not blank
// lines: classifying them as blank (which happens whenever a language
// no-ops its string nodes, so the rows reach neither PLOC nor CLOC and
// `blank = sloc - ploc - cloc` mislabels them) diverges from Python's
// established behaviour, where a non-docstring multi-line string credits
// all of its rows to PLOC (#415). This helper makes every other language
// agree with that decision (#778).
//
// Mirrors Python's `String` arm: the opening row is inserted only when the
// enclosing statement begins on an earlier row — otherwise that row is
// already attributed to the parent — and the interior and closing rows are
// always inserted.
//
// **That parent gate assumes some other node credits the opening row.**
// It is a skip, and it is only safe where the language's catch-all
// credits every node's start row, as it does in all eighteen languages
// #778 covered. Where the catch-all is leaf-gated
// (`if node.child_count() == 0`, as in `bash.rs` and `elixir.rs`) a
// container parent contributes nothing, so a *childless* literal is the
// only node covering its own row and the gate deletes it: `'ls'` alone
// in a Bash file reported `ploc 0, blank 1` (#1260). Elixir is
// unaffected because a `quoted_content`'s parent always carries its
// opening delimiter as a leaf token on that row. Bash's arm owns its
// opening row outright and calls
// [`add_string_interior_ploc`] instead — do not reach for this one
// there, where the gate can only be wrong.
pub
// Adds a multi-row literal's interior and closing rows to PLOC, leaving
// its opening row to the caller. The half of
// [`add_multiline_string_ploc`] a language whose catch-all is leaf-gated
// wants on its own: those callers must credit the opening row
// unconditionally, so the parent gate above is dead weight for them and
// re-running its two statements after the fact reads as a second rule.
//
// The last row is derived from [`Node::end_line`] rather than the raw
// end row: a node whose end column is 0 finished at the *start* of the
// row below its last content row, so that row is not part of its span,
// and crediting it claims a row the literal does not cover.
//
// `insert_range` rather than a row-at-a-time loop: the range is one
// bitmap span, and Bash's heredoc bodies push thousands of rows through
// here where the loop paid a reserve and a bounds check per row.
//
// The name records where the rule came from, not the only node it
// applies to: #1423 routed the four C-family `PreprocArg` arms here
// too, since an unexpanded macro body is the same question — which rows
// does this multi-row node cover — and had the same off-by-one bound
// open-coded.
pub