@codometer/languages 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1697 @@
1
+ import { CodometerCommentLanguage } from '@codometer/configuration';
2
+ import { CodometerCommentMeasurement } from '@codometer/configuration';
3
+ import { CodometerSeverity } from '@codometer/core';
4
+ import { CodometerSymbolKind } from '@codometer/core';
5
+ import { CodometerSymbolModifier } from '@codometer/core';
6
+ import { ConsoleLogger } from '@nestjs/common';
7
+ import { default as default_2 } from 'typescript';
8
+ import pino from 'pino';
9
+ import { ResolvedCodometerConfiguration } from '@codometer/configuration';
10
+ import { SourceFile } from 'typescript';
11
+
12
+ /** Arguments accepted by the Jupyter analyzer. */
13
+ export declare interface AnalyzeJupyterArguments {
14
+ notebookFiles: string[];
15
+ pythonCommand: string;
16
+ workingDirectory: string;
17
+ }
18
+
19
+ /** Arguments accepted when running every language analyzer. */
20
+ export declare interface AnalyzeLanguagesArguments {
21
+ /**
22
+ * One `comment`-selector custom statistic's budget, per declared statistic.
23
+ *
24
+ * Every counter arrives in one list, and every measurer is handed all of
25
+ * them: a counter naming a `kind` is measured by the TypeScript walk, and
26
+ * every other one by the comment readers, each selecting for itself.
27
+ */
28
+ commentCounters: CommentCounter[];
29
+ configuration: ResolvedCodometerConfiguration;
30
+ discoveredFiles: DiscoveredLanguageFiles;
31
+ /** Configured counters over declarations, tallied during the TypeScript walk. */
32
+ symbolCounters: TypescriptSymbolCounter[];
33
+ workingDirectory: string;
34
+ }
35
+
36
+ /** Arguments accepted by the Python analyzer. */
37
+ export declare interface AnalyzePythonArguments {
38
+ command: string;
39
+ pythonFiles: string[];
40
+ workingDirectory: string;
41
+ }
42
+
43
+ /** Arguments accepted when analyzing Python source text without files. */
44
+ export declare interface AnalyzePythonContentsArguments {
45
+ command: string;
46
+ contents: string[];
47
+ workingDirectory: string;
48
+ }
49
+
50
+ /** Arguments for analyzing a single source file. */
51
+ export declare interface AnalyzeTypescriptFileArguments {
52
+ commentCounters: CommentCounter[];
53
+ counters: TypescriptSymbolCounter[];
54
+ filePath: string;
55
+ stats: TypescriptResult;
56
+ workingDirectory: string;
57
+ }
58
+
59
+ /** A run of comment lines a reader takes as one thought. */
60
+ export declare interface CommentBlock {
61
+ tokens: CommentToken[];
62
+ }
63
+
64
+ /**
65
+ * How long one comment or JSDoc block may run, carried as an explicit
66
+ * argument rather than read off a resolved configuration object.
67
+ *
68
+ * Shaped to match the `comment` selector `@codometer/configuration` declares
69
+ * — a `language`, a `kind`, and this same set of optional maxima plus
70
+ * `severity` — so mapping one onto this is a direct field copy rather than a
71
+ * translation.
72
+ */
73
+ export declare interface CommentBudget {
74
+ maximumCharacters: number | undefined;
75
+ maximumLines: number | undefined;
76
+ maximumWords: number | undefined;
77
+ severity: CodometerSeverity;
78
+ }
79
+
80
+ /**
81
+ * One `comment`-selector custom statistic's budget, and what it is over.
82
+ *
83
+ * Mirrors the selector field for field: `kind` names a documentable
84
+ * declaration whose JSDoc is measured, `language` names the language whose
85
+ * plain comment blocks are, and `undefined` on both means every language that
86
+ * has comments — exactly as `CodometerCommentSelector` documents. A counter
87
+ * naming both is measured as a `kind`: the whole list reaches both measurers,
88
+ * and each selects from it by reading `kind`.
89
+ *
90
+ * Kept apart per statistic rather than merged into one budget per language or
91
+ * kind, so two statistics naming the same one with different maxima each
92
+ * count only their own breaches back against their own label.
93
+ */
94
+ export declare interface CommentCounter {
95
+ budget: CommentBudget;
96
+ kind: CodometerSymbolKind | undefined;
97
+ label: string;
98
+ language: CodometerCommentLanguage | undefined;
99
+ }
100
+
101
+ /** One comment block, measured against one declared maximum. */
102
+ export declare type CommentMeasurement = CodometerCommentMeasurement;
103
+
104
+ /**
105
+ * NestJS module that provides comment-length measurement.
106
+ *
107
+ * One measuring service and one reader per comment syntax, so a language that
108
+ * marks its comments differently is a new reader here rather than a second
109
+ * definition of what a word is.
110
+ */
111
+ export declare class CommentsModule {
112
+ }
113
+
114
+ /**
115
+ * Measures comments against the maxima a configuration declares.
116
+ *
117
+ * Everything here is language-agnostic: it takes comments somebody else
118
+ * already found and says how long they are. What counts as a comment differs
119
+ * per language and lives with the reader that knows — `HashCommentsService`
120
+ * for the `#` languages, `YamlCommentsService` for YAML's tokenizer, and the
121
+ * TypeScript walk for a JSDoc block.
122
+ *
123
+ * That split is what keeps one definition of a word, a line, and a character
124
+ * across every language, rather than four analyzers each counting slightly
125
+ * differently.
126
+ */
127
+ export declare class CommentsService {
128
+ constructor();
129
+ /**
130
+ * Counts the words in a comment's prose, markers already stripped.
131
+ *
132
+ * Splitting a trimmed string on whitespace runs never yields an empty
133
+ * token, so the empty case is the only one worth guarding — and guarding it
134
+ * rather than filtering keeps a callback frame off the deepest stack this
135
+ * package owns.
136
+ */
137
+ private countWords;
138
+ /**
139
+ * Every declared maximum, paired with what this comment measured.
140
+ *
141
+ * Written as three guarded pushes rather than a table walked by `flatMap`,
142
+ * because the callback would be one more frame on the deepest stack this
143
+ * package owns — the JSDoc walk reaches here through eleven of them, and
144
+ * `callidescope.config.ts` gates that at what it measures.
145
+ */
146
+ private declaredLimits;
147
+ /** The prose of a run of comment lines, markers already stripped. */
148
+ private readProse;
149
+ /** A run of comment lines exactly as the file carries them. */
150
+ private readSource;
151
+ /** Shortens a block's prose to something a breach line can carry. */
152
+ private toExcerpt;
153
+ /**
154
+ * Groups a file's comment lines into the blocks a reader perceives.
155
+ *
156
+ * A trailing comment never joins anything — it sits after a value and is
157
+ * read with that value, not with the prose above it — and neither does a
158
+ * comment separated from the previous one by a blank line, which is how a
159
+ * writer marks the end of a thought.
160
+ */
161
+ groupIntoBlocks(tokens: readonly CommentToken[]): CommentBlock[];
162
+ /**
163
+ * Measures every block a file's comment tokens form, breached or not.
164
+ *
165
+ * Every block is reported rather than only the breaches, so a length is
166
+ * visible in the JSON report before it ever becomes a problem — the same
167
+ * bargain the TypeScript declaration-comment measurement makes. A block is
168
+ * reported once per declared maximum, because the maxima are not
169
+ * alternatives: one can hold while another breaks.
170
+ */
171
+ measure(args: MeasureCommentsArguments): CommentMeasurement[];
172
+ /**
173
+ * Measures one comment's text against every maximum declared for it.
174
+ *
175
+ * The seam a JSDoc block enters through: it is one comment already, found by
176
+ * the TypeScript walk rather than grouped from lines, so it needs the
177
+ * counting and none of the grouping.
178
+ */
179
+ measureText(args: MeasureCommentTextArguments): CommentMeasurement[];
180
+ }
181
+
182
+ /** One comment line, with where it sits and what it says. */
183
+ export declare interface CommentToken {
184
+ /** 1-indexed line the comment is written on. */
185
+ line: number;
186
+ /** Whether nothing but whitespace precedes it on its line. */
187
+ ownLine: boolean;
188
+ /** The comment's prose, with its marker already stripped. */
189
+ prose: string;
190
+ /** The comment exactly as the file carries it, marker and all. */
191
+ source: string;
192
+ }
193
+
194
+ /**
195
+ * Reads CSS's `/* ... *\/` comments from postcss's own parse.
196
+ *
197
+ * CSS has only the one comment syntax, and no line form at all, so a real
198
+ * parser costs nothing extra here: postcss already reads the repository's
199
+ * stylesheets for `CssService`, and its comment nodes carry a line and column
200
+ * a scanner would otherwise have to recompute by hand.
201
+ */
202
+ export declare class CssCommentsService {
203
+ constructor();
204
+ /**
205
+ * A comment's text with the whitespace postcss split off restored.
206
+ *
207
+ * `raws.left`/`raws.right` are typed optional, but postcss's own parse
208
+ * always fills both in — with an empty string for an empty comment, never
209
+ * `undefined` — so the fallback is unreachable rather than untested.
210
+ */
211
+ private toBody;
212
+ /**
213
+ * Whether only whitespace precedes a comment's opening marker on its line.
214
+ *
215
+ * `comment.source` is typed optional because postcss also allows building a
216
+ * node by hand with none, which never happens here: every comment measured
217
+ * came from parsing real content, which always carries its position.
218
+ */
219
+ private toOwnLine;
220
+ /**
221
+ * Reads every comment postcss's parse finds, in document order.
222
+ *
223
+ * Empty for a stylesheet postcss cannot parse at all, the same way
224
+ * `CssService` skips a file it cannot parse rather than throwing out of the
225
+ * whole measurement run.
226
+ */
227
+ read(content: string): CommentToken[];
228
+ }
229
+
230
+ /** Input to the Css analysis step. */
231
+ export declare interface CssInput {
232
+ cssFiles: string[];
233
+ workingDirectory: string;
234
+ }
235
+
236
+ /**
237
+ * NestJS module that provides Css source analysis.
238
+ */
239
+ export declare class CssModule {
240
+ }
241
+
242
+ /** Aggregated metrics collected from parsing Css sources. */
243
+ export declare interface CssResult {
244
+ atRules: number;
245
+ comments: number;
246
+ customProperties: number;
247
+ declarations: number;
248
+ files: number;
249
+ lines: number;
250
+ mediaQueries: number;
251
+ rules: number;
252
+ selectors: number;
253
+ }
254
+
255
+ /**
256
+ * Walks parsed stylesheets to collect structural metrics.
257
+ *
258
+ * Parsed with postcss, which the repository's stylelint already reads CSS
259
+ * through, so a selector split across lines counts once and a declaration
260
+ * inside a comment counts not at all.
261
+ */
262
+ export declare class CssService {
263
+ private readonly logger;
264
+ constructor(logger: LoggerService);
265
+ /** Records one node against the running totals. */
266
+ private countNode;
267
+ /** Analyze the given stylesheets, resolved against the directory. */
268
+ analyze({ cssFiles, workingDirectory }: CssInput): CssResult;
269
+ }
270
+
271
+ /**
272
+ * Finds a documentable declaration's leading JSDoc comment and hands it to be
273
+ * measured.
274
+ *
275
+ * Only the finding is TypeScript's: which declarations can carry a limit,
276
+ * where the `/**` range sits, and what the declaration is called. How long the
277
+ * comment is comes from `CommentsService`, the same counting every other
278
+ * language's comments go through, so a word means one thing across the tool
279
+ * rather than one thing per analyzer.
280
+ */
281
+ declare class DeclarationCommentsService {
282
+ private readonly comments;
283
+ constructor(comments: CommentsService);
284
+ /** Reads a declaration's own name, or `"(anonymous)"` when it has none. */
285
+ private getDeclarationName;
286
+ /** Finds the node's leading JSDoc comment range, the last one if several. */
287
+ private getJsDocRange;
288
+ /**
289
+ * Everything a measurement needs about one node, or `undefined` when there
290
+ * is nothing to measure.
291
+ *
292
+ * Split out of `measure` so that method stays inside this repository's
293
+ * statement budget without a helper on the measuring path itself — this one
294
+ * is called before the comment counting starts, so it adds no frame to the
295
+ * deepest stack the JSDoc walk reaches.
296
+ */
297
+ private prepare;
298
+ /**
299
+ * The comment's prose, with its delimiters and each line's `*` stripped.
300
+ *
301
+ * Stripped for the word count only. A character count stays the raw slice —
302
+ * it is the one unit a reader can check against their editor's own column
303
+ * count, and a marker is very much a character even though it is not a word.
304
+ */
305
+ private readProse;
306
+ /**
307
+ * Measures one declaration's leading JSDoc comment against every
308
+ * comment counter that names its kind, if it has one.
309
+ *
310
+ * Empty when the node's kind matches no configured counter, or when it
311
+ * carries no `/**` comment at all — neither is a measurement, and reporting
312
+ * one would name a declaration nothing documented. A declaration is
313
+ * measured once per counter that names its kind and once per maximum that
314
+ * counter declares, because neither is an alternative: one can hold while
315
+ * another breaks.
316
+ */
317
+ measure(node: default_2.Node, context: TypescriptWalkContext): LabeledCommentMeasurement[];
318
+ }
319
+
320
+ /** The categorized file lists every language analyzer reads from. */
321
+ export declare interface DiscoveredLanguageFiles {
322
+ cssFiles: string[];
323
+ hclFiles: string[];
324
+ jsonFiles: string[];
325
+ markdownFiles: string[];
326
+ notebookFiles: string[];
327
+ pyFiles: string[];
328
+ shellFiles: string[];
329
+ sourceFiles: string[];
330
+ sqlFiles: string[];
331
+ tomlFiles: string[];
332
+ yamlFiles: string[];
333
+ }
334
+
335
+ /**
336
+ * Reads the comments of a language whose comments start with `#`.
337
+ *
338
+ * Shell, TOML, and Python all mark a comment the same way and all three
339
+ * already scan their sources line by line, which is why one reader serves
340
+ * them.
341
+ *
342
+ * It is a line scanner, not a tokenizer, and that is a real limitation rather
343
+ * than an oversight: a `#` inside a string literal is read as a comment here,
344
+ * exactly as those three analyzers' own `comments` counters already read it.
345
+ * YAML is measured by `YamlCommentsService` instead, whose tokenizer knows the
346
+ * difference, because YAML's `#` sits next to quoted scalars constantly.
347
+ */
348
+ export declare class HashCommentsService {
349
+ constructor();
350
+ /**
351
+ * Whether this is the interpreter line rather than a comment.
352
+ *
353
+ * `#!` on the first line is an instruction to the kernel, not prose. Left in,
354
+ * it would be grouped with whatever comment follows it — every shell script
355
+ * opening with a shebang and a comment would measure one block carrying
356
+ * `!/bin/sh` as its first word.
357
+ */
358
+ private isShebang;
359
+ /** Reads every `#` comment in a file, with its line and its placement. */
360
+ read(content: string): CommentToken[];
361
+ }
362
+
363
+ /**
364
+ * Reads HCL's `#`, `//`, and `/* ... *\/` comments — the only language this
365
+ * tool measures that marks a comment three different ways.
366
+ *
367
+ * A line scanner for the two line forms, none of it string-aware:
368
+ * `HclService`'s own line-based counting already accepts the same
369
+ * limitation, checking only whether a trimmed line's first characters open a
370
+ * comment rather than parsing the language properly. The block form is found
371
+ * with `indexOf` rather than a regular expression: a pattern matching an
372
+ * unclosed `/*` through to end of input has to fail once per occurrence,
373
+ * which is quadratic on adversarial input, while two `indexOf` calls per
374
+ * comment never scan the same text twice.
375
+ */
376
+ export declare class HclCommentsService {
377
+ constructor();
378
+ /** Every `/* *\/` comment's span, left to right and never overlapping. */
379
+ private findBlockComments;
380
+ /** Whether only whitespace precedes an offset on its own line. */
381
+ private isOwnLine;
382
+ /** The 1-indexed line an offset sits on. */
383
+ private lineOf;
384
+ /** Every block comment's span, as a positioned token. */
385
+ private readBlocks;
386
+ /** Every match of a comment pattern, as a positioned token. */
387
+ private readMatches;
388
+ /**
389
+ * Reads every `#`, `//`, and `/* *\/` comment, in the order they appear.
390
+ *
391
+ * Block spans are found first so a line marker found inside one — a `#`
392
+ * written as prose in a `/* *\/` block, say — can be dropped rather than
393
+ * measured a second time as a comment of its own.
394
+ */
395
+ read(content: string): CommentToken[];
396
+ }
397
+
398
+ /** Input to the Hcl analysis step. */
399
+ export declare interface HclInput {
400
+ hclFiles: string[];
401
+ workingDirectory: string;
402
+ }
403
+
404
+ /**
405
+ * NestJS module that provides Hcl source analysis.
406
+ */
407
+ export declare class HclModule {
408
+ }
409
+
410
+ /** Aggregated metrics collected from parsing Hcl sources. */
411
+ export declare interface HclResult {
412
+ attributes: number;
413
+ blocks: number;
414
+ comments: number;
415
+ files: number;
416
+ interpolations: number;
417
+ lines: number;
418
+ outputs: number;
419
+ resources: number;
420
+ variables: number;
421
+ }
422
+
423
+ /**
424
+ * Counts the blocks and attributes an HCL configuration declares.
425
+ *
426
+ * Blocks are recognized by the header that opens them, which also names what
427
+ * kind they are: a `resource` block and an `output` block are both blocks, and
428
+ * knowing how many of each is what makes the count worth reading.
429
+ */
430
+ export declare class HclService {
431
+ private readonly logger;
432
+ constructor(logger: LoggerService);
433
+ /** Records the block a header opens, by the kind it names. */
434
+ private countBlock;
435
+ /** Records what one line of HCL declares. */
436
+ private countLine;
437
+ /** Analyze the given HCL files, resolved against the directory. */
438
+ analyze({ hclFiles, workingDirectory }: HclInput): HclResult;
439
+ }
440
+
441
+ /** State used while stripping JSONC comments from a document. */
442
+ export declare interface JsoncState {
443
+ isInBlockComment: boolean;
444
+ isInLineComment: boolean;
445
+ isInString: boolean;
446
+ sanitizedContent: string;
447
+ shouldAdvanceIndex: boolean;
448
+ }
449
+
450
+ /** Input to the JSON analysis step. */
451
+ export declare interface JsonInput {
452
+ jsonFiles: string[];
453
+ workingDirectory: string;
454
+ }
455
+
456
+ /**
457
+ * TODO: Document the measureJson module.
458
+ */
459
+ export declare class JsonModule {
460
+ }
461
+
462
+ /** Aggregated metrics collected from parsing JSON documents. */
463
+ export declare interface JsonResult {
464
+ arrays: number;
465
+ booleans: number;
466
+ files: number;
467
+ items: number;
468
+ lines: number;
469
+ maxDepth: number;
470
+ nulls: number;
471
+ numbers: number;
472
+ objects: number;
473
+ properties: number;
474
+ strings: number;
475
+ totalNodes: number;
476
+ }
477
+
478
+ /** Walks parsed JSON values to collect structural metrics. */
479
+ export declare class JsonService {
480
+ private readonly logger;
481
+ constructor(logger: LoggerService);
482
+ /** Consume a character that is not inside a comment or string. */
483
+ private consumeCharacterOutsideComments;
484
+ /** Consume one JSONC character and update the parser state. */
485
+ private consumeJsoncCharacter;
486
+ /** Count array nodes and their child values. */
487
+ private countArrayNode;
488
+ /** Recursively count JSON containers, primitives, and nesting depth. */
489
+ private countNode;
490
+ /** Count scalar values and update primitive stats. */
491
+ private countPrimitiveNode;
492
+ /** Increment stats for a scalar JSON value. */
493
+ private countPrimitiveValue;
494
+ /** Count object nodes and their child values. */
495
+ private countRecordNode;
496
+ /** Handle block comments while parsing JSONC content. */
497
+ private handleBlockCommentState;
498
+ /** Update the JSONC parser when it is inside a line comment. */
499
+ private handleLineCommentState;
500
+ /** Update the JSONC parser when it is inside a string literal. */
501
+ private handleStringState;
502
+ /** Return true when a value is a JSON array. */
503
+ private isArrayNode;
504
+ /** Return true when a value is a JSON object. */
505
+ private isRecordNode;
506
+ /** Parse a file into one or more JSON documents depending on the extension. */
507
+ private parseDocuments;
508
+ /** Remove comments from JSONC content while preserving string literals. */
509
+ private stripJsoncComments;
510
+ /** Analyze JSON files and return structural metrics for their contents. */
511
+ analyze(input: JsonInput): JsonResult;
512
+ }
513
+
514
+ /**
515
+ * NestJS module that measures notebooks through the JSON, Python, and
516
+ * markdown analyzers it composes.
517
+ */
518
+ export declare class JupyterModule {
519
+ }
520
+
521
+ /** Aggregated metrics collected from Jupyter notebooks. */
522
+ export declare interface JupyterResult {
523
+ cells: number;
524
+ classes: number;
525
+ codeBlocks: number;
526
+ codeCells: number;
527
+ codeLines: number;
528
+ decorators: number;
529
+ executedCells: number;
530
+ files: number;
531
+ functions: number;
532
+ headings: number;
533
+ images: number;
534
+ imports: number;
535
+ links: number;
536
+ markdownCells: number;
537
+ markdownLines: number;
538
+ maxDepth: number;
539
+ outputs: number;
540
+ properties: number;
541
+ rawCells: number;
542
+ totalNodes: number;
543
+ }
544
+
545
+ /**
546
+ * Measures Jupyter notebooks by handing their parts to the other analyzers.
547
+ *
548
+ * A notebook is three languages in one file, and this service owns none of
549
+ * them: the document is JSON, its code cells are Python, and its markdown
550
+ * cells are prose, so each is counted by the analyzer that already knows how.
551
+ * What is left — cells, outputs, execution — belongs to the notebook itself
552
+ * and is counted here.
553
+ */
554
+ export declare class JupyterService {
555
+ private readonly jsonService;
556
+ private readonly markdownService;
557
+ private readonly pythonService;
558
+ private readonly logger;
559
+ constructor(jsonService: JsonService, markdownService: MarkdownService, pythonService: PythonService, logger: LoggerService);
560
+ /** Record one cell against the running notebook totals. */
561
+ private collectCell;
562
+ /** Read every notebook, collecting cell counts and cell sources. */
563
+ private collectParts;
564
+ /** Sum every heading level the markdown analyzer reports. */
565
+ private countHeadings;
566
+ /** Read and validate one notebook, returning its cells. */
567
+ private readNotebook;
568
+ /** Join a cell's source, which nbformat writes as a string or line array. */
569
+ private readSource;
570
+ /** Analyze the given notebooks, resolved against the directory. */
571
+ analyze(args: AnalyzeJupyterArguments): JupyterResult;
572
+ }
573
+
574
+ /** One measurement, tagged with the custom statistic label that produced it. */
575
+ declare interface LabeledCommentMeasurement {
576
+ label: string;
577
+ measurement: CommentMeasurement;
578
+ }
579
+
580
+ /**
581
+ * The file lists comment measurement reads, one per language that has a
582
+ * budget.
583
+ *
584
+ * Named here rather than taken from `DiscoveredLanguageFiles`, which would
585
+ * point this module back at the one that depends on it. `languages` depends on
586
+ * `comments` and never the reverse; a cycle between the two crashes the Nest
587
+ * container outright rather than failing anything readable. The discovery
588
+ * result satisfies this structurally, so callers pass it unchanged.
589
+ */
590
+ declare interface LanguageCommentFiles {
591
+ cssFiles: string[];
592
+ hclFiles: string[];
593
+ shellFiles: string[];
594
+ /** TypeScript and JavaScript sources, in every dialect the workspace holds. */
595
+ sourceFiles: string[];
596
+ sqlFiles: string[];
597
+ tomlFiles: string[];
598
+ yamlFiles: string[];
599
+ }
600
+
601
+ /**
602
+ * Measures the comment blocks every `comment`-selector custom statistic asks
603
+ * for, one counter at a time.
604
+ *
605
+ * Files are read here rather than inside each language analyzer, which is
606
+ * what lets Python be measured at all: its analysis runs in a subprocess and
607
+ * returns zeros when the interpreter is unreachable, so a gate that lived
608
+ * there would stop gating on any machine without `uv` and say nothing about
609
+ * it. Reading the sources directly makes the budget independent of that.
610
+ *
611
+ * A counter is measured on its own rather than merged with every other one
612
+ * that shares a language: two custom statistics can watch the same language
613
+ * with different maxima, and keeping them apart is what lets each one's own
614
+ * breaches be counted back against its own label.
615
+ */
616
+ export declare class LanguageCommentsService {
617
+ private readonly comments;
618
+ private readonly cssComments;
619
+ private readonly hashComments;
620
+ private readonly hclComments;
621
+ private readonly logger;
622
+ private readonly sqlComments;
623
+ private readonly typescriptComments;
624
+ private readonly yamlComments;
625
+ constructor(comments: CommentsService, cssComments: CssCommentsService, hashComments: HashCommentsService, hclComments: HclCommentsService, logger: LoggerService, sqlComments: SqlCommentsService, typescriptComments: TypescriptCommentsService, yamlComments: YamlCommentsService);
626
+ /** Reads and measures one language's files against one counter's budget. */
627
+ private measureLanguage;
628
+ /** Measures one counter's budget against one language's discovered files. */
629
+ private measureOneLanguage;
630
+ /**
631
+ * Measures Python's comments, which its own analyzer already found.
632
+ *
633
+ * The tokens arrive grouped by nothing, so they are split per file before
634
+ * measuring — a block never spans two files, and `groupIntoBlocks` compares
635
+ * line numbers that would otherwise run together.
636
+ */
637
+ private measurePython;
638
+ /** Reads one file, or reports which one it gave up on. */
639
+ private readFile;
640
+ /**
641
+ * Measures every counter that names no declaration kind, keyed by the custom
642
+ * statistic's label.
643
+ *
644
+ * A counter naming no language measures every one of them; one naming a
645
+ * language measures only that one. Either way its results land under its
646
+ * own label, never merged with another counter's.
647
+ */
648
+ measure(args: MeasureLanguageCommentsArguments): Record<string, CommentMeasurement[]>;
649
+ }
650
+
651
+ /** What every language analyzer reported, keyed by language. */
652
+ export declare interface LanguageResults {
653
+ /** One measurement list per configured comment counter, keyed by its label. */
654
+ commentCounts: Record<string, CommentMeasurement[]>;
655
+ css: CssResult;
656
+ hcl: HclResult;
657
+ json: JsonResult;
658
+ jupyter: JupyterResult;
659
+ markdown: MarkdownResult;
660
+ python: PythonResult;
661
+ shell: ShellResult;
662
+ sql: SqlResult;
663
+ toml: TomlResult;
664
+ typescript: TypescriptResult;
665
+ yaml: YamlResult;
666
+ }
667
+
668
+ /**
669
+ * NestJS module that gathers every language analyzer behind one service.
670
+ */
671
+ export declare class LanguagesModule {
672
+ }
673
+
674
+ /**
675
+ * Runs every language analyzer over the discovered files.
676
+ *
677
+ * One collaborator for the measurement pipeline instead of eleven: which
678
+ * languages exist is this service's business, and adding a twelfth changes
679
+ * nothing above it.
680
+ */
681
+ export declare class LanguagesService {
682
+ private readonly cssService;
683
+ private readonly languageComments;
684
+ private readonly hclService;
685
+ private readonly jsonService;
686
+ private readonly jupyterService;
687
+ private readonly markdownService;
688
+ private readonly pythonService;
689
+ private readonly shellService;
690
+ private readonly sqlService;
691
+ private readonly tomlService;
692
+ private readonly typescriptService;
693
+ private readonly yamlService;
694
+ constructor(cssService: CssService, languageComments: LanguageCommentsService, hclService: HclService, jsonService: JsonService, jupyterService: JupyterService, markdownService: MarkdownService, pythonService: PythonService, shellService: ShellService, sqlService: SqlService, tomlService: TomlService, typescriptService: TypescriptService, yamlService: YamlService);
695
+ /** Analyze every language present in the discovered files. */
696
+ analyze(args: AnalyzeLanguagesArguments): LanguageResults;
697
+ }
698
+
699
+ /** A comment token together with the file it was found in. */
700
+ declare interface LocatedCommentToken extends CommentToken {
701
+ file: string;
702
+ }
703
+
704
+ /**
705
+ * Structured values that belong beside a log line rather than inside it.
706
+ *
707
+ * Counts, percentages, and durations are the values that change on every
708
+ * occurrence, so they are carried as fields: the message stays constant and
709
+ * groupable in telemetry, and the numbers stay queryable instead of having to
710
+ * be parsed back out of prose.
711
+ *
712
+ * The named members are the recurring ones; the index signature keeps the
713
+ * argument open for whatever a given call site needs to attach.
714
+ */
715
+ declare interface LogData {
716
+ [key: string]: unknown;
717
+ /** How many things the operation handled. */
718
+ count?: number;
719
+ /** Wall-clock milliseconds the operation took. */
720
+ durationMs?: number;
721
+ /** Completion between 0 and 100. */
722
+ percent?: number;
723
+ /** How many things the operation set out to handle. */
724
+ total?: number;
725
+ }
726
+
727
+ /**
728
+ * Transient-scoped logger so each injecting class gets its own instance.
729
+ * Each consumer calls `setContext(ClassName.name)` to tag every log line
730
+ * with the originating class. Backed by pino for structured JSON output in
731
+ * production and human-readable pretty-print in development.
732
+ *
733
+ * Messages follow one grammar: an emoji naming the subject, a verb in present
734
+ * progressive or past tense, then the object. Values that vary per call —
735
+ * counts, percentages, durations — go in the `data` argument rather than the
736
+ * message, so the message stays constant enough for telemetry to group on.
737
+ *
738
+ * ```ts
739
+ * this.logger.info("📥 Downloading CSEL sources", undefined, { total: 428 });
740
+ * this.logger.info("📥 Downloaded CSEL sources", undefined, { count: 412 });
741
+ * ```
742
+ */
743
+ declare @Injectable({ scope: Scope.TRANSIENT })
744
+ class LoggerService extends ConsoleLogger {
745
+ // 🏗 Dependency Injection
746
+
747
+ constructor() {
748
+ super();
749
+ }
750
+
751
+ // 🔐 Private Fields
752
+
753
+ private static readonly isProduction =
754
+ process.env["NODE_ENV"] === "production";
755
+
756
+ /**
757
+ * Built on first use, not when this file is evaluated.
758
+ *
759
+ * A destination fixed at import time could only ever be chosen by this
760
+ * package, since every consumer's own code runs after its imports.
761
+ */
762
+ private static rootLogger: pino.Logger | undefined;
763
+
764
+ /** Whether lines go to standard error instead of standard output. */
765
+ private static writesToStandardError = false;
766
+
767
+ private child: pino.Logger = LoggerService.root;
768
+
769
+ // 🔑 Public Fields
770
+
771
+ // 🔏 Private Methods
772
+
773
+ /** Build the pino instance for production or local development output. */
774
+ private static createRootLogger(): pino.Logger {
775
+ const level = process.env["LOG_LEVEL"] ?? "info";
776
+
777
+ if (LoggerService.isProduction) {
778
+ return LoggerService.writesToStandardError
779
+ ? pino({ level }, pino.destination(STANDARD_ERROR_DESCRIPTOR))
780
+ : pino({ level });
781
+ }
782
+
783
+ return pino({
784
+ level,
785
+ transport: {
786
+ options: {
787
+ colorize: true,
788
+ destination: LoggerService.writesToStandardError
789
+ ? STANDARD_ERROR_DESCRIPTOR
790
+ : STANDARD_OUTPUT_DESCRIPTOR,
791
+ // The emoji is a field, not part of the message, so the console can
792
+ // show it while telemetry stores unadorned prose. `ignore` then keeps
793
+ // it from being printed a second time in the trailing object.
794
+ ignore: "pid,hostname,emoji",
795
+ messageFormat: "{emoji} {msg}",
796
+ singleLine: true,
797
+ },
798
+ target: "pino-pretty",
799
+ },
800
+ });
801
+ }
802
+
803
+ /**
804
+ * Sends every subsequent line to standard error instead of standard output.
805
+ *
806
+ * For a command-line application whose standard output *is* its result. A log
807
+ * line sharing that stream is not a diagnostic beside the data, it is a
808
+ * corruption of it. Call it before anything logs — the first statement of the
809
+ * application's bootstrap.
810
+ *
811
+ * A call after the first line warns and changes nothing: the destination is
812
+ * fixed when the pino instance is built, and tearing down a transport
813
+ * somebody is writing through would be worse than refusing. The warning is
814
+ * the point — silently leaving the lines on standard output is how a caller
815
+ * would ship a corrupted pipe without ever being told.
816
+ */
817
+ static logToStandardError(): void {
818
+ if (LoggerService.rootLogger !== undefined) {
819
+ process.emitWarning(
820
+ "LoggerService.logToStandardError() was called after the first log line, so log lines still go to standard output and anything piping that stream will read them as data. Call it as the first statement of the application's bootstrap.",
821
+ );
822
+ return;
823
+ }
824
+
825
+ LoggerService.writesToStandardError = true;
826
+ }
827
+
828
+ /**
829
+ * Fails a malformed message in development, and never in production.
830
+ *
831
+ * A logger that throws in production turns an observability call into an
832
+ * outage, so the check runs only where a developer is present to fix it.
833
+ */
834
+ private assertConventionalMessage(args: {
835
+ context: string | undefined;
836
+ parsed: ParsedLogMessage;
837
+ }): void {
838
+ if (
839
+ LoggerService.isProduction ||
840
+ this.shouldSkipConventionalMessageValidation(args.context)
841
+ ) {
842
+ return;
843
+ }
844
+
845
+ const violation = this.getConventionalMessageViolation(args.parsed);
846
+
847
+ if (violation !== undefined) {
848
+ throw new Error(violation);
849
+ }
850
+ }
851
+
852
+ /** Assembles the object pino merges into the line. */
853
+ private buildBindings(args: {
854
+ context: string | undefined;
855
+ data: LogData | undefined;
856
+ parsed: ParsedLogMessage;
857
+ }): Record<string, unknown> {
858
+ this.assertConventionalMessage({
859
+ context: args.context,
860
+ parsed: args.parsed,
861
+ });
862
+
863
+ return {
864
+ ...args.data,
865
+ context: args.context,
866
+ // Telemetry gets prose; only the console-bound transport reads this.
867
+ ...(LoggerService.isProduction ? {} : { emoji: args.parsed.emoji }),
868
+ };
869
+ }
870
+
871
+ /** Returns a human-readable explanation when the message format is invalid. */
872
+ private getConventionalMessageViolation(
873
+ parsed: ParsedLogMessage,
874
+ ): string | undefined {
875
+ const emoji = parsed.emoji;
876
+ const text = parsed.text;
877
+
878
+ if (emoji === undefined) {
879
+ return `Log message must start with an emoji naming its subject, then a verb: "${text}"`;
880
+ }
881
+
882
+ const firstWord = FIRST_WORD_PATTERN.exec(text)?.[1];
883
+
884
+ if (firstWord === undefined || !this.isConventionalVerb(firstWord)) {
885
+ return `Log message must begin with a verb in present progressive or past tense, got "${firstWord ?? ""}": "${emoji} ${text}"`;
886
+ }
887
+
888
+ return undefined;
889
+ }
890
+
891
+ /**
892
+ * Whether a word is a verb in one of the two tenses the convention allows.
893
+ *
894
+ * Present progressive means the operation is under way; past means it
895
+ * finished. Regular morphology covers both, so a new verb needs no
896
+ * registration anywhere — only irregular pasts are enumerated.
897
+ */
898
+ private isConventionalVerb(word: string): boolean {
899
+ const lowercased = word.toLowerCase();
900
+
901
+ return (
902
+ lowercased.endsWith("ing") ||
903
+ lowercased.endsWith("ed") ||
904
+ IRREGULAR_PAST_VERBS.has(lowercased)
905
+ );
906
+ }
907
+
908
+ /** Splits a leading emoji off a message, leaving prose behind. */
909
+ private parseMessage(message: unknown): ParsedLogMessage {
910
+ const text = String(message);
911
+ const match = LEADING_EMOJI_PATTERN.exec(text);
912
+ const emoji = match?.[1];
913
+
914
+ return emoji === undefined
915
+ ? { emoji: undefined, text }
916
+ : { emoji, text: text.slice(match?.[0].length) };
917
+ }
918
+
919
+ /** Whether a context is intentionally exempt from the validation rule. */
920
+ private shouldSkipConventionalMessageValidation(
921
+ context: string | undefined,
922
+ ): boolean {
923
+ return context !== undefined && UNVALIDATED_LOG_CONTEXTS.has(context);
924
+ }
925
+
926
+ // 🌎 Public Methods
927
+
928
+ /** The pino instance every logger's child is taken from. */
929
+ private static get root(): pino.Logger {
930
+ LoggerService.rootLogger ??= LoggerService.createRootLogger();
931
+
932
+ return LoggerService.rootLogger;
933
+ }
934
+
935
+ /** Normalizes unknown errors into a stable message and timestamped log line. */
936
+ buildErrorLogEntry(
937
+ context: string,
938
+ error: unknown,
939
+ ): { errorMessage: string; logLine: string } {
940
+ const errorMessage =
941
+ error instanceof Error ? error.stack || error.message : String(error);
942
+
943
+ return {
944
+ errorMessage,
945
+ logLine: `[${new Date().toISOString()}] ${context}: ${errorMessage}\n`,
946
+ };
947
+ }
948
+
949
+ /** Builds a timestamped output log file path and ensures the output directory exists. */
950
+ createTimestampedOutputLogFilePath(filePrefix: string): string {
951
+ const outputDirectory = path.join(process.cwd(), "output");
952
+ if (!existsSync(outputDirectory)) {
953
+ mkdirSync(outputDirectory, { recursive: true });
954
+ }
955
+
956
+ return path.join(
957
+ outputDirectory,
958
+ `${filePrefix}-${new Date().toISOString().replaceAll(/[:.]/g, "-")}.log`,
959
+ );
960
+ }
961
+
962
+ /** Logs a debug message at the `debug` level. */
963
+ override debug(message: unknown, context?: string, data?: LogData): void {
964
+ const parsed = this.parseMessage(message);
965
+ this.child.debug(
966
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
967
+ parsed.text,
968
+ );
969
+ }
970
+
971
+ /**
972
+ * Logs an error message at the `error` level, optionally including a stack trace.
973
+ *
974
+ * `ConsoleLogger.error` spends a third slot on a context string that the
975
+ * other levels do not have, so this one accepts either: a string keeps
976
+ * NestJS's meaning, an object is structured data like everywhere else.
977
+ */
978
+ override error(
979
+ message: unknown,
980
+ stackOrContext?: string,
981
+ contextOrData?: LogData | string,
982
+ ): void {
983
+ const parsed = this.parseMessage(message);
984
+ const data = typeof contextOrData === "object" ? contextOrData : undefined;
985
+ const context =
986
+ typeof contextOrData === "string" ? contextOrData : this.context;
987
+
988
+ this.child.error(
989
+ {
990
+ ...this.buildBindings({ context, data, parsed }),
991
+ stack: stackOrContext,
992
+ },
993
+ parsed.text,
994
+ );
995
+ }
996
+
997
+ /** Logs an informational message at the `info` level. */
998
+ info(message: unknown, context?: string, data?: LogData): void {
999
+ const parsed = this.parseMessage(message);
1000
+ this.child.info(
1001
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
1002
+ parsed.text,
1003
+ );
1004
+ }
1005
+
1006
+ /**
1007
+ * Logs an informational message at the `info` level.
1008
+ *
1009
+ * NestJS and `nest-commander` call this method directly as part of the
1010
+ * framework's own `LoggerService` contract, so it must keep working
1011
+ * exactly as before. Application code should call `info` instead — the
1012
+ * same behavior under a name that says what level it logs at.
1013
+ */
1014
+ override log(message: unknown, context?: string, data?: LogData): void {
1015
+ this.info(message, context, data);
1016
+ }
1017
+
1018
+ /** Sets the context label included in every subsequent log line. */
1019
+ override setContext(context: string): void {
1020
+ super.setContext(context);
1021
+ this.child = LoggerService.root.child({ context });
1022
+ }
1023
+
1024
+ /** Logs a verbose message at the `trace` level. */
1025
+ override verbose(message: unknown, context?: string, data?: LogData): void {
1026
+ const parsed = this.parseMessage(message);
1027
+ this.child.trace(
1028
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
1029
+ parsed.text,
1030
+ );
1031
+ }
1032
+
1033
+ /** Logs a warning message at the `warn` level. */
1034
+ override warn(message: unknown, context?: string, data?: LogData): void {
1035
+ const parsed = this.parseMessage(message);
1036
+ this.child.warn(
1037
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
1038
+ parsed.text,
1039
+ );
1040
+ }
1041
+ }
1042
+
1043
+ /** Input to the markdown analysis step. */
1044
+ export declare interface MarkdownInput {
1045
+ markdownFiles: string[];
1046
+ workingDirectory: string;
1047
+ }
1048
+
1049
+ /**
1050
+ * Provides structural measurement of markdown documents.
1051
+ */
1052
+ export declare class MarkdownModule {
1053
+ }
1054
+
1055
+ /** Aggregated metrics collected from parsing markdown documents. */
1056
+ export declare interface MarkdownResult {
1057
+ blockQuotes: number;
1058
+ codeBlocks: number;
1059
+ files: number;
1060
+ headingLevel1: number;
1061
+ headingLevel2: number;
1062
+ headingLevel3: number;
1063
+ headingLevel4: number;
1064
+ headingLevel5: number;
1065
+ headingLevel6: number;
1066
+ images: number;
1067
+ inlineCode: number;
1068
+ lines: number;
1069
+ links: number;
1070
+ listItems: number;
1071
+ lists: number;
1072
+ paragraphs: number;
1073
+ tableRows: number;
1074
+ tables: number;
1075
+ taskListItems: number;
1076
+ thematicBreaks: number;
1077
+ }
1078
+
1079
+ /**
1080
+ * Walks parsed markdown documents to collect structural metrics.
1081
+ *
1082
+ * Comparison is structural rather than textual, so a heading only counts when
1083
+ * the parser agrees it is one. Frontmatter is parsed as its own node because
1084
+ * a `---` delimited block is otherwise read as a setext heading, which would
1085
+ * report a level-two heading at the top of every file that has frontmatter.
1086
+ * GFM is enabled so tables and task list items exist as nodes at all.
1087
+ */
1088
+ export declare class MarkdownService {
1089
+ private readonly logger;
1090
+ constructor(logger: LoggerService);
1091
+ private readonly processor;
1092
+ /** Records a heading against the field for its depth. */
1093
+ private countHeading;
1094
+ /** Records a list item, separating GFM checkboxes from plain bullets. */
1095
+ private countListItem;
1096
+ /** Records a single node against the running totals. */
1097
+ private countNode;
1098
+ /** Walks every node in a parsed document. */
1099
+ private walk;
1100
+ /** Analyze the given markdown files, resolved against the directory. */
1101
+ analyze({ markdownFiles, workingDirectory }: MarkdownInput): MarkdownResult;
1102
+ /**
1103
+ * Analyze markdown source text that never came from a file of its own.
1104
+ *
1105
+ * The seam the jupyter analyzer reads through: a notebook's markdown cells
1106
+ * are documents without paths, and re-parsing them anywhere else would be a
1107
+ * second implementation of this counting.
1108
+ */
1109
+ analyzeContents(contents: string[]): MarkdownResult;
1110
+ }
1111
+
1112
+ /** Arguments accepted when measuring one file's comment blocks. */
1113
+ declare interface MeasureCommentsArguments {
1114
+ comments: CommentBudget;
1115
+ filePath: string;
1116
+ tokens: CommentToken[];
1117
+ }
1118
+
1119
+ /** Arguments accepted when measuring one comment's text directly. */
1120
+ declare interface MeasureCommentTextArguments {
1121
+ comments: CommentBudget;
1122
+ declaration: string;
1123
+ filePath: string;
1124
+ kind: string;
1125
+ line: number;
1126
+ /** The comment's prose, with every marker stripped. */
1127
+ prose: string;
1128
+ /** The comment exactly as the file carries it, markers and all. */
1129
+ source: string;
1130
+ }
1131
+
1132
+ /** Arguments accepted when measuring every configured language's comments. */
1133
+ declare interface MeasureLanguageCommentsArguments {
1134
+ /** One `comment`-selector custom statistic's budget, per declared statistic. */
1135
+ counters: CommentCounter[];
1136
+ files: LanguageCommentFiles;
1137
+ /**
1138
+ * Python's comments, already found by its own analyzer.
1139
+ *
1140
+ * Python is read in Python: its analysis runs `tokenize` in a subprocess,
1141
+ * which knows a `#` inside a string literal from a real comment the way no
1142
+ * line scanner can. The tokens arrive here rather than the measurements, so
1143
+ * a word still means one thing across every language.
1144
+ */
1145
+ pythonComments: readonly LocatedCommentToken[];
1146
+ workingDirectory: string;
1147
+ }
1148
+
1149
+ /**
1150
+ * What the notebooks themselves report, before any language analyzer runs.
1151
+ *
1152
+ * The two source lists are what the notebook contributes to the Python and
1153
+ * markdown analyzers: cell bodies that exist in no file of their own.
1154
+ */
1155
+ export declare interface NotebookParts {
1156
+ cells: number;
1157
+ codeCells: number;
1158
+ codeSources: string[];
1159
+ executedCells: number;
1160
+ files: number;
1161
+ markdownCells: number;
1162
+ markdownSources: string[];
1163
+ outputs: number;
1164
+ rawCells: number;
1165
+ }
1166
+
1167
+ /** A message split into the emoji the console shows and the prose telemetry stores. */
1168
+ declare interface ParsedLogMessage {
1169
+ emoji: string | undefined;
1170
+ text: string;
1171
+ }
1172
+
1173
+ /**
1174
+ * NestJS module that provides Python code analysis.
1175
+ */
1176
+ export declare class PythonModule {
1177
+ }
1178
+
1179
+ /**
1180
+ * Aggregated statistics produced by the Python analyzer.
1181
+ */
1182
+ export declare interface PythonResult {
1183
+ classes: number;
1184
+ commentLines: number;
1185
+ comments: number;
1186
+ /** Every comment `tokenize` found, with the file it was found in. */
1187
+ commentTokens: LocatedCommentToken[];
1188
+ constants: number;
1189
+ decorators: number;
1190
+ docstringLines: number;
1191
+ docstrings: number;
1192
+ files: number;
1193
+ functions: number;
1194
+ imports: number;
1195
+ lines: number;
1196
+ protocols: number;
1197
+ }
1198
+
1199
+ /**
1200
+ * Executes the Python analysis script and returns aggregated metrics.
1201
+ */
1202
+ export declare class PythonService {
1203
+ private readonly logger;
1204
+ constructor(logger: LoggerService);
1205
+ private readonly scriptPath;
1206
+ /**
1207
+ * Analyze the given Python files, resolved relative to the directory.
1208
+ *
1209
+ * The interpreter comes from the configuration because reaching Python is a
1210
+ * property of the repository, not of the analysis: a managed environment is
1211
+ * entered with `uv run python` or `poetry run python`, and a plain one with
1212
+ * `python3`.
1213
+ *
1214
+ * The paths travel over stdin rather than argv so that a repository with
1215
+ * thousands of Python files cannot overflow the command-line length limit.
1216
+ */
1217
+ analyze(args: AnalyzePythonArguments): PythonResult;
1218
+ /**
1219
+ * Analyze Python source text that never came from a file of its own.
1220
+ *
1221
+ * The seam the jupyter analyzer reads through. The interpreter reads files,
1222
+ * so the sources are staged in a temporary directory first — but the
1223
+ * analysis still runs from the measured directory, because a command like
1224
+ * `uv run python` resolves its environment from the working directory and
1225
+ * would find no project under the system temp directory.
1226
+ */
1227
+ analyzeContents(args: AnalyzePythonContentsArguments): PythonResult;
1228
+ }
1229
+
1230
+ /** Input to the Shell analysis step. */
1231
+ export declare interface ShellInput {
1232
+ shellFiles: string[];
1233
+ workingDirectory: string;
1234
+ }
1235
+
1236
+ /**
1237
+ * NestJS module that provides Shell source analysis.
1238
+ */
1239
+ export declare class ShellModule {
1240
+ }
1241
+
1242
+ /** Aggregated metrics collected from parsing Shell sources. */
1243
+ export declare interface ShellResult {
1244
+ commentLines: number;
1245
+ comments: number;
1246
+ conditionals: number;
1247
+ exports: number;
1248
+ files: number;
1249
+ functions: number;
1250
+ lines: number;
1251
+ loops: number;
1252
+ pipelines: number;
1253
+ shebangs: number;
1254
+ variables: number;
1255
+ }
1256
+
1257
+ /**
1258
+ * Counts the constructs a shell script is built from.
1259
+ *
1260
+ * Pattern-based rather than parsed: shell has no syntax tree available without
1261
+ * a native dependency, and a heuristic that a reader would agree with is worth
1262
+ * more here than no measurement at all. Comment lines are recognized before
1263
+ * anything else so that a `#` opening a line never reads as code.
1264
+ */
1265
+ export declare class ShellService {
1266
+ private readonly logger;
1267
+ constructor(logger: LoggerService);
1268
+ /** Records the constructs one line of shell holds. */
1269
+ private countLine;
1270
+ /** Records the constructs a line of shell code holds. */
1271
+ private countStatements;
1272
+ /** Analyze the given shell scripts, resolved against the directory. */
1273
+ analyze({ shellFiles, workingDirectory }: ShellInput): ShellResult;
1274
+ }
1275
+
1276
+ /**
1277
+ * Reads SQL's `--` and `/* ... *\/` comments.
1278
+ *
1279
+ * A line scanner rather than a tokenizer, so a `--` or `/*` inside a `'…'`
1280
+ * string literal is read as a comment here exactly as it already is when
1281
+ * `SqlService` counts keywords — the reading is the same, only the positions
1282
+ * are new. The block form is found with `indexOf` rather than a regular
1283
+ * expression: a pattern matching an unclosed `/*` through to end of input has
1284
+ * to fail once per occurrence, which is quadratic on adversarial input, while
1285
+ * two `indexOf` calls per comment never scan the same text twice.
1286
+ */
1287
+ export declare class SqlCommentsService {
1288
+ constructor();
1289
+ /** Every `/* *\/` comment's span, left to right and never overlapping. */
1290
+ private findBlockComments;
1291
+ /** Whether only whitespace precedes an offset on its own line. */
1292
+ private isOwnLine;
1293
+ /** The 1-indexed line an offset sits on. */
1294
+ private lineOf;
1295
+ /** Every block comment's span, as a positioned token. */
1296
+ private readBlocks;
1297
+ /** Every match of a comment pattern, as a positioned token. */
1298
+ private readMatches;
1299
+ /**
1300
+ * Reads every `--` and `/* *\/` comment, in the order they appear.
1301
+ *
1302
+ * Block spans are found first so a `--` matched inside one can be
1303
+ * dropped — the block already spans the whole thing, and without dropping
1304
+ * the overlap a `--` written as prose inside a block would be measured a
1305
+ * second time as a comment of its own.
1306
+ */
1307
+ read(content: string): CommentToken[];
1308
+ }
1309
+
1310
+ /** Input to the Sql analysis step. */
1311
+ export declare interface SqlInput {
1312
+ sqlFiles: string[];
1313
+ workingDirectory: string;
1314
+ }
1315
+
1316
+ /**
1317
+ * NestJS module that provides Sql source analysis.
1318
+ */
1319
+ export declare class SqlModule {
1320
+ }
1321
+
1322
+ /** Aggregated metrics collected from parsing Sql sources. */
1323
+ export declare interface SqlResult {
1324
+ comments: number;
1325
+ commonTableExpressions: number;
1326
+ creates: number;
1327
+ deletes: number;
1328
+ files: number;
1329
+ inserts: number;
1330
+ joins: number;
1331
+ lines: number;
1332
+ selects: number;
1333
+ statements: number;
1334
+ updates: number;
1335
+ }
1336
+
1337
+ /**
1338
+ * Counts the statements and clauses a SQL script is built from.
1339
+ *
1340
+ * Comments are stripped before anything is counted, so a `SELECT` inside a
1341
+ * `--` explanation is prose rather than a query. Statements are separated on
1342
+ * semicolons, which is what the dialect-agnostic reading of a script is.
1343
+ */
1344
+ export declare class SqlService {
1345
+ private readonly logger;
1346
+ constructor(logger: LoggerService);
1347
+ /** Records every keyword occurrence in the comment-free source. */
1348
+ private countKeywords;
1349
+ /** Counts the comments in a script and returns the source without them. */
1350
+ private stripComments;
1351
+ /** Analyze the given SQL scripts, resolved against the directory. */
1352
+ analyze({ sqlFiles, workingDirectory }: SqlInput): SqlResult;
1353
+ }
1354
+
1355
+ /** Input to the Toml analysis step. */
1356
+ export declare interface TomlInput {
1357
+ tomlFiles: string[];
1358
+ workingDirectory: string;
1359
+ }
1360
+
1361
+ /**
1362
+ * NestJS module that provides Toml source analysis.
1363
+ */
1364
+ export declare class TomlModule {
1365
+ }
1366
+
1367
+ /** Aggregated metrics collected from parsing Toml sources. */
1368
+ export declare interface TomlResult {
1369
+ arrays: number;
1370
+ arrayTables: number;
1371
+ comments: number;
1372
+ files: number;
1373
+ keys: number;
1374
+ lines: number;
1375
+ tables: number;
1376
+ }
1377
+
1378
+ /**
1379
+ * Counts the tables, keys, and arrays a TOML document declares.
1380
+ *
1381
+ * Read line by line while tracking whether a multi-line string is open, so a
1382
+ * `#` or a `[heading]` inside one of those strings is content rather than
1383
+ * syntax. That state is the whole reason this is not three regexes.
1384
+ */
1385
+ export declare class TomlService {
1386
+ private readonly logger;
1387
+ constructor(logger: LoggerService);
1388
+ /** Records a key assignment and whether its value opens an array. */
1389
+ private countKey;
1390
+ /** Records the declaration one line of TOML holds. */
1391
+ private countLine;
1392
+ /**
1393
+ * Whether the line closes the multi-line string that was open, or opens one.
1394
+ *
1395
+ * An odd number of `"""` or `'''` delimiters flips the state; an even number
1396
+ * opens and closes within the same line and leaves it as it was.
1397
+ */
1398
+ private isInsideMultilineString;
1399
+ /** Analyze the given TOML documents, resolved against the directory. */
1400
+ analyze({ tomlFiles, workingDirectory }: TomlInput): TomlResult;
1401
+ }
1402
+
1403
+ /**
1404
+ * Reads TypeScript and JavaScript's `//` and non-JSDoc `/* ... *\/` comments
1405
+ * from the compiler's real parse, rather than its bare scanner.
1406
+ *
1407
+ * A bare `ts.createScanner` cannot tell a `/` that divides from one that opens
1408
+ * a regular expression without the parser's context, and gets it wrong on real
1409
+ * source — swallowing a long run of genuine comments as trivia inside a
1410
+ * misread token. Parsing for real and walking every leaf token's leading and
1411
+ * trailing trivia is what a full parse resolves correctly, at the cost of one
1412
+ * parse per file instead of one scan.
1413
+ *
1414
+ * A JSDoc `/**` block is skipped here — `DeclarationCommentsService`
1415
+ * already measures those — the same check it uses to find one: the
1416
+ * delimiter's first three characters.
1417
+ */
1418
+ export declare class TypescriptCommentsService {
1419
+ constructor();
1420
+ /**
1421
+ * Walks every leaf token, collecting the comments around it.
1422
+ *
1423
+ * Both sides of every leaf, because a comment sharing its line with the
1424
+ * token before it is that token's *trailing* trivia rather than the next
1425
+ * token's *leading* trivia — `getLeadingCommentRanges` at a token's start
1426
+ * never finds it, only `getTrailingCommentRanges` at the previous token's
1427
+ * end does.
1428
+ */
1429
+ private collectComments;
1430
+ /** Choose the dialect a file is parsed as, from its extension. */
1431
+ private getScriptKind;
1432
+ /** Whether this range is a JSDoc block, measured elsewhere. */
1433
+ private isJsDoc;
1434
+ /** Whether only whitespace precedes an offset on its own line. */
1435
+ private isOwnLine;
1436
+ /** The 1-indexed line an offset sits on. */
1437
+ private lineOf;
1438
+ /** Strips a comment's delimiters, leaving its prose. */
1439
+ private toProse;
1440
+ /** Turns one comment range into a token, unless it is a JSDoc block. */
1441
+ private toToken;
1442
+ /** Reads every non-JSDoc comment the parse finds, in source order. */
1443
+ read(content: string, filePath: string): CommentToken[];
1444
+ }
1445
+
1446
+ /** Input to the TypeScript/JavaScript AST analysis step. */
1447
+ export declare interface TypescriptInput {
1448
+ /**
1449
+ * Every `comment`-selector custom statistic's budget, per declared statistic.
1450
+ *
1451
+ * The whole list, not only the counters this walk measures: a counter naming
1452
+ * a documentable `kind` is one of those and every other one is ignored here,
1453
+ * which is what leaves a configuration declaring no such statistic measuring
1454
+ * nothing rather than measuring against a limit nobody chose.
1455
+ */
1456
+ commentCounters: CommentCounter[];
1457
+ sourceFiles: string[];
1458
+ /** Configured counters over declarations, tallied during the same walk. */
1459
+ symbolCounters: TypescriptSymbolCounter[];
1460
+ workingDirectory: string;
1461
+ }
1462
+
1463
+ /**
1464
+ * NestJS module that provides TypeScript and JavaScript code analysis.
1465
+ */
1466
+ export declare class TypescriptModule {
1467
+ }
1468
+
1469
+ /** Aggregated metrics collected from walking TypeScript and JavaScript ASTs. */
1470
+ export declare interface TypescriptResult {
1471
+ asyncFunctions: number;
1472
+ blockComments: number;
1473
+ classes: number;
1474
+ commentLines: number;
1475
+ comments: number;
1476
+ constants: number;
1477
+ /** One measurement list per configured declaration-comment counter, keyed by its label. */
1478
+ declarationCommentCounts: Record<string, CommentMeasurement[]>;
1479
+ decorators: number;
1480
+ docComments: number;
1481
+ docTags: Record<string, number>;
1482
+ enums: number;
1483
+ exported: number;
1484
+ externalPackages: Set<string>;
1485
+ functions: number;
1486
+ genericDeclarations: number;
1487
+ imports: number;
1488
+ interfaces: number;
1489
+ jsFiles: number;
1490
+ lineComments: number;
1491
+ lines: number;
1492
+ methods: number;
1493
+ /** One tally per configured symbol counter, keyed by its label. */
1494
+ symbolCounts: Record<string, number>;
1495
+ syncFunctions: number;
1496
+ testFiles: number;
1497
+ todos: number;
1498
+ tsFiles: number;
1499
+ }
1500
+
1501
+ /** Walks TypeScript and JavaScript ASTs to collect code metrics. */
1502
+ export declare class TypescriptService {
1503
+ private readonly declarationComments;
1504
+ /** Creates the TypescriptService. */
1505
+ constructor(declarationComments: DeclarationCommentsService);
1506
+ /**
1507
+ * What to count for each syntax kind the walk cares about.
1508
+ *
1509
+ * Read by `dispatchNode`, which is the only caller: a kind absent from the
1510
+ * table is a node this analyzer counts nothing for, so adding a statistic
1511
+ * means adding a row here rather than another branch inside the walk.
1512
+ *
1513
+ * `dispatchNode` reaches a row by computed member access, which no call
1514
+ * graph can follow — callidescope records the call as unfollowable and
1515
+ * every row here as an entry point nothing calls. So a traced stack stops
1516
+ * at `dispatchNode` and each `handle*` method appears again at the root of
1517
+ * a stack of its own. That is the price of a table over a `switch`, and
1518
+ * worth knowing before reading those roots as dead code.
1519
+ */
1520
+ private readonly kindDispatch;
1521
+ /** Read one source file, count its lines and comments, and walk its AST. */
1522
+ private analyzeFile;
1523
+ /** Measure a documentable declaration's leading JSDoc comment, if it has one. */
1524
+ private collectDeclarationComments;
1525
+ /** Count a discovered comment and update the appropriate metrics. */
1526
+ private countComment;
1527
+ /**
1528
+ * Tally every configured counter that claims this declaration.
1529
+ *
1530
+ * A declaration is claimed when its kind is one the counter asked for and
1531
+ * it carries every modifier the counter requires; a counter naming no
1532
+ * modifiers asks for the kind alone.
1533
+ */
1534
+ private countSymbols;
1535
+ /**
1536
+ * Build the zeroed result every file's counters accumulate into.
1537
+ *
1538
+ * Every configured counter is seeded, so one that matches nothing reports a
1539
+ * zero rather than going missing from the report entirely.
1540
+ */
1541
+ private createEmptyResult;
1542
+ /** Dispatches non-class AST nodes to the appropriate metric-collection handler. */
1543
+ private dispatchNode;
1544
+ /** Narrow the configured counters to the ones that search this file. */
1545
+ private getCountersForFile;
1546
+ /** Choose the dialect a file is parsed as, from its extension. */
1547
+ private getScriptKind;
1548
+ /** Collect the modifier keywords a node carries, by configured name. */
1549
+ private getSymbolModifiers;
1550
+ /** Increments class, exported, and generic counts for a class node. */
1551
+ private handleClass;
1552
+ /** Increments enum and exported counts for an enum declaration node. */
1553
+ private handleEnum;
1554
+ /** Increments function, method, async, sync, exported, and generic counts for a function node. */
1555
+ private handleFunction;
1556
+ /** Increments import count and tracks the external package name if applicable. */
1557
+ private handleImport;
1558
+ /** Increments interface, exported, and generic counts for an interface declaration node. */
1559
+ private handleInterface;
1560
+ /** Increments method and async or sync counts for a method or accessor node. */
1561
+ private handleMethodOrAccessor;
1562
+ /** Increments exported and generic counts for a type alias declaration node. */
1563
+ private handleTypeAlias;
1564
+ /** Increments constant and exported counts for a const variable statement. */
1565
+ private handleVariable;
1566
+ /** Returns true when the node has an async modifier keyword. */
1567
+ private hasAsyncKeyword;
1568
+ /** Returns true when the node has an export modifier keyword. */
1569
+ private hasExportKeyword;
1570
+ /** Returns true when the node declares one or more type parameters. */
1571
+ private hasTypeParameters;
1572
+ /** Scan the provided source text and collect comment-based metrics. */
1573
+ private scanComments;
1574
+ /**
1575
+ * Seed one empty list per counter this walk is the measurer for.
1576
+ *
1577
+ * A counter naming a `kind` is one of those, and a counter naming only a
1578
+ * `language` is not: `kind` takes precedence, so a counter naming both is
1579
+ * measured here and its `language` never read, which is the same tie-break
1580
+ * `LanguageCommentsService` makes by skipping it. Written as a guarded loop
1581
+ * rather than a filtered `Object.fromEntries`, which would spend two of
1582
+ * `createEmptyResult`'s direct calls instead of one.
1583
+ */
1584
+ private seedDeclarationCommentCounts;
1585
+ /** Recursively visits each AST node and dispatches to the appropriate handler. */
1586
+ private walkNode;
1587
+ /** Analyzes TypeScript and JavaScript source files and returns aggregated AST metrics. */
1588
+ analyze(input: TypescriptInput): TypescriptResult;
1589
+ }
1590
+
1591
+ /** One configured counter over declarations, resolved for the analyzer. */
1592
+ export declare interface TypescriptSymbolCounter {
1593
+ kinds: CodometerSymbolKind[];
1594
+ label: string;
1595
+ modifiers: CodometerSymbolModifier[];
1596
+ /** Globs narrowing which files are searched; empty searches all of them. */
1597
+ patterns: string[];
1598
+ }
1599
+
1600
+ /**
1601
+ * What one file's AST walk carries with it, node to node.
1602
+ *
1603
+ * Narrowed once per file rather than rebuilt per node: which counters search a
1604
+ * file depends on its path, and the path does not change as the walk descends.
1605
+ */
1606
+ export declare interface TypescriptWalkContext {
1607
+ commentCounters: CommentCounter[];
1608
+ /**
1609
+ * The symbol counters that apply to the file being walked.
1610
+ *
1611
+ * Narrowed once per file rather than per node: which counters search a file
1612
+ * depends on its path, which does not change as the walk descends.
1613
+ */
1614
+ counters: TypescriptSymbolCounter[];
1615
+ filePath: string;
1616
+ insideClass: boolean;
1617
+ sourceFile: SourceFile;
1618
+ stats: TypescriptResult;
1619
+ }
1620
+
1621
+ /**
1622
+ * Reads YAML's comments from the tokenizer rather than from the text.
1623
+ *
1624
+ * The CST is the only view carrying both facts a measurement needs: an offset,
1625
+ * so a breach can name the line it sits on, and the tokenizer's judgement of
1626
+ * what is a comment at all, so a `#` inside a quoted scalar stays a character
1627
+ * in a string. The composed document has the second and not the first — it
1628
+ * hangs a whole run of `#` lines on a node as one string with no position,
1629
+ * blank lines and all.
1630
+ *
1631
+ * This is why YAML does not use `HashCommentsService`: a line scanner would
1632
+ * read `key: "a # b"` as carrying a comment, and YAML puts quoted scalars next
1633
+ * to `#` constantly.
1634
+ */
1635
+ export declare class YamlCommentsService {
1636
+ constructor();
1637
+ /** Walks one parsed token, recording every comment beneath it. */
1638
+ private collectComments;
1639
+ /** Whether a parsed token is a comment carrying an offset. */
1640
+ private isCommentToken;
1641
+ /** Turns one tokenizer offset into a positioned, placed comment line. */
1642
+ private toToken;
1643
+ /** Reads every comment the tokenizer found, in the order they appear. */
1644
+ read(content: string): CommentToken[];
1645
+ }
1646
+
1647
+ /** Input to the YAML analysis step. */
1648
+ export declare interface YamlInput {
1649
+ workingDirectory: string;
1650
+ yamlFiles: string[];
1651
+ }
1652
+
1653
+ /**
1654
+ * NestJS module that provides YAML document analysis.
1655
+ */
1656
+ export declare class YamlModule {
1657
+ }
1658
+
1659
+ /** Aggregated metrics collected from parsing YAML documents. */
1660
+ export declare interface YamlResult {
1661
+ aliases: number;
1662
+ anchors: number;
1663
+ comments: number;
1664
+ documents: number;
1665
+ files: number;
1666
+ keys: number;
1667
+ lines: number;
1668
+ mappings: number;
1669
+ maxDepth: number;
1670
+ scalars: number;
1671
+ sequences: number;
1672
+ }
1673
+
1674
+ /**
1675
+ * Walks parsed YAML documents to collect structural metrics.
1676
+ *
1677
+ * Counted from the parse tree rather than from the text, so a `#` inside a
1678
+ * quoted scalar stays a character in a string and an indented block reports
1679
+ * its real nesting instead of a column number. The `yaml` package keeps
1680
+ * comments and anchors on the nodes, which is what makes both countable at all.
1681
+ */
1682
+ export declare class YamlService {
1683
+ private readonly logger;
1684
+ constructor(logger: LoggerService);
1685
+ /** Walks a mapping's pairs or a sequence's items. */
1686
+ private countCollection;
1687
+ /** Records the comments attached to one node or document. */
1688
+ private countComments;
1689
+ /** Records one document and everything under it. */
1690
+ private countDocument;
1691
+ /** Records one node against the running totals, then walks its children. */
1692
+ private countNode;
1693
+ /** Analyze the given YAML files, resolved against the directory. */
1694
+ analyze({ workingDirectory, yamlFiles }: YamlInput): YamlResult;
1695
+ }
1696
+
1697
+ export { }