code-gauge 10.2.4 → 10.4.0

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.
Files changed (68) hide show
  1. package/README.md +47 -5
  2. package/THIRD-PARTY-NOTICES.txt +36 -12
  3. package/dist/allowDirectives.cjs +2 -0
  4. package/dist/allowDirectives.cjs.map +1 -0
  5. package/dist/allowDirectives.d.ts +45 -0
  6. package/dist/allowDirectives.js +2 -0
  7. package/dist/allowDirectives.js.map +1 -0
  8. package/dist/checkCommand.cjs +3 -3
  9. package/dist/checkCommand.cjs.map +1 -1
  10. package/dist/checkCommand.d.ts +1 -1
  11. package/dist/checkCommand.js +3 -3
  12. package/dist/checkCommand.js.map +1 -1
  13. package/dist/cli.cjs.map +1 -1
  14. package/dist/cli.js.map +1 -1
  15. package/dist/cliConfig.cjs.map +1 -1
  16. package/dist/cliConfig.js.map +1 -1
  17. package/dist/crossFileDuplication.cjs.map +1 -1
  18. package/dist/crossFileDuplication.js.map +1 -1
  19. package/dist/crossFileNearMiss.cjs +1 -1
  20. package/dist/crossFileNearMiss.cjs.map +1 -1
  21. package/dist/crossFileNearMiss.js +1 -1
  22. package/dist/crossFileNearMiss.js.map +1 -1
  23. package/dist/duplicateSelection.cjs.map +1 -1
  24. package/dist/duplicateSelection.js.map +1 -1
  25. package/dist/duplication.cjs +1 -1
  26. package/dist/duplication.cjs.map +1 -1
  27. package/dist/duplication.js +1 -1
  28. package/dist/duplication.js.map +1 -1
  29. package/dist/exclusion.cjs.map +1 -1
  30. package/dist/exclusion.js.map +1 -1
  31. package/dist/git.cjs +2 -1
  32. package/dist/git.cjs.map +1 -1
  33. package/dist/git.d.ts +7 -0
  34. package/dist/git.js +2 -1
  35. package/dist/git.js.map +1 -1
  36. package/dist/languages.cjs.map +1 -1
  37. package/dist/languages.js.map +1 -1
  38. package/dist/metrics.cjs.map +1 -1
  39. package/dist/metrics.js.map +1 -1
  40. package/dist/nativeMetrics.cjs +1 -1
  41. package/dist/nativeMetrics.cjs.map +1 -1
  42. package/dist/nativeMetrics.js +1 -1
  43. package/dist/nativeMetrics.js.map +1 -1
  44. package/dist/scan.cjs +1 -1
  45. package/dist/scan.cjs.map +1 -1
  46. package/dist/scan.js +1 -1
  47. package/dist/scan.js.map +1 -1
  48. package/dist/thresholdCheck.cjs +1 -1
  49. package/dist/thresholdCheck.cjs.map +1 -1
  50. package/dist/thresholdCheck.d.ts +15 -1
  51. package/dist/thresholdCheck.js +1 -1
  52. package/dist/thresholdCheck.js.map +1 -1
  53. package/dist/thresholds.cjs.map +1 -1
  54. package/dist/thresholds.js.map +1 -1
  55. package/dist/wasmBinding.cjs.map +1 -1
  56. package/dist/wasmBinding.js.map +1 -1
  57. package/dist/worker.cjs.map +1 -1
  58. package/dist/worker.js.map +1 -1
  59. package/native/Cargo.lock +24 -24
  60. package/native/Cargo.toml +12 -12
  61. package/native/code-gauge.wasm +0 -0
  62. package/native/src/complexity.rs +28 -22
  63. package/native/src/duplication.rs +37 -3
  64. package/native/src/functions.rs +10 -12
  65. package/native/src/languages.rs +2 -3
  66. package/native/src/ncss.rs +29 -12
  67. package/native/src/util.rs +6 -0
  68. package/package.json +39 -43
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Test](https://github.com/WillBooster/code-gauge/actions/workflows/test.yml/badge.svg)](https://github.com/WillBooster/code-gauge/actions/workflows/test.yml)
6
6
  [![Test rust](https://github.com/WillBooster/code-gauge/actions/workflows/test-rust.yml/badge.svg)](https://github.com/WillBooster/code-gauge/actions/workflows/test-rust.yml)
7
7
  [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
8
- [![wbfy](https://img.shields.io/badge/wbfy-20.36.11-1e90ff.svg)](https://github.com/WillBooster/shared/tree/main/packages/wbfy)
8
+ [![wbfy](https://img.shields.io/badge/wbfy-21.3.0-1e90ff.svg)](https://github.com/WillBooster/shared/tree/main/packages/wbfy)
9
9
 
10
10
  A command-line tool that ranks the files of a project by refactoring priority and checks code
11
11
  against metric thresholds, built for AI-agent workflows: an agent asked to "refactor this
@@ -156,7 +156,11 @@ With `--base <ref>`, the check covers only what the working tree changed since t
156
156
  tail;
157
157
  - a file-level threshold, when the change adds or deletes lines of the file (a rename or a mode
158
158
  change alone does not count);
159
- - a duplicated block, when it overlaps lines the change added.
159
+ - a duplicated block, when it overlaps lines the change added;
160
+ - the whole file, when the change touches a [directive](#allowing-a-warning-in-the-code): it edits
161
+ a directive's line or the lines between it and its function (through the next code line for a
162
+ directive attached to no function), or deletes a line holding one. Editing the function itself
163
+ only touches the function.
160
164
 
161
165
  `--base` only filters the report: the files measured and the duplication found are those of the
162
166
  check without it, so code pasted from an unchanged file is reported where it was pasted. A
@@ -206,6 +210,39 @@ repository are warnings on stderr.
206
210
  `check` accepts `--config`, `--include-tests`, `--json`, and the `--duplication-*` options of the
207
211
  ranking command.
208
212
 
213
+ ### Allowing a warning in the code
214
+
215
+ When code exceeding a warning limit is deliberately kept, a comment on a line of its own allows the
216
+ value it has, with the reason:
217
+
218
+ ```ts
219
+ // code-gauge-allow functionParameterCount 8 -- mirrors the positional API of the vendor SDK
220
+ export function createClient(host, port, user, password, database, timeout, retries, logger) {
221
+ ```
222
+
223
+ ```py
224
+ # code-gauge-allow-file functionParameterCount 9 -- each benchmark passes its whole setup
225
+ ```
226
+
227
+ - `code-gauge-allow <metric> <max> -- <reason>` covers the function starting on the next code
228
+ line, the functions nested in it, and the duplicated blocks starting in it. Only comment lines
229
+ may stand between the directive and that line. Where a function's span starts below its
230
+ decorators or attributes (Python and Rust), the directive goes below them; where the span
231
+ includes them (Java, Kotlin, C#, and TypeScript), above them. A directive after code on the same line is
232
+ not read.
233
+ - `code-gauge-allow-file <metric> <max> -- <reason>` covers the whole file, and is the only way to
234
+ allow `fileNcss`.
235
+ - `<metric>` is a metric name of the [JSON report](#json-report) (`functionCognitiveComplexity`,
236
+ `duplicateLines`, ...), `<max>` the largest value allowed, and the reason is required.
237
+
238
+ A directive allows warnings only: a value above `<max>` or above an error limit is reported as
239
+ usual, so code that grows worse is reported again, and an error is fixed or its limit changed in the
240
+ [config](#configuration). A copy of a duplicated block is reported in its own place, so each copy
241
+ needs a directive of its own. The report counts the limits directives allowed, and lists as
242
+ warnings, which never fail the check, each directive that is malformed, misplaced, allows an
243
+ error-level value, is outgrown by the value, or allows nothing; with `--base`, only those whose
244
+ line or covered code the change touches.
245
+
209
246
  ### JSON report
210
247
 
211
248
  `code-gauge check --json` prints:
@@ -223,7 +260,8 @@ ranking command.
223
260
  "fileViolationCount": 0,
224
261
  "duplicationViolationCount": 1,
225
262
  "checkedFileCount": 2,
226
- "checkedFunctionCount": 2
263
+ "checkedFunctionCount": 2,
264
+ "allowedLimitCount": 0
227
265
  },
228
266
  "violations": [
229
267
  {
@@ -267,6 +305,7 @@ ranking command.
267
305
  "partners": [{ "file": "src/report.ts", "startLine": 1, "endLine": 18 }]
268
306
  }
269
307
  ],
308
+ "directiveProblems": [],
270
309
  "errors": [],
271
310
  "warnings": []
272
311
  }
@@ -276,7 +315,8 @@ ranking command.
276
315
  - `base`, `mergeBase`: the `--base` ref and the commit compared against; absent without `--base`.
277
316
  - `summary`: the violation counts in total, per level, and per kind, and the number of files and
278
317
  functions checked: those under the target or, with `--base`, the changed files under it and the
279
- functions the change touches.
318
+ functions the change touches, and `allowedLimitCount`, the exceeded limits that
319
+ [directives](#allowing-a-warning-in-the-code) allowed, which `violations` leaves out.
280
320
  - `violations`: the errors first, each level ordered by `file`, then `startLine`. Each has
281
321
  - `kind`: `function`, `file`, or `duplication`;
282
322
  - `level`: `error` when any of its `exceeded` limits is an error, otherwise `warning`;
@@ -297,6 +337,8 @@ ranking command.
297
337
  place repeating only a part of the block (less than `minSimilarityPercent` of its tokens) is
298
338
  not a copy of it, and is listed only for a block that no place copies as a whole, its parts
299
339
  being repeated in different places.
340
+ - `directiveProblems`: the directives the text report lists as warnings, each with its `file`,
341
+ `line`, and `message`, ordered by `file`, then `line`.
300
342
  - `errors`: the files the check covers that could not be measured (exit code 2); unrelated to the
301
343
  `error` level of a violation.
302
344
  - `warnings`: files measured without cross-file duplication data, and unmeasured files of the
@@ -511,7 +553,7 @@ does not skip generated files (e.g. `.d.ts`, `.min.js`) or test files.
511
553
  ### Cloudflare Workers
512
554
 
513
555
  Workers cannot load native addons, so the package's `workerd` export runs the same API on a
514
- WebAssembly build of the Rust engine (about 3.2 MB gzipped). Wrangler resolves that export
556
+ WebAssembly build of the Rust engine (about 5.3 MB gzipped). Wrangler resolves that export
515
557
  automatically, and no `nodejs_compat` flag is needed:
516
558
 
517
559
  ```ts
@@ -8366,7 +8366,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
8366
8366
  DEALINGS IN THE SOFTWARE.
8367
8367
 
8368
8368
  ====================================================================================================
8369
- willbooster-tree-sitter 2.0.1 — MIT — https://github.com/WillBooster/tree-sitter
8369
+ willbooster-tree-sitter 17.1.4 — MIT — https://github.com/WillBooster/tree-sitter
8370
8370
  ====================================================================================================
8371
8371
  ----- LICENSE -----
8372
8372
  The MIT License (MIT)
@@ -9004,7 +9004,7 @@ permissive licensing, and of not having licensing issues being an
9004
9004
  obstacle to adoption, that text has been removed.
9005
9005
 
9006
9006
  ====================================================================================================
9007
- willbooster-tree-sitter-c 5.0.0 — MIT — https://github.com/WillBooster/tree-sitter-c
9007
+ willbooster-tree-sitter-c 34.0.0 — MIT — https://github.com/WillBooster/tree-sitter-c
9008
9008
  ====================================================================================================
9009
9009
  ----- LICENSE -----
9010
9010
  The MIT License (MIT)
@@ -9031,7 +9031,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9031
9031
  SOFTWARE.
9032
9032
 
9033
9033
  ====================================================================================================
9034
- willbooster-tree-sitter-c-sharp 4.0.0 — MIT — https://github.com/WillBooster/tree-sitter-c-sharp
9034
+ willbooster-tree-sitter-c-sharp 19.0.0 — MIT — https://github.com/WillBooster/tree-sitter-c-sharp
9035
9035
  ====================================================================================================
9036
9036
  ----- LICENSE -----
9037
9037
  The MIT License (MIT)
@@ -9058,7 +9058,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9058
9058
  SOFTWARE.
9059
9059
 
9060
9060
  ====================================================================================================
9061
- willbooster-tree-sitter-cpp 3.0.0 — MIT — https://github.com/WillBooster/tree-sitter-cpp
9061
+ willbooster-tree-sitter-cpp 28.0.0 — MIT — https://github.com/WillBooster/tree-sitter-cpp
9062
9062
  ====================================================================================================
9063
9063
  ----- LICENSE -----
9064
9064
  The MIT License (MIT)
@@ -9085,7 +9085,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9085
9085
  SOFTWARE.
9086
9086
 
9087
9087
  ====================================================================================================
9088
- willbooster-tree-sitter-go 2.0.0 — MIT — https://github.com/WillBooster/tree-sitter-go
9088
+ willbooster-tree-sitter-go 9.0.0 — MIT — https://github.com/WillBooster/tree-sitter-go
9089
9089
  ====================================================================================================
9090
9090
  ----- LICENSE -----
9091
9091
  The MIT License (MIT)
@@ -9112,7 +9112,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9112
9112
  SOFTWARE.
9113
9113
 
9114
9114
  ====================================================================================================
9115
- willbooster-tree-sitter-java 2.0.0 — MIT — https://github.com/WillBooster/tree-sitter-java
9115
+ willbooster-tree-sitter-java 32.0.0 — MIT — https://github.com/WillBooster/tree-sitter-java
9116
9116
  ====================================================================================================
9117
9117
  ----- LICENSE -----
9118
9118
  MIT License
@@ -9139,7 +9139,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9139
9139
  SOFTWARE.
9140
9140
 
9141
9141
  ====================================================================================================
9142
- willbooster-tree-sitter-javascript 5.0.0 — MIT — https://github.com/WillBooster/tree-sitter-javascript
9142
+ willbooster-tree-sitter-javascript 21.1.0 — MIT — https://github.com/WillBooster/tree-sitter-javascript
9143
9143
  ====================================================================================================
9144
9144
  ----- LICENSE -----
9145
9145
  The MIT License (MIT)
@@ -9166,7 +9166,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9166
9166
  SOFTWARE.
9167
9167
 
9168
9168
  ====================================================================================================
9169
- willbooster-tree-sitter-kotlin 5.0.0 — MIT — https://github.com/WillBooster/tree-sitter-kotlin
9169
+ willbooster-tree-sitter-kotlin 19.0.0 — MIT — https://github.com/WillBooster/tree-sitter-kotlin
9170
9170
  ====================================================================================================
9171
9171
  ----- LICENSE -----
9172
9172
  The MIT License (MIT)
@@ -9193,7 +9193,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9193
9193
  SOFTWARE.
9194
9194
 
9195
9195
  ====================================================================================================
9196
- willbooster-tree-sitter-python 2.0.0 — MIT — https://github.com/WillBooster/tree-sitter-python
9196
+ willbooster-tree-sitter-python 16.0.0 — MIT — https://github.com/WillBooster/tree-sitter-python
9197
9197
  ====================================================================================================
9198
9198
  ----- LICENSE -----
9199
9199
  The MIT License (MIT)
@@ -9220,7 +9220,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9220
9220
  SOFTWARE.
9221
9221
 
9222
9222
  ====================================================================================================
9223
- willbooster-tree-sitter-ruby 2.0.0 — MIT — https://github.com/WillBooster/tree-sitter-ruby
9223
+ willbooster-tree-sitter-ruby 15.0.0 — MIT — https://github.com/WillBooster/tree-sitter-ruby
9224
9224
  ====================================================================================================
9225
9225
  ----- LICENSE -----
9226
9226
  The MIT License (MIT)
@@ -9247,7 +9247,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9247
9247
  SOFTWARE.
9248
9248
 
9249
9249
  ====================================================================================================
9250
- willbooster-tree-sitter-rust 3.0.0 — MIT — https://github.com/WillBooster/tree-sitter-rust
9250
+ willbooster-tree-sitter-rust 17.0.0 — MIT — https://github.com/WillBooster/tree-sitter-rust
9251
9251
  ====================================================================================================
9252
9252
  ----- LICENSE -----
9253
9253
  The MIT License (MIT)
@@ -9274,7 +9274,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9274
9274
  SOFTWARE.
9275
9275
 
9276
9276
  ====================================================================================================
9277
- willbooster-tree-sitter-typescript 8.0.0 — MIT — https://github.com/WillBooster/tree-sitter-typescript
9277
+ willbooster-tree-sitter-typescript 26.0.1 — MIT — https://github.com/WillBooster/tree-sitter-typescript
9278
9278
  ====================================================================================================
9279
9279
  ----- LICENSE -----
9280
9280
  The MIT License (MIT)
@@ -9300,6 +9300,30 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9300
9300
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9301
9301
  SOFTWARE.
9302
9302
 
9303
+ ----- javascript/LICENSE -----
9304
+ The MIT License (MIT)
9305
+
9306
+ Copyright (c) 2014 Max Brunsfeld
9307
+ Copyright (c) 2026 WillBooster Inc.
9308
+
9309
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9310
+ of this software and associated documentation files (the "Software"), to deal
9311
+ in the Software without restriction, including without limitation the rights
9312
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9313
+ copies of the Software, and to permit persons to whom the Software is
9314
+ furnished to do so, subject to the following conditions:
9315
+
9316
+ The above copyright notice and this permission notice shall be included in all
9317
+ copies or substantial portions of the Software.
9318
+
9319
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9320
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9321
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
9322
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
9323
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9324
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9325
+ SOFTWARE.
9326
+
9303
9327
  ====================================================================================================
9304
9328
  windows-link 0.2.1 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs
9305
9329
  ====================================================================================================
@@ -0,0 +1,2 @@
1
+ "use strict";const e=require("./thresholds.cjs"),t=`code-gauge-allow`,n=`code-gauge-allow-file`,r=`\`${t} <metric> <max> -- <reason>\``,i=/^[\s!#*/]*(code-gauge-allow(?:-file)?)(?![\w-])(.*)$/u,a=/^(\w+)\s+(\d+(?:\.\d+)?)\s+--\s+\S/u,o=new Set(e.fileThresholds.map(t=>e.metricNameOf(t))),s=new Set([...e.functionThresholds,e.duplicationThreshold].map(t=>e.metricNameOf(t)));function c(e,n){let r={directives:[],problems:[],attachmentSpans:[]};if(!e.includes(t))return r;let a=e.split(/\r\n|\n|\r/u),o=new Set(n.codeLineNumbers);for(let e=1;e<=a.length;e++){let t=o.has(e)?void 0:i.exec(a[e-1]??``);if(!t)continue;let[,s=``,c=``]=t,{directive:l,span:d}=u(e,s,c,a,n);r.attachmentSpans.push(d),typeof l==`string`?r.problems.push({line:e,message:l}):r.directives.push({line:e,...l})}return r}function l(e){return i.test(e)}function u(e,t,r,i,a){let o=d(t,r.replace(/\*\/\s*$/u,``).trim());if(typeof o==`string`||t===n)return{directive:o,span:{startLine:e,endLine:e}};let s=a.codeLineNumbers.find(t=>t>e),c=f(o,i.slice(e,(s??i.length+1)-1).some(e=>e.trim()===``)?void 0:s,a.functions);return{directive:c,span:typeof c==`string`?{startLine:e,endLine:s??i.length}:{startLine:e,endLine:c.target.startLine,endsAtFunction:!0}}}function d(e,i){let c=a.exec(i);if(!c)return`malformed directive; expected ${r.replace(t,e)} with a reason`;let[,l=``,u=``]=c,d=e===n?/* @__PURE__ */ new Set([...s,...o]):s;return d.has(l)?{metric:l,max:Number(u)}:o.has(l)?`${l} is measured per file; allow it with ${n}`:`unknown metric ${l}; expected one of ${[...d].join(`, `)}`}function f(e,r,i){let[a]=i.filter(e=>e.startLine===r).toSorted((e,t)=>e.startColumn-t.startColumn);return a?{...e,target:a}:r===void 0?`${t} must directly precede a function; use ${n} for the whole file`:`no function starts on line ${r}, the code after ${t}; put it right above the line a function starts on (below decorators the function's span leaves out), or use ${n}`}exports.isDirectiveLine=l,exports.parseAllowDirectives=c;
2
+ //# sourceMappingURL=allowDirectives.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowDirectives.cjs","names":["fileThresholds","metricNameOf","functionThresholds","duplicationThreshold","require_thresholds","allowKeyword","allowFileKeyword","syntax","directivePattern","argumentsPattern","fileMetrics","threshold","functionMetrics","parseAllowDirectives","result","content","lines","codeLines","metrics","line","match","keyword","rest","readDirective","span","directive","isDirectiveLine","text","parsed","parseDirective","following","codeLine","attachToFunction","metric","max","allowed","target","functions","fn","next","left","right"],"sources":["../src/allowDirectives.ts"],"sourcesContent":["import { duplicationThreshold, fileThresholds, functionThresholds, metricNameOf } from './thresholds.js';\nimport type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** A `code-gauge-allow` or `code-gauge-allow-file` comment: the warnings of a metric it allows. */\nexport interface AllowDirective {\n line: number;\n metric: string;\n /** The largest value of the metric allowed at the warning level. */\n max: number;\n /** The function a `code-gauge-allow` precedes; absent for `code-gauge-allow-file`, which covers the file. */\n target?: FunctionMetrics;\n}\n\n/** A directive that cannot be applied as written, or that allows nothing. */\nexport interface DirectiveProblem {\n line: number;\n message: string;\n}\n\nexport interface AttachmentSpan {\n startLine: number;\n endLine: number;\n endsAtFunction?: true;\n}\n\nexport interface AllowDirectives {\n directives: AllowDirective[];\n problems: DirectiveProblem[];\n /**\n * The lines whose change may change what a directive allows: those from the directive to the\n * first line of the function it attaches to, which `endsAtFunction` marks as the function's own,\n * or, when it attaches to none, through the next code line; a `code-gauge-allow-file` or\n * malformed directive's own line.\n */\n attachmentSpans: AttachmentSpan[];\n}\n\nconst allowKeyword = 'code-gauge-allow';\nconst allowFileKeyword = 'code-gauge-allow-file';\nconst syntax = `\\`${allowKeyword} <metric> <max> -- <reason>\\``;\n\n// The comment delimiters of every supported language (`//`, `///`, `/*`, `*`, `#`) may precede the\n// keyword; any other text before it makes the comment prose mentioning a directive.\nconst directivePattern = /^[\\s!#*/]*(code-gauge-allow(?:-file)?)(?![\\w-])(.*)$/u;\nconst argumentsPattern = /^(\\w+)\\s+(\\d+(?:\\.\\d+)?)\\s+--\\s+\\S/u;\n\nconst fileMetrics = new Set(fileThresholds.map((threshold) => metricNameOf(threshold)));\nconst functionMetrics = new Set(\n [...functionThresholds, duplicationThreshold].map((threshold) => metricNameOf(threshold))\n);\n\n/**\n * The directives of a file, each on a comment-only line: `code-gauge-allow` attaches to the\n * function starting on the next code line, with only comment lines between, and\n * `code-gauge-allow-file` to the whole file. Decorators and attributes are not skipped, since\n * telling where one ends takes a parser; a directive for a function whose span starts after them\n * goes below them. A directive after code on a line is not read, since the line may hold it in a\n * string.\n */\nexport function parseAllowDirectives(content: string, metrics: CodeMetrics): AllowDirectives {\n const result: AllowDirectives = { directives: [], problems: [], attachmentSpans: [] };\n if (!content.includes(allowKeyword)) {\n return result;\n }\n const lines = content.split(/\\r\\n|\\n|\\r/u);\n const codeLines = new Set(metrics.codeLineNumbers);\n for (let line = 1; line <= lines.length; line++) {\n const match = codeLines.has(line) ? undefined : directivePattern.exec(lines[line - 1] ?? '');\n if (!match) continue;\n const [, keyword = '', rest = ''] = match;\n const { directive, span } = readDirective(line, keyword, rest, lines, metrics);\n result.attachmentSpans.push(span);\n if (typeof directive === 'string') {\n result.problems.push({ line, message: directive });\n } else {\n result.directives.push({ line, ...directive });\n }\n }\n return result;\n}\n\n/**\n * Whether the line reads as a directive when comment-only, for a line whose measurement is not at\n * hand, such as one a change deleted.\n */\nexport function isDirectiveLine(text: string): boolean {\n return directivePattern.test(text);\n}\n\n/** The directive on the line or what is wrong with it, and the end of its attachment span. */\nfunction readDirective(\n line: number,\n keyword: string,\n rest: string,\n lines: readonly string[],\n metrics: CodeMetrics\n): { directive: Omit<AllowDirective, 'line'> | string; span: AttachmentSpan } {\n const parsed = parseDirective(keyword, rest.replace(/\\*\\/\\s*$/u, '').trim());\n if (typeof parsed === 'string' || keyword === allowFileKeyword) {\n return { directive: parsed, span: { startLine: line, endLine: line } };\n }\n const following = metrics.codeLineNumbers.find((codeLine) => codeLine > line);\n const isBlankBetween = lines.slice(line, (following ?? lines.length + 1) - 1).some((text) => text.trim() === '');\n const directive = attachToFunction(parsed, isBlankBetween ? undefined : following, metrics.functions);\n // An edit of the code line a directive fails to attach to may be what detached it.\n const span: AttachmentSpan =\n typeof directive === 'string'\n ? { startLine: line, endLine: following ?? lines.length }\n : { startLine: line, endLine: directive.target.startLine, endsAtFunction: true };\n return { directive, span };\n}\n\n/** The metric and max of a directive, or what is wrong with it. */\nfunction parseDirective(keyword: string, rest: string): { metric: string; max: number } | string {\n const match = argumentsPattern.exec(rest);\n if (!match) {\n return `malformed directive; expected ${syntax.replace(allowKeyword, keyword)} with a reason`;\n }\n const [, metric = '', max = ''] = match;\n const allowed = keyword === allowFileKeyword ? new Set([...functionMetrics, ...fileMetrics]) : functionMetrics;\n if (allowed.has(metric)) {\n return { metric, max: Number(max) };\n }\n if (fileMetrics.has(metric)) {\n return `${metric} is measured per file; allow it with ${allowFileKeyword}`;\n }\n return `unknown metric ${metric}; expected one of ${[...allowed].join(', ')}`;\n}\n\n/** The parsed directive with the outermost function starting on `next`, or what is wrong. */\nfunction attachToFunction(\n parsed: { metric: string; max: number },\n next: number | undefined,\n functions: readonly FunctionMetrics[]\n): { metric: string; max: number; target: FunctionMetrics } | string {\n const [target] = functions\n .filter((fn) => fn.startLine === next)\n .toSorted((left, right) => left.startColumn - right.startColumn);\n if (target) {\n return { ...parsed, target };\n }\n return next === undefined\n ? `${allowKeyword} must directly precede a function; use ${allowFileKeyword} for the whole file`\n : `no function starts on line ${next}, the code after ${allowKeyword}; put it right above the line a function starts on (below decorators the function's span leaves out), or use ${allowFileKeyword}`;\n}\n"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { CodeMetrics, FunctionMetrics } from './types.js';
2
+ /** A `code-gauge-allow` or `code-gauge-allow-file` comment: the warnings of a metric it allows. */
3
+ export interface AllowDirective {
4
+ line: number;
5
+ metric: string;
6
+ /** The largest value of the metric allowed at the warning level. */
7
+ max: number;
8
+ /** The function a `code-gauge-allow` precedes; absent for `code-gauge-allow-file`, which covers the file. */
9
+ target?: FunctionMetrics;
10
+ }
11
+ /** A directive that cannot be applied as written, or that allows nothing. */
12
+ export interface DirectiveProblem {
13
+ line: number;
14
+ message: string;
15
+ }
16
+ export interface AttachmentSpan {
17
+ startLine: number;
18
+ endLine: number;
19
+ endsAtFunction?: true;
20
+ }
21
+ export interface AllowDirectives {
22
+ directives: AllowDirective[];
23
+ problems: DirectiveProblem[];
24
+ /**
25
+ * The lines whose change may change what a directive allows: those from the directive to the
26
+ * first line of the function it attaches to, which `endsAtFunction` marks as the function's own,
27
+ * or, when it attaches to none, through the next code line; a `code-gauge-allow-file` or
28
+ * malformed directive's own line.
29
+ */
30
+ attachmentSpans: AttachmentSpan[];
31
+ }
32
+ /**
33
+ * The directives of a file, each on a comment-only line: `code-gauge-allow` attaches to the
34
+ * function starting on the next code line, with only comment lines between, and
35
+ * `code-gauge-allow-file` to the whole file. Decorators and attributes are not skipped, since
36
+ * telling where one ends takes a parser; a directive for a function whose span starts after them
37
+ * goes below them. A directive after code on a line is not read, since the line may hold it in a
38
+ * string.
39
+ */
40
+ export declare function parseAllowDirectives(content: string, metrics: CodeMetrics): AllowDirectives;
41
+ /**
42
+ * Whether the line reads as a directive when comment-only, for a line whose measurement is not at
43
+ * hand, such as one a change deleted.
44
+ */
45
+ export declare function isDirectiveLine(text: string): boolean;
@@ -0,0 +1,2 @@
1
+ import{duplicationThreshold as e,fileThresholds as t,functionThresholds as n,metricNameOf as r}from"./thresholds.js";const i=`code-gauge-allow`,a=`code-gauge-allow-file`,o=`\`${i} <metric> <max> -- <reason>\``,s=/^[\s!#*/]*(code-gauge-allow(?:-file)?)(?![\w-])(.*)$/u,c=/^(\w+)\s+(\d+(?:\.\d+)?)\s+--\s+\S/u,l=new Set(t.map(e=>r(e))),u=new Set([...n,e].map(e=>r(e)));function d(e,t){let n={directives:[],problems:[],attachmentSpans:[]};if(!e.includes(i))return n;let r=e.split(/\r\n|\n|\r/u),a=new Set(t.codeLineNumbers);for(let e=1;e<=r.length;e++){let i=a.has(e)?void 0:s.exec(r[e-1]??``);if(!i)continue;let[,o=``,c=``]=i,{directive:l,span:u}=p(e,o,c,r,t);n.attachmentSpans.push(u),typeof l==`string`?n.problems.push({line:e,message:l}):n.directives.push({line:e,...l})}return n}function f(e){return s.test(e)}function p(e,t,n,r,i){let o=m(t,n.replace(/\*\/\s*$/u,``).trim());if(typeof o==`string`||t===a)return{directive:o,span:{startLine:e,endLine:e}};let s=i.codeLineNumbers.find(t=>t>e),c=h(o,r.slice(e,(s??r.length+1)-1).some(e=>e.trim()===``)?void 0:s,i.functions);return{directive:c,span:typeof c==`string`?{startLine:e,endLine:s??r.length}:{startLine:e,endLine:c.target.startLine,endsAtFunction:!0}}}function m(e,t){let n=c.exec(t);if(!n)return`malformed directive; expected ${o.replace(i,e)} with a reason`;let[,r=``,s=``]=n,d=e===a?/* @__PURE__ */ new Set([...u,...l]):u;return d.has(r)?{metric:r,max:Number(s)}:l.has(r)?`${r} is measured per file; allow it with ${a}`:`unknown metric ${r}; expected one of ${[...d].join(`, `)}`}function h(e,t,n){let[r]=n.filter(e=>e.startLine===t).toSorted((e,t)=>e.startColumn-t.startColumn);return r?{...e,target:r}:t===void 0?`${i} must directly precede a function; use ${a} for the whole file`:`no function starts on line ${t}, the code after ${i}; put it right above the line a function starts on (below decorators the function's span leaves out), or use ${a}`}export{f as isDirectiveLine,d as parseAllowDirectives};
2
+ //# sourceMappingURL=allowDirectives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowDirectives.js","names":["allowKeyword","allowFileKeyword","syntax","directivePattern","argumentsPattern","fileMetrics","fileThresholds","metricNameOf","threshold","functionMetrics","functionThresholds","duplicationThreshold","parseAllowDirectives","result","content","lines","codeLines","metrics","line","match","keyword","rest","readDirective","span","directive","isDirectiveLine","text","parsed","parseDirective","following","codeLine","attachToFunction","metric","max","allowed","target","functions","fn","next","left","right"],"sources":["../src/allowDirectives.ts"],"sourcesContent":["import { duplicationThreshold, fileThresholds, functionThresholds, metricNameOf } from './thresholds.js';\nimport type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** A `code-gauge-allow` or `code-gauge-allow-file` comment: the warnings of a metric it allows. */\nexport interface AllowDirective {\n line: number;\n metric: string;\n /** The largest value of the metric allowed at the warning level. */\n max: number;\n /** The function a `code-gauge-allow` precedes; absent for `code-gauge-allow-file`, which covers the file. */\n target?: FunctionMetrics;\n}\n\n/** A directive that cannot be applied as written, or that allows nothing. */\nexport interface DirectiveProblem {\n line: number;\n message: string;\n}\n\nexport interface AttachmentSpan {\n startLine: number;\n endLine: number;\n endsAtFunction?: true;\n}\n\nexport interface AllowDirectives {\n directives: AllowDirective[];\n problems: DirectiveProblem[];\n /**\n * The lines whose change may change what a directive allows: those from the directive to the\n * first line of the function it attaches to, which `endsAtFunction` marks as the function's own,\n * or, when it attaches to none, through the next code line; a `code-gauge-allow-file` or\n * malformed directive's own line.\n */\n attachmentSpans: AttachmentSpan[];\n}\n\nconst allowKeyword = 'code-gauge-allow';\nconst allowFileKeyword = 'code-gauge-allow-file';\nconst syntax = `\\`${allowKeyword} <metric> <max> -- <reason>\\``;\n\n// The comment delimiters of every supported language (`//`, `///`, `/*`, `*`, `#`) may precede the\n// keyword; any other text before it makes the comment prose mentioning a directive.\nconst directivePattern = /^[\\s!#*/]*(code-gauge-allow(?:-file)?)(?![\\w-])(.*)$/u;\nconst argumentsPattern = /^(\\w+)\\s+(\\d+(?:\\.\\d+)?)\\s+--\\s+\\S/u;\n\nconst fileMetrics = new Set(fileThresholds.map((threshold) => metricNameOf(threshold)));\nconst functionMetrics = new Set(\n [...functionThresholds, duplicationThreshold].map((threshold) => metricNameOf(threshold))\n);\n\n/**\n * The directives of a file, each on a comment-only line: `code-gauge-allow` attaches to the\n * function starting on the next code line, with only comment lines between, and\n * `code-gauge-allow-file` to the whole file. Decorators and attributes are not skipped, since\n * telling where one ends takes a parser; a directive for a function whose span starts after them\n * goes below them. A directive after code on a line is not read, since the line may hold it in a\n * string.\n */\nexport function parseAllowDirectives(content: string, metrics: CodeMetrics): AllowDirectives {\n const result: AllowDirectives = { directives: [], problems: [], attachmentSpans: [] };\n if (!content.includes(allowKeyword)) {\n return result;\n }\n const lines = content.split(/\\r\\n|\\n|\\r/u);\n const codeLines = new Set(metrics.codeLineNumbers);\n for (let line = 1; line <= lines.length; line++) {\n const match = codeLines.has(line) ? undefined : directivePattern.exec(lines[line - 1] ?? '');\n if (!match) continue;\n const [, keyword = '', rest = ''] = match;\n const { directive, span } = readDirective(line, keyword, rest, lines, metrics);\n result.attachmentSpans.push(span);\n if (typeof directive === 'string') {\n result.problems.push({ line, message: directive });\n } else {\n result.directives.push({ line, ...directive });\n }\n }\n return result;\n}\n\n/**\n * Whether the line reads as a directive when comment-only, for a line whose measurement is not at\n * hand, such as one a change deleted.\n */\nexport function isDirectiveLine(text: string): boolean {\n return directivePattern.test(text);\n}\n\n/** The directive on the line or what is wrong with it, and the end of its attachment span. */\nfunction readDirective(\n line: number,\n keyword: string,\n rest: string,\n lines: readonly string[],\n metrics: CodeMetrics\n): { directive: Omit<AllowDirective, 'line'> | string; span: AttachmentSpan } {\n const parsed = parseDirective(keyword, rest.replace(/\\*\\/\\s*$/u, '').trim());\n if (typeof parsed === 'string' || keyword === allowFileKeyword) {\n return { directive: parsed, span: { startLine: line, endLine: line } };\n }\n const following = metrics.codeLineNumbers.find((codeLine) => codeLine > line);\n const isBlankBetween = lines.slice(line, (following ?? lines.length + 1) - 1).some((text) => text.trim() === '');\n const directive = attachToFunction(parsed, isBlankBetween ? undefined : following, metrics.functions);\n // An edit of the code line a directive fails to attach to may be what detached it.\n const span: AttachmentSpan =\n typeof directive === 'string'\n ? { startLine: line, endLine: following ?? lines.length }\n : { startLine: line, endLine: directive.target.startLine, endsAtFunction: true };\n return { directive, span };\n}\n\n/** The metric and max of a directive, or what is wrong with it. */\nfunction parseDirective(keyword: string, rest: string): { metric: string; max: number } | string {\n const match = argumentsPattern.exec(rest);\n if (!match) {\n return `malformed directive; expected ${syntax.replace(allowKeyword, keyword)} with a reason`;\n }\n const [, metric = '', max = ''] = match;\n const allowed = keyword === allowFileKeyword ? new Set([...functionMetrics, ...fileMetrics]) : functionMetrics;\n if (allowed.has(metric)) {\n return { metric, max: Number(max) };\n }\n if (fileMetrics.has(metric)) {\n return `${metric} is measured per file; allow it with ${allowFileKeyword}`;\n }\n return `unknown metric ${metric}; expected one of ${[...allowed].join(', ')}`;\n}\n\n/** The parsed directive with the outermost function starting on `next`, or what is wrong. */\nfunction attachToFunction(\n parsed: { metric: string; max: number },\n next: number | undefined,\n functions: readonly FunctionMetrics[]\n): { metric: string; max: number; target: FunctionMetrics } | string {\n const [target] = functions\n .filter((fn) => fn.startLine === next)\n .toSorted((left, right) => left.startColumn - right.startColumn);\n if (target) {\n return { ...parsed, target };\n }\n return next === undefined\n ? `${allowKeyword} must directly precede a function; use ${allowFileKeyword} for the whole file`\n : `no function starts on line ${next}, the code after ${allowKeyword}; put it right above the line a function starts on (below decorators the function's span leaves out), or use ${allowFileKeyword}`;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- "use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./thresholds.cjs"),n=require("./cliConfig.cjs"),r=require("./git.cjs"),i=require("./exclusion.cjs"),a=require("./scan.cjs"),o=require("./thresholdCheck.cjs");let s=require("@willbooster/shared-lib"),c=require("node:fs/promises"),l=require("node:path");l=e.__toESM(l,1);let u=require("node:os");u=e.__toESM(u,1);async function d(e,r,i){try{let s=a.resolveTarget(e),c=await n.loadConfig(r.config,await a.configSearchDirectory(s)),l=n.resolveOptions(r,c),u=await m(s,r.base,l),d=o.checkThresholds(u.files,u.duplication,t.resolveLimits(i,c.config.thresholds));await p(d.violations,u.root),l.json?A(r,u,d):x(r,u,d),u.errors.length>0?process.exitCode=2:f(d.violations)&&(process.exitCode=1)}catch(e){a.writeStderr(`Error: ${(0,s.getErrorMessage)(e)}\n`),process.exitCode=2}}function f(e){return e.some(({level:e})=>e===`error`)}async function p(e,t){for(let n of e.filter(({kind:e})=>e===`file`)){let e=await(0,c.readFile)(l.default.join(t,n.file),`utf8`);/[\n\r]$/u.test(e)&&(n.endLine=Math.max(n.endLine-1,1))}}async function m(e,t,n){let i=await(0,c.realpath)(e),o=(await(0,c.stat)(i)).isFile()?i:void 0;if(o&&!a.getLanguage(o,n,!0))throw Error(`${l.default.basename(o)}: unsupported file type`);let s;try{s=await(0,c.realpath)(await r.resolveRepoRoot(o?l.default.dirname(i):i))}catch(e){if(t!==void 0)throw e;return await h(i,n)}return await g({repoRoot:s,canonicalTarget:i,targetFile:o},t,n)}async function h(e,t){let n=await a.configSearchDirectory(e),r=await a.scanTarget(e,{...t,loadExclusion:e=>i.loadExclusion(n,t.exclude,e)});return{files:r.files.map(({file:e,metrics:t})=>({file:a.formatPath(e,r.displayRoot),metrics:t})),duplication:b(r,t),errors:r.errors,warnings:r.warnings,root:r.displayRoot}}async function g({repoRoot:e,canonicalTarget:t,targetFile:n},o,s){if(n===void 0&&t!==e&&await r.isIgnored(e,t))throw Error(`${a.formatPath(t,e)}: git ignores this directory, so no file in it is checked; target a file to check it regardless`);let c=await r.listRepositoryFiles(e),u=new Set(n===void 0?[]:[n]),d=n!==void 0&&!c.has(a.formatPath(n,e))?a.formatPath(n,e):void 0;d!==void 0&&c.add(d);let f=i.keepPaths(await i.loadRepositoryExclusion(e,c,s.exclude),u),p=a.formatPath(t,e),m=await a.scanListedFiles(e,c,{...s,loadExclusion:()=>Promise.resolve(f)},u,n===void 0&&t!==e?p:``),h=b(m,s),g=n=>a.isWithinDirectory(l.default.join(e,n),t),y=m.files.map(({file:t,metrics:n})=>({file:a.formatPath(t,e),metrics:n})),x=y.filter(({file:e})=>g(e)),S=n=>t===e||n.startsWith(`${p}/`)||n.startsWith(`${p}:`),C;if(o!==void 0){C=await r.resolveMergeBase(e,o);let t=await r.listChangedFiles(e,C);d!==void 0&&t.push({status:`added`,headPath:d});let n=t.filter(({status:e,headPath:t})=>e!==`deleted`&&g(t));x=await v(y,n,e,C),S=e=>n.some(({headPath:t})=>e.startsWith(`${t}:`))}return{files:x,duplication:h,..._(m,S),root:e,mergeBase:C}}function _(e,t){return{errors:e.errors.filter(e=>t(e)),warnings:[...e.warnings,...e.errors.filter(e=>!t(e))]}}async function v(e,t,n,i){let a=new Map(e.map(e=>[e.file,e]));return(await(0,s.mapConcurrently)(t,u.default.availableParallelism()*2,async e=>{let t=a.get(e.headPath);if(t===void 0)return[];let{metrics:o}=t,s={headStart:1,headCount:o.lines.total},u=e.basePath===void 0?[s]:await r.listLineHunks(n,i,e.basePath,e.headPath);return o.language===`python`&&u.some(({headCount:e})=>e===0)&&y(u,await(0,c.readFile)(l.default.join(n,e.headPath),`utf8`)),[{...t,hunks:u}]})).flat()}function y(e,t){let n=t.split(/\r\n|\n|\r/u);for(let t of e)if(!(t.headCount>0))for(;t.headStart>0&&/^\s*(?:#.*)?$/u.test(n[t.headStart-1]??``);)--t.headStart}function b(e,t){if(e.fatalError)throw Error(e.fatalError);return a.addCrossFileDuplication(e,t),{crossFile:e.crossFileDuplication,minSimilarityPercent:t.duplication.minSimilarityPercent}}function x(e,n,r){let{violations:i}=r,o=`${n.files.length} ${e.base===void 0?``:`changed `}files, ${r.checkedFunctionCount} functions checked${S(e,n)}`;n.errors.length>0?a.writeStdout(`Check could not complete: ${n.errors.length} measurement failures (details on stderr); ${C(i)} in the measured files (${o}).\n`):i.length===0?a.writeStdout(`No threshold violations: ${o}.\n`):a.writeStdout(`Threshold violations: ${C(i)} (${o}).\n`);for(let e of i)a.writeStdout(`${T(e)}\n`);let s=new Set(i.flatMap(({exceeded:e})=>e.map(({metric:e})=>e))),c=t.thresholds.filter(e=>s.has(t.metricNameOf(e)));if(c.length>0){a.writeStdout(`
1
+ "use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./metrics.cjs"),n=require("./thresholds.cjs"),r=require("./allowDirectives.cjs"),i=require("./cliConfig.cjs"),a=require("./git.cjs"),o=require("./exclusion.cjs"),s=require("./scan.cjs"),c=require("./thresholdCheck.cjs");let l=require("@willbooster/shared-lib"),u=require("node:fs/promises"),d=require("node:os");d=e.__toESM(d,1);let f=require("node:path");f=e.__toESM(f,1);async function p(e,t,r){try{let a=s.resolveTarget(e),o=await i.loadConfig(t.config,await s.configSearchDirectory(a)),l=i.resolveOptions(t,o),u=await _(a,t.base,l),d=await m(u.files,u.root),f=c.checkThresholds(d,u.duplication,n.resolveLimits(r,o.config.thresholds));await g(f.violations,u.root),l.json?I(t,u,f):E(t,u,f),u.errors.length>0?process.exitCode=2:h(f.violations)&&(process.exitCode=1)}catch(e){s.writeStderr(`Error: ${(0,l.getErrorMessage)(e)}\n`),process.exitCode=2}}async function m(e,t){return await(0,l.mapConcurrently)(e,d.default.availableParallelism()*2,async e=>({...e,allowDirectives:r.parseAllowDirectives(await(0,u.readFile)(f.default.join(t,e.file),`utf8`),e.metrics)}))}function h(e){return e.some(({level:e})=>e===`error`)}async function g(e,t){for(let n of e.filter(({kind:e})=>e===`file`)){let e=await(0,u.readFile)(f.default.join(t,n.file),`utf8`);/[\n\r]$/u.test(e)&&(n.endLine=Math.max(n.endLine-1,1))}}async function _(e,t,n){let r=await(0,u.realpath)(e),i=(await(0,u.stat)(r)).isFile()?r:void 0;if(i&&!s.getLanguage(i,n,!0))throw Error(`${f.default.basename(i)}: unsupported file type`);let o;try{o=await(0,u.realpath)(await a.resolveRepoRoot(i?f.default.dirname(r):r))}catch(e){if(t!==void 0)throw e;return await v(r,n)}return await y({repoRoot:o,canonicalTarget:r,targetFile:i},t,n)}async function v(e,t){let n=await s.configSearchDirectory(e),r=await s.scanTarget(e,{...t,loadExclusion:e=>o.loadExclusion(n,t.exclude,e)});return{files:r.files.map(({file:e,metrics:t})=>({file:s.formatPath(e,r.displayRoot),metrics:t})),duplication:T(r,t),errors:r.errors,warnings:r.warnings,root:r.displayRoot}}async function y({repoRoot:e,canonicalTarget:t,targetFile:n},r,i){if(n===void 0&&t!==e&&await a.isIgnored(e,t))throw Error(`${s.formatPath(t,e)}: git ignores this directory, so no file in it is checked; target a file to check it regardless`);let c=await a.listRepositoryFiles(e),l=new Set(n===void 0?[]:[n]),u=n!==void 0&&!c.has(s.formatPath(n,e))?s.formatPath(n,e):void 0;u!==void 0&&c.add(u);let d=o.keepPaths(await o.loadRepositoryExclusion(e,c,i.exclude),l),p=s.formatPath(t,e),m=await s.scanListedFiles(e,c,{...i,loadExclusion:()=>Promise.resolve(d)},l,n===void 0&&t!==e?p:``),h=T(m,i),g=n=>s.isWithinDirectory(f.default.join(e,n),t),_=m.files.map(({file:t,metrics:n})=>({file:s.formatPath(t,e),metrics:n})),v=_.filter(({file:e})=>g(e)),y=n=>t===e||n.startsWith(`${p}/`)||n.startsWith(`${p}:`),S;if(r!==void 0){S=await a.resolveMergeBase(e,r);let t=await a.listChangedFiles(e,S);u!==void 0&&t.push({status:`added`,headPath:u});let n=t.filter(({status:e,headPath:t})=>e!==`deleted`&&g(t));v=await x(_,n,e,S),y=e=>n.some(({headPath:t})=>e.startsWith(`${t}:`))}return{files:v,duplication:h,...b(m,y),root:e,mergeBase:S}}function b(e,t){return{errors:e.errors.filter(e=>t(e)),warnings:[...e.warnings,...e.errors.filter(e=>!t(e))]}}async function x(e,t,n,r){let i=new Map(e.map(e=>[e.file,e]));return(await(0,l.mapConcurrently)(t,d.default.availableParallelism()*2,async e=>{let t=i.get(e.headPath);if(t===void 0)return[];let{metrics:o}=t,s={headStart:1,headCount:o.lines.total},c=e.basePath===void 0?[s]:await a.listLineHunks(n,r,e.basePath,e.headPath);return e.basePath!==void 0&&await S(c,o.language,()=>a.readFileAt(n,r,e.basePath??``)),o.language===`python`&&c.some(({headCount:e})=>e===0)&&C(c,await(0,u.readFile)(f.default.join(n,e.headPath),`utf8`)),[{...t,hunks:c}]})).flat()}async function S(e,n,i){let a=e.filter(({deletedLines:e})=>e?.some(e=>r.isDirectiveLine(e)));if(a.length===0)return;let o=new Set(t.measureCode(await i(),{language:n}).codeLineNumbers);for(let e of a)e.deletesDirective=(e.deletedLines??[]).some((t,n)=>r.isDirectiveLine(t)&&!o.has((e.baseStart??0)+n))}function C(e,t){let n=t.split(/\r\n|\n|\r/u);for(let t of e)if(!(t.headCount>0))for(;t.headStart>0&&w(n[t.headStart-1]??``);)--t.headStart}function w(e){return/^\s*(?:#.*)?$/u.test(e)&&!r.isDirectiveLine(e)}function T(e,t){if(e.fatalError)throw Error(e.fatalError);return s.addCrossFileDuplication(e,t),{crossFile:e.crossFileDuplication,minSimilarityPercent:t.duplication.minSimilarityPercent}}function E(e,t,r){let{violations:i}=r,a=r.allowedLimitCount>0?`, ${r.allowedLimitCount} exceeded limits allowed by directives`:``,o=`${t.files.length} ${e.base===void 0?``:`changed `}files, ${r.checkedFunctionCount} functions checked${a}${O(e,t)}`;t.errors.length>0?s.writeStdout(`Check could not complete: ${t.errors.length} measurement failures (details on stderr); ${k(i)} in the measured files (${o}).\n`):i.length===0?s.writeStdout(`No threshold violations: ${o}.\n`):s.writeStdout(`Threshold violations: ${k(i)} (${o}).\n`);for(let e of i)s.writeStdout(`${j(e)}\n`);for(let e of r.directiveProblems)s.writeStdout(`${D(e)}\n`);let c=new Set(i.flatMap(({exceeded:e})=>e.map(({metric:e})=>e))),l=n.thresholds.filter(e=>c.has(n.metricNameOf(e)));if(l.length>0){s.writeStdout(`
2
2
  How to fix:
3
- `);for(let{label:e,hint:t}of c)a.writeStdout(`- ${e}: ${t}\n`)}for(let e of n.warnings)a.writeStderr(`Warning: ${e}\n`);for(let e of n.errors)a.writeStderr(`Error: ${e}\n`)}function S(e,t){return t.mergeBase===void 0?``:`; base ${e.base}, merge-base ${t.mergeBase.slice(0,12)}`}function C(e){let t=w(e);return`${t.errorViolationCount} errors, ${t.warningViolationCount} warnings (${t.functionViolationCount} functions, ${t.fileViolationCount} files, ${t.duplicationViolationCount} duplicated blocks)`}function w(e){let t=t=>e.filter(e=>e.kind===t).length,n=e.filter(({level:e})=>e===`error`).length;return{violationCount:e.length,errorViolationCount:n,warningViolationCount:e.length-n,functionViolationCount:t(`function`),fileViolationCount:t(`file`),duplicationViolationCount:t(`duplication`)}}function T(e){let t=e.exceeded.map(({metric:t,label:n,value:r,level:i,limit:a})=>`${n} ${Math.ceil(r*10)/10} (${i===e.level?``:`${i} `}max ${e.kind===`duplication`?Math.max(Math.ceil(a)-1,0):a}${t===`functionCognitiveComplexity`?E(e):``})`).join(`, `);return`${e.level}: ${D(e,t)}`}function E({largestBlocks:e=[]}){let t=e.map(({startLine:e,endLine:t,name:n,cognitiveComplexity:r})=>`L${e}${t===e?``:`-${t}`}${n?` ${O(n)}`:``} ${r}`);return t.length>0?`; largest parts ${t.join(`, `)}`:``}function D(e,t){switch(e.kind){case`file`:return`${e.file}: ${t}`;case`function`:return`${k(e)} ${O(e.name??``)}: ${t}`;case`duplication`:{let n=(e.partners??[]).map(e=>k(e)),r=[...n.slice(0,3),...n.length>3?[`...`]:[]];return`${k(e)}: ${t}${r.length>0?`, also at ${r.join(`, `)}`:``}`}}}function O(e){return e.replaceAll(/\s*[\n\r]\s*/gu,` `)}function k({file:e,startLine:t,endLine:n}){return`${e}:${t}-${n}`}function A(e,t,n){a.writeStdout(JSON.stringify({passed:!f(n.violations)&&t.errors.length===0,base:e.base,mergeBase:t.mergeBase,summary:{...w(n.violations),checkedFileCount:t.files.length,checkedFunctionCount:n.checkedFunctionCount},violations:n.violations,errors:t.errors,warnings:t.warnings},void 0,2)+`
4
- `)}exports.runCheckCommand=d,exports.scanCheckScope=m;
3
+ `);for(let{label:e,hint:t}of l)s.writeStdout(`- ${e}: ${t}\n`)}for(let e of t.warnings)s.writeStderr(`Warning: ${e}\n`);for(let e of t.errors)s.writeStderr(`Error: ${e}\n`)}function D({file:e,line:t,message:n}){return`warning: ${e}:${t}: ${n}`}function O(e,t){return t.mergeBase===void 0?``:`; base ${e.base}, merge-base ${t.mergeBase.slice(0,12)}`}function k(e){let t=A(e);return`${t.errorViolationCount} errors, ${t.warningViolationCount} warnings (${t.functionViolationCount} functions, ${t.fileViolationCount} files, ${t.duplicationViolationCount} duplicated blocks)`}function A(e){let t=t=>e.filter(e=>e.kind===t).length,n=e.filter(({level:e})=>e===`error`).length;return{violationCount:e.length,errorViolationCount:n,warningViolationCount:e.length-n,functionViolationCount:t(`function`),fileViolationCount:t(`file`),duplicationViolationCount:t(`duplication`)}}function j(e){let t=e.exceeded.map(({metric:t,label:n,value:r,level:i,limit:a})=>`${n} ${Math.ceil(r*10)/10} (${i===e.level?``:`${i} `}max ${e.kind===`duplication`?Math.max(Math.ceil(a)-1,0):a}${t===`functionCognitiveComplexity`?M(e):``})`).join(`, `);return`${e.level}: ${N(e,t)}`}function M({largestBlocks:e=[]}){let t=e.map(({startLine:e,endLine:t,name:n,cognitiveComplexity:r})=>`L${e}${t===e?``:`-${t}`}${n?` ${P(n)}`:``} ${r}`);return t.length>0?`; largest parts ${t.join(`, `)}`:``}function N(e,t){switch(e.kind){case`file`:return`${e.file}: ${t}`;case`function`:return`${F(e)} ${P(e.name??``)}: ${t}`;case`duplication`:{let n=(e.partners??[]).map(e=>F(e)),r=[...n.slice(0,3),...n.length>3?[`...`]:[]];return`${F(e)}: ${t}${r.length>0?`, also at ${r.join(`, `)}`:``}`}}}function P(e){return e.replaceAll(/\s*[\n\r]\s*/gu,` `)}function F({file:e,startLine:t,endLine:n}){return`${e}:${t}-${n}`}function I(e,t,n){s.writeStdout(JSON.stringify({passed:!h(n.violations)&&t.errors.length===0,base:e.base,mergeBase:t.mergeBase,summary:{...A(n.violations),checkedFileCount:t.files.length,checkedFunctionCount:n.checkedFunctionCount,allowedLimitCount:n.allowedLimitCount},violations:n.violations,directiveProblems:n.directiveProblems,errors:t.errors,warnings:t.warnings},void 0,2)+`
4
+ `)}exports.runCheckCommand=p,exports.scanCheckScope=_;
5
5
  //# sourceMappingURL=checkCommand.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"checkCommand.cjs","names":["resolveTarget","loadConfig","configSearchDirectory","resolveOptions","checkThresholds","resolveLimits","getErrorMessage","readFile","path","realpath","stat","getLanguage","resolveRepoRoot","scanTarget","loadExclusion","formatPath","isIgnored","listRepositoryFiles","keepPaths","loadRepositoryExclusion","scanListedFiles","isWithinDirectory","resolveMergeBase","listChangedFiles","mapConcurrently","os","listLineHunks","thresholds","metricNameOf","require_runtime","require_thresholds","require_cliConfig","require_git","require_exclusion","require_scan","require_thresholdCheck","_willbooster_shared_lib","node_fs_promises","node_path","node_os","runCheckCommand","resolvedTarget","target","config","cliOptions","options","scope","scanCheckScope","result","cliLimits","endFileViolationsAtLastLine","printJsonReport","printTextReport","hasErrors","error","violations","level","violation","kind","content","root","canonicalTarget","targetFile","repoRoot","base","scanDirectoryWalk","scanRepository","searchDirectory","scan","absolutePaths","file","measureDuplication","repositoryFiles","explicitFiles","unlistedTarget","exclusion","targetPath","duplication","isInTarget","relativePath","measuredFiles","files","isCovered","mergeBase","changedFiles","changesInTarget","status","headPath","attachHunks","partitionErrors","measuredByPath","measured","changed","wholeFile","metrics","hunks","headCount","anchorDeletionsToCode","lines","hunk","checked","describeBase","describeCounts","formatViolation","violatedMetrics","exceeded","metric","violatedThresholds","threshold","label","hint","warning","counts","countViolations","count","errorViolationCount","value","limit","describeLargestBlocks","describeViolation","blocks","largestBlocks","startLine","endLine","name","toOneLine","cognitiveComplexity","formatLocation","partners","partner","listed"],"sources":["../src/checkCommand.ts"],"sourcesContent":["import { readFile, realpath, stat } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { getErrorMessage, mapConcurrently } from '@willbooster/shared-lib';\nimport { loadConfig, resolveOptions, type CliOptions, type ResolvedOptions } from './cliConfig.js';\nimport { keepPaths, loadExclusion, loadRepositoryExclusion } from './exclusion.js';\nimport {\n isIgnored,\n listChangedFiles,\n listLineHunks,\n listRepositoryFiles,\n resolveMergeBase,\n resolveRepoRoot,\n type ChangedFile,\n type LineHunk,\n} from './git.js';\nimport {\n addCrossFileDuplication,\n configSearchDirectory,\n formatPath,\n getLanguage,\n isWithinDirectory,\n resolveTarget,\n scanListedFiles,\n scanTarget,\n writeStderr,\n writeStdout,\n type ScanResult,\n} from './scan.js';\nimport {\n checkThresholds,\n type BlockLocation,\n type CheckedFile,\n type CheckedDuplication,\n type CheckResult,\n type Violation,\n} from './thresholdCheck.js';\nimport { metricNameOf, resolveLimits, thresholds, type LimitsByLevel } from './thresholds.js';\n\n/** Raw options of the `check` subcommand; every field is undefined unless the flag was passed. */\nexport interface CheckCliOptions extends CliOptions {\n base?: string;\n}\n\n/** The measured files a check covers and the duplication they take part in. */\nexport interface CheckScope {\n files: CheckedFile[];\n duplication: CheckedDuplication;\n /** Measurement failures of files the check covers. */\n errors: string[];\n warnings: string[];\n /** The directory the files' paths are relative to. */\n root: string;\n mergeBase?: string;\n}\n\n/** Caps how many partner locations a duplicated block lists so the report stays scannable. */\nconst maxListedPartners = 3;\n\n/**\n * Reports every exceeded threshold of the target or, with `base`, of what the working tree changed\n * under it since the merge-base with that ref. Exit codes: 0 no error-level violations, 1 error-level\n * violations, 2 files the check covers could not be measured.\n */\nexport async function runCheckCommand(\n target: string,\n cliOptions: CheckCliOptions,\n cliLimits: LimitsByLevel\n): Promise<void> {\n try {\n const resolvedTarget = resolveTarget(target);\n const config = await loadConfig(cliOptions.config, await configSearchDirectory(resolvedTarget));\n const options = resolveOptions(cliOptions, config);\n const scope = await scanCheckScope(resolvedTarget, cliOptions.base, options);\n const result = checkThresholds(scope.files, scope.duplication, resolveLimits(cliLimits, config.config.thresholds));\n await endFileViolationsAtLastLine(result.violations, scope.root);\n\n if (options.json) {\n printJsonReport(cliOptions, scope, result);\n } else {\n printTextReport(cliOptions, scope, result);\n }\n if (scope.errors.length > 0) {\n process.exitCode = 2;\n } else if (hasErrors(result.violations)) {\n process.exitCode = 1;\n }\n } catch (error) {\n writeStderr(`Error: ${getErrorMessage(error)}\\n`);\n process.exitCode = 2;\n }\n}\n\nfunction hasErrors(violations: readonly Violation[]): boolean {\n return violations.some(({ level }) => level === 'error');\n}\n\n/**\n * A file's measured line count includes the empty line after a final line terminator, which is\n * not a line of the file, so file-level violations end at the last line that exists.\n */\nasync function endFileViolationsAtLastLine(violations: readonly Violation[], root: string): Promise<void> {\n for (const violation of violations.filter(({ kind }) => kind === 'file')) {\n const content = await readFile(path.join(root, violation.file), 'utf8');\n if (/[\\n\\r]$/u.test(content)) {\n violation.endLine = Math.max(violation.endLine - 1, 1);\n }\n }\n}\n\n/**\n * Inside a git repository, the scope is the repository's git-visible files under the target, and\n * duplication is detected against all of them; `base` only narrows the scope to what changed.\n * Outside a repository, where there is no change to narrow to, the target directory is walked.\n */\nexport async function scanCheckScope(\n resolvedTarget: string,\n base: string | undefined,\n options: ResolvedOptions\n): Promise<CheckScope> {\n const canonicalTarget = await realpath(resolvedTarget);\n const targetStat = await stat(canonicalTarget);\n const targetFile = targetStat.isFile() ? canonicalTarget : undefined;\n if (targetFile && !getLanguage(targetFile, options, true)) {\n throw new Error(`${path.basename(targetFile)}: unsupported file type`);\n }\n let repoRoot;\n try {\n repoRoot = await realpath(await resolveRepoRoot(targetFile ? path.dirname(canonicalTarget) : canonicalTarget));\n } catch (error) {\n if (base !== undefined) {\n throw error;\n }\n return await scanDirectoryWalk(canonicalTarget, options);\n }\n return await scanRepository({ repoRoot, canonicalTarget, targetFile }, base, options);\n}\n\nasync function scanDirectoryWalk(canonicalTarget: string, options: ResolvedOptions): Promise<CheckScope> {\n const searchDirectory = await configSearchDirectory(canonicalTarget);\n const scan = await scanTarget(canonicalTarget, {\n ...options,\n loadExclusion: (absolutePaths) => loadExclusion(searchDirectory, options.exclude, absolutePaths),\n });\n return {\n files: scan.files.map(({ file, metrics }) => ({ file: formatPath(file, scan.displayRoot), metrics })),\n duplication: measureDuplication(scan, options),\n errors: scan.errors,\n warnings: scan.warnings,\n root: scan.displayRoot,\n };\n}\n\ninterface RepositoryTarget {\n repoRoot: string;\n canonicalTarget: string;\n /** Set when the target is a file, which is measured even when git ignores it or it is excluded. */\n targetFile?: string;\n}\n\n/**\n * Measures every git-visible file (tracked or untracked non-ignored) of the repository, so that\n * code copied from outside the target is detected as duplication, and keeps the files under the\n * target: all of them or, with `base`, the changed ones with their line hunks.\n */\nasync function scanRepository(\n { repoRoot, canonicalTarget, targetFile }: RepositoryTarget,\n base: string | undefined,\n options: ResolvedOptions\n): Promise<CheckScope> {\n // Only git-visible files are measured, so a directory git ignores would pass as a check of no\n // files. A directory git lists but whose sources it ignores is not told apart from a repository\n // or package whose only sources are ignored build output, which must pass.\n if (targetFile === undefined && canonicalTarget !== repoRoot && (await isIgnored(repoRoot, canonicalTarget))) {\n throw new Error(\n `${formatPath(canonicalTarget, repoRoot)}: git ignores this directory, so no file in it is checked; target a file to check it regardless`\n );\n }\n const repositoryFiles = await listRepositoryFiles(repoRoot);\n const explicitFiles = new Set(targetFile === undefined ? [] : [targetFile]);\n const unlistedTarget =\n targetFile !== undefined && !repositoryFiles.has(formatPath(targetFile, repoRoot))\n ? formatPath(targetFile, repoRoot)\n : undefined;\n if (unlistedTarget !== undefined) {\n repositoryFiles.add(unlistedTarget);\n }\n const exclusion = keepPaths(await loadRepositoryExclusion(repoRoot, repositoryFiles, options.exclude), explicitFiles);\n // Scan errors start with the file or directory they concern.\n const targetPath = formatPath(canonicalTarget, repoRoot);\n const scan = await scanListedFiles(\n repoRoot,\n repositoryFiles,\n { ...options, loadExclusion: () => Promise.resolve(exclusion) },\n explicitFiles,\n targetFile === undefined && canonicalTarget !== repoRoot ? targetPath : ''\n );\n const duplication = measureDuplication(scan, options);\n const isInTarget = (relativePath: string): boolean =>\n isWithinDirectory(path.join(repoRoot, relativePath), canonicalTarget);\n const measuredFiles = scan.files.map(({ file, metrics }) => ({ file: formatPath(file, repoRoot), metrics }));\n let files = measuredFiles.filter(({ file }) => isInTarget(file));\n let isCovered = (error: string): boolean =>\n canonicalTarget === repoRoot || error.startsWith(`${targetPath}/`) || error.startsWith(`${targetPath}:`);\n let mergeBase;\n if (base !== undefined) {\n mergeBase = await resolveMergeBase(repoRoot, base);\n const changedFiles = await listChangedFiles(repoRoot, mergeBase);\n // A targeted file git does not list has no diff; all of it counts as added.\n if (unlistedTarget !== undefined) {\n changedFiles.push({ status: 'added', headPath: unlistedTarget });\n }\n const changesInTarget = changedFiles.filter(({ status, headPath }) => status !== 'deleted' && isInTarget(headPath));\n files = await attachHunks(measuredFiles, changesInTarget, repoRoot, mergeBase);\n isCovered = (error) => changesInTarget.some(({ headPath }) => error.startsWith(`${headPath}:`));\n }\n return {\n files,\n duplication,\n ...partitionErrors(scan, isCovered),\n root: repoRoot,\n mergeBase,\n };\n}\n\n/** Only a failure on a file the check covers leaves it incomplete; the others are warnings. */\nfunction partitionErrors(\n scan: ScanResult,\n isCovered: (error: string) => boolean\n): Pick<CheckScope, 'errors' | 'warnings'> {\n return {\n errors: scan.errors.filter((error) => isCovered(error)),\n warnings: [...scan.warnings, ...scan.errors.filter((error) => !isCovered(error))],\n };\n}\n\n/** The measured changed files with their line hunks against the merge-base. */\nasync function attachHunks(\n measuredFiles: readonly CheckedFile[],\n changedFiles: readonly ChangedFile[],\n repoRoot: string,\n mergeBase: string\n): Promise<CheckedFile[]> {\n const measuredByPath = new Map(measuredFiles.map((measured) => [measured.file, measured]));\n const files = await mapConcurrently(changedFiles, os.availableParallelism() * 2, async (changed) => {\n const measured = measuredByPath.get(changed.headPath);\n // A changed file the scan left out (unsupported, excluded, or failed) has nothing to check.\n if (measured === undefined) {\n return [];\n }\n const { metrics } = measured;\n const wholeFile: LineHunk = { headStart: 1, headCount: metrics.lines.total };\n const hunks =\n changed.basePath === undefined\n ? [wholeFile]\n : await listLineHunks(repoRoot, mergeBase, changed.basePath, changed.headPath);\n if (metrics.language === 'python' && hunks.some(({ headCount }) => headCount === 0)) {\n anchorDeletionsToCode(hunks, await readFile(path.join(repoRoot, changed.headPath), 'utf8'));\n }\n return [{ ...measured, hunks }];\n });\n return files.flat();\n}\n\n/**\n * Moves each pure deletion up to the last code line before it. A Python function ends at its last\n * statement, so the blank and comment-only lines left above a deleted tail lie outside the span the\n * deletion shortened; anchored to that statement, the deletion counts as touching the function.\n */\nfunction anchorDeletionsToCode(hunks: LineHunk[], content: string): void {\n const lines = content.split(/\\r\\n|\\n|\\r/u);\n for (const hunk of hunks) {\n if (hunk.headCount > 0) continue;\n while (hunk.headStart > 0 && /^\\s*(?:#.*)?$/u.test(lines[hunk.headStart - 1] ?? '')) {\n hunk.headStart -= 1;\n }\n }\n}\n\n/** A run-wide failure (a missing target or native addon) leaves nothing to check. */\nfunction measureDuplication(scan: ScanResult, options: ResolvedOptions): CheckedDuplication {\n if (scan.fatalError) {\n throw new Error(scan.fatalError);\n }\n addCrossFileDuplication(scan, options);\n return {\n crossFile: scan.crossFileDuplication,\n minSimilarityPercent: options.duplication.minSimilarityPercent,\n };\n}\n\nfunction printTextReport(cliOptions: CheckCliOptions, scope: CheckScope, result: CheckResult): void {\n const { violations } = result;\n const checked = `${scope.files.length} ${cliOptions.base === undefined ? '' : 'changed '}files, ${result.checkedFunctionCount} functions checked${describeBase(cliOptions, scope)}`;\n if (scope.errors.length > 0) {\n // Unmeasured files were not checked, so \"no violations\" would be vacuous; never claim a pass.\n writeStdout(\n `Check could not complete: ${scope.errors.length} measurement failures (details on stderr); ${describeCounts(violations)} in the measured files (${checked}).\\n`\n );\n } else if (violations.length === 0) {\n writeStdout(`No threshold violations: ${checked}.\\n`);\n } else {\n writeStdout(`Threshold violations: ${describeCounts(violations)} (${checked}).\\n`);\n }\n for (const violation of violations) {\n writeStdout(`${formatViolation(violation)}\\n`);\n }\n\n const violatedMetrics = new Set(violations.flatMap(({ exceeded }) => exceeded.map(({ metric }) => metric)));\n const violatedThresholds = thresholds.filter((threshold) => violatedMetrics.has(metricNameOf(threshold)));\n if (violatedThresholds.length > 0) {\n writeStdout('\\nHow to fix:\\n');\n for (const { label, hint } of violatedThresholds) {\n writeStdout(`- ${label}: ${hint}\\n`);\n }\n }\n\n for (const warning of scope.warnings) {\n writeStderr(`Warning: ${warning}\\n`);\n }\n for (const error of scope.errors) {\n writeStderr(`Error: ${error}\\n`);\n }\n}\n\nfunction describeBase(cliOptions: CheckCliOptions, scope: CheckScope): string {\n return scope.mergeBase === undefined ? '' : `; base ${cliOptions.base}, merge-base ${scope.mergeBase.slice(0, 12)}`;\n}\n\nfunction describeCounts(violations: readonly Violation[]): string {\n const counts = countViolations(violations);\n return `${counts.errorViolationCount} errors, ${counts.warningViolationCount} warnings (${counts.functionViolationCount} functions, ${counts.fileViolationCount} files, ${counts.duplicationViolationCount} duplicated blocks)`;\n}\n\nfunction countViolations(violations: readonly Violation[]): {\n violationCount: number;\n errorViolationCount: number;\n warningViolationCount: number;\n functionViolationCount: number;\n fileViolationCount: number;\n duplicationViolationCount: number;\n} {\n const count = (kind: Violation['kind']): number => violations.filter((violation) => violation.kind === kind).length;\n const errorViolationCount = violations.filter(({ level }) => level === 'error').length;\n return {\n violationCount: violations.length,\n errorViolationCount,\n warningViolationCount: violations.length - errorViolationCount,\n functionViolationCount: count('function'),\n fileViolationCount: count('file'),\n duplicationViolationCount: count('duplication'),\n };\n}\n\n/** One violation as a single line: its level, the location, then every limit it exceeds. */\nfunction formatViolation(violation: Violation): string {\n const exceeded = violation.exceeded\n .map(({ metric, label, value, level, limit }) => {\n // Halstead values are fractional; rounding up keeps a violating value above the printed maximum.\n const roundedValue = Math.ceil(value * 10) / 10;\n const levelPrefix = level === violation.level ? '' : `${level} `;\n // A duplicated block violates from its limit on, so the largest allowed code-line count is\n // the last whole number below it; a limit of 0 allows none.\n const maxAllowed = violation.kind === 'duplication' ? Math.max(Math.ceil(limit) - 1, 0) : limit;\n const blocks = metric === 'functionCognitiveComplexity' ? describeLargestBlocks(violation) : '';\n return `${label} ${roundedValue} (${levelPrefix}max ${maxAllowed}${blocks})`;\n })\n .join(', ');\n return `${violation.level}: ${describeViolation(violation, exceeded)}`;\n}\n\nfunction describeLargestBlocks({ largestBlocks = [] }: Violation): string {\n const blocks = largestBlocks.map(\n ({ startLine, endLine, name, cognitiveComplexity }) =>\n `L${startLine}${endLine === startLine ? '' : `-${endLine}`}${name ? ` ${toOneLine(name)}` : ''} ${cognitiveComplexity}`\n );\n return blocks.length > 0 ? `; largest parts ${blocks.join(', ')}` : '';\n}\n\nfunction describeViolation(violation: Violation, exceeded: string): string {\n switch (violation.kind) {\n case 'file': {\n return `${violation.file}: ${exceeded}`;\n }\n case 'function': {\n return `${formatLocation(violation)} ${toOneLine(violation.name ?? '')}: ${exceeded}`;\n }\n case 'duplication': {\n const partners = (violation.partners ?? []).map((partner) => formatLocation(partner));\n const listed = [...partners.slice(0, maxListedPartners), ...(partners.length > maxListedPartners ? ['...'] : [])];\n return `${formatLocation(violation)}: ${exceeded}${listed.length > 0 ? `, also at ${listed.join(', ')}` : ''}`;\n }\n }\n}\n\n/** A computed name can span lines in the source; the report keeps one line per function. */\nfunction toOneLine(name: string): string {\n return name.replaceAll(/\\s*[\\n\\r]\\s*/gu, ' ');\n}\n\nfunction formatLocation({ file, startLine, endLine }: BlockLocation): string {\n return `${file}:${startLine}-${endLine}`;\n}\n\nfunction printJsonReport(cliOptions: CheckCliOptions, scope: CheckScope, result: CheckResult): void {\n writeStdout(\n JSON.stringify(\n {\n passed: !hasErrors(result.violations) && scope.errors.length === 0,\n base: cliOptions.base,\n mergeBase: scope.mergeBase,\n summary: {\n ...countViolations(result.violations),\n checkedFileCount: scope.files.length,\n checkedFunctionCount: result.checkedFunctionCount,\n },\n violations: result.violations,\n errors: scope.errors,\n warnings: scope.warnings,\n },\n undefined,\n 2\n ) + '\\n'\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"checkCommand.cjs","names":["resolveTarget","loadConfig","configSearchDirectory","resolveOptions","checkThresholds","resolveLimits","getErrorMessage","mapConcurrently","os","parseAllowDirectives","readFile","path","realpath","stat","getLanguage","resolveRepoRoot","scanTarget","loadExclusion","formatPath","isIgnored","listRepositoryFiles","keepPaths","loadRepositoryExclusion","scanListedFiles","isWithinDirectory","resolveMergeBase","listChangedFiles","listLineHunks","readFileAt","isDirectiveLine","measureCode","thresholds","metricNameOf","require_runtime","require_metrics","require_thresholds","require_allowDirectives","require_cliConfig","require_git","require_exclusion","require_scan","require_thresholdCheck","_willbooster_shared_lib","node_fs_promises","node_os","node_path","runCheckCommand","resolvedTarget","target","config","cliOptions","options","scope","scanCheckScope","files","attachAllowDirectives","result","cliLimits","endFileViolationsAtLastLine","printJsonReport","printTextReport","hasErrors","error","checked","root","violations","level","violation","kind","content","canonicalTarget","targetFile","repoRoot","base","scanDirectoryWalk","scanRepository","searchDirectory","scan","absolutePaths","file","measureDuplication","repositoryFiles","explicitFiles","unlistedTarget","exclusion","targetPath","duplication","isInTarget","relativePath","measuredFiles","isCovered","mergeBase","changedFiles","changesInTarget","status","headPath","attachHunks","partitionErrors","measuredByPath","measured","changed","wholeFile","metrics","hunks","markDeletedDirectives","headCount","anchorDeletionsToCode","candidates","deletedLines","line","baseCodeLines","readBase","hunk","index","lines","isPassedByAnchoring","allowed","describeBase","describeCounts","formatViolation","problem","formatDirectiveProblem","violatedMetrics","exceeded","metric","violatedThresholds","threshold","label","hint","warning","message","counts","countViolations","count","errorViolationCount","value","limit","describeLargestBlocks","describeViolation","blocks","largestBlocks","startLine","endLine","name","toOneLine","cognitiveComplexity","formatLocation","partners","partner","listed"],"sources":["../src/checkCommand.ts"],"sourcesContent":["import { readFile, realpath, stat } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { getErrorMessage, mapConcurrently } from '@willbooster/shared-lib';\n\nimport { isDirectiveLine, parseAllowDirectives } from './allowDirectives.js';\nimport { type CliOptions, loadConfig, type ResolvedOptions, resolveOptions } from './cliConfig.js';\nimport { keepPaths, loadExclusion, loadRepositoryExclusion } from './exclusion.js';\nimport {\n type ChangedFile,\n isIgnored,\n type LineHunk,\n listChangedFiles,\n listLineHunks,\n listRepositoryFiles,\n readFileAt,\n resolveMergeBase,\n resolveRepoRoot,\n} from './git.js';\nimport { measureCode } from './metrics.js';\nimport {\n addCrossFileDuplication,\n configSearchDirectory,\n formatPath,\n getLanguage,\n isWithinDirectory,\n resolveTarget,\n scanListedFiles,\n type ScanResult,\n scanTarget,\n writeStderr,\n writeStdout,\n} from './scan.js';\nimport {\n type BlockLocation,\n type CheckedDuplication,\n type CheckedFile,\n type CheckResult,\n checkThresholds,\n type LocatedDirectiveProblem,\n type Violation,\n} from './thresholdCheck.js';\nimport { type LimitsByLevel, metricNameOf, resolveLimits, thresholds } from './thresholds.js';\n\n/** Raw options of the `check` subcommand; every field is undefined unless the flag was passed. */\nexport interface CheckCliOptions extends CliOptions {\n base?: string;\n}\n\n/** The measured files a check covers and the duplication they take part in. */\nexport interface CheckScope {\n files: CheckedFile[];\n duplication: CheckedDuplication;\n /** Measurement failures of files the check covers. */\n errors: string[];\n warnings: string[];\n /** The directory the files' paths are relative to. */\n root: string;\n mergeBase?: string;\n}\n\n/** Caps how many partner locations a duplicated block lists so the report stays scannable. */\nconst maxListedPartners = 3;\n\n/**\n * Reports every exceeded threshold of the target or, with `base`, of what the working tree changed\n * under it since the merge-base with that ref. Exit codes: 0 no error-level violations, 1 error-level\n * violations, 2 files the check covers could not be measured.\n */\nexport async function runCheckCommand(\n target: string,\n cliOptions: CheckCliOptions,\n cliLimits: LimitsByLevel\n): Promise<void> {\n try {\n const resolvedTarget = resolveTarget(target);\n const config = await loadConfig(cliOptions.config, await configSearchDirectory(resolvedTarget));\n const options = resolveOptions(cliOptions, config);\n const scope = await scanCheckScope(resolvedTarget, cliOptions.base, options);\n const files = await attachAllowDirectives(scope.files, scope.root);\n const result = checkThresholds(files, scope.duplication, resolveLimits(cliLimits, config.config.thresholds));\n await endFileViolationsAtLastLine(result.violations, scope.root);\n\n if (options.json) {\n printJsonReport(cliOptions, scope, result);\n } else {\n printTextReport(cliOptions, scope, result);\n }\n if (scope.errors.length > 0) {\n process.exitCode = 2;\n } else if (hasErrors(result.violations)) {\n process.exitCode = 1;\n }\n } catch (error) {\n writeStderr(`Error: ${getErrorMessage(error)}\\n`);\n process.exitCode = 2;\n }\n}\n\nasync function attachAllowDirectives(files: readonly CheckedFile[], root: string): Promise<CheckedFile[]> {\n return await mapConcurrently(files, os.availableParallelism() * 2, async (checked) => ({\n ...checked,\n allowDirectives: parseAllowDirectives(await readFile(path.join(root, checked.file), 'utf8'), checked.metrics),\n }));\n}\n\nfunction hasErrors(violations: readonly Violation[]): boolean {\n return violations.some(({ level }) => level === 'error');\n}\n\n/**\n * A file's measured line count includes the empty line after a final line terminator, which is\n * not a line of the file, so file-level violations end at the last line that exists.\n */\nasync function endFileViolationsAtLastLine(violations: readonly Violation[], root: string): Promise<void> {\n for (const violation of violations.filter(({ kind }) => kind === 'file')) {\n const content = await readFile(path.join(root, violation.file), 'utf8');\n if (/[\\n\\r]$/u.test(content)) {\n violation.endLine = Math.max(violation.endLine - 1, 1);\n }\n }\n}\n\n/**\n * Inside a git repository, the scope is the repository's git-visible files under the target, and\n * duplication is detected against all of them; `base` only narrows the scope to what changed.\n * Outside a repository, where there is no change to narrow to, the target directory is walked.\n */\nexport async function scanCheckScope(\n resolvedTarget: string,\n base: string | undefined,\n options: ResolvedOptions\n): Promise<CheckScope> {\n const canonicalTarget = await realpath(resolvedTarget);\n const targetStat = await stat(canonicalTarget);\n const targetFile = targetStat.isFile() ? canonicalTarget : undefined;\n if (targetFile && !getLanguage(targetFile, options, true)) {\n throw new Error(`${path.basename(targetFile)}: unsupported file type`);\n }\n let repoRoot;\n try {\n repoRoot = await realpath(await resolveRepoRoot(targetFile ? path.dirname(canonicalTarget) : canonicalTarget));\n } catch (error) {\n if (base !== undefined) {\n throw error;\n }\n return await scanDirectoryWalk(canonicalTarget, options);\n }\n return await scanRepository({ repoRoot, canonicalTarget, targetFile }, base, options);\n}\n\nasync function scanDirectoryWalk(canonicalTarget: string, options: ResolvedOptions): Promise<CheckScope> {\n const searchDirectory = await configSearchDirectory(canonicalTarget);\n const scan = await scanTarget(canonicalTarget, {\n ...options,\n loadExclusion: (absolutePaths) => loadExclusion(searchDirectory, options.exclude, absolutePaths),\n });\n return {\n files: scan.files.map(({ file, metrics }) => ({ file: formatPath(file, scan.displayRoot), metrics })),\n duplication: measureDuplication(scan, options),\n errors: scan.errors,\n warnings: scan.warnings,\n root: scan.displayRoot,\n };\n}\n\ninterface RepositoryTarget {\n repoRoot: string;\n canonicalTarget: string;\n /** Set when the target is a file, which is measured even when git ignores it or it is excluded. */\n targetFile?: string;\n}\n\n/**\n * Measures every git-visible file (tracked or untracked non-ignored) of the repository, so that\n * code copied from outside the target is detected as duplication, and keeps the files under the\n * target: all of them or, with `base`, the changed ones with their line hunks.\n */\nasync function scanRepository(\n { repoRoot, canonicalTarget, targetFile }: RepositoryTarget,\n base: string | undefined,\n options: ResolvedOptions\n): Promise<CheckScope> {\n // Only git-visible files are measured, so a directory git ignores would pass as a check of no\n // files. A directory git lists but whose sources it ignores is not told apart from a repository\n // or package whose only sources are ignored build output, which must pass.\n if (targetFile === undefined && canonicalTarget !== repoRoot && (await isIgnored(repoRoot, canonicalTarget))) {\n throw new Error(\n `${formatPath(canonicalTarget, repoRoot)}: git ignores this directory, so no file in it is checked; target a file to check it regardless`\n );\n }\n const repositoryFiles = await listRepositoryFiles(repoRoot);\n const explicitFiles = new Set(targetFile === undefined ? [] : [targetFile]);\n const unlistedTarget =\n targetFile !== undefined && !repositoryFiles.has(formatPath(targetFile, repoRoot))\n ? formatPath(targetFile, repoRoot)\n : undefined;\n if (unlistedTarget !== undefined) {\n repositoryFiles.add(unlistedTarget);\n }\n const exclusion = keepPaths(await loadRepositoryExclusion(repoRoot, repositoryFiles, options.exclude), explicitFiles);\n // Scan errors start with the file or directory they concern.\n const targetPath = formatPath(canonicalTarget, repoRoot);\n const scan = await scanListedFiles(\n repoRoot,\n repositoryFiles,\n { ...options, loadExclusion: () => Promise.resolve(exclusion) },\n explicitFiles,\n targetFile === undefined && canonicalTarget !== repoRoot ? targetPath : ''\n );\n const duplication = measureDuplication(scan, options);\n const isInTarget = (relativePath: string): boolean =>\n isWithinDirectory(path.join(repoRoot, relativePath), canonicalTarget);\n const measuredFiles = scan.files.map(({ file, metrics }) => ({ file: formatPath(file, repoRoot), metrics }));\n let files = measuredFiles.filter(({ file }) => isInTarget(file));\n let isCovered = (error: string): boolean =>\n canonicalTarget === repoRoot || error.startsWith(`${targetPath}/`) || error.startsWith(`${targetPath}:`);\n let mergeBase;\n if (base !== undefined) {\n mergeBase = await resolveMergeBase(repoRoot, base);\n const changedFiles = await listChangedFiles(repoRoot, mergeBase);\n // A targeted file git does not list has no diff; all of it counts as added.\n if (unlistedTarget !== undefined) {\n changedFiles.push({ status: 'added', headPath: unlistedTarget });\n }\n const changesInTarget = changedFiles.filter(({ status, headPath }) => status !== 'deleted' && isInTarget(headPath));\n files = await attachHunks(measuredFiles, changesInTarget, repoRoot, mergeBase);\n isCovered = (error) => changesInTarget.some(({ headPath }) => error.startsWith(`${headPath}:`));\n }\n return {\n files,\n duplication,\n ...partitionErrors(scan, isCovered),\n root: repoRoot,\n mergeBase,\n };\n}\n\n/** Only a failure on a file the check covers leaves it incomplete; the others are warnings. */\nfunction partitionErrors(\n scan: ScanResult,\n isCovered: (error: string) => boolean\n): Pick<CheckScope, 'errors' | 'warnings'> {\n return {\n errors: scan.errors.filter((error) => isCovered(error)),\n warnings: [...scan.warnings, ...scan.errors.filter((error) => !isCovered(error))],\n };\n}\n\n/** The measured changed files with their line hunks against the merge-base. */\nasync function attachHunks(\n measuredFiles: readonly CheckedFile[],\n changedFiles: readonly ChangedFile[],\n repoRoot: string,\n mergeBase: string\n): Promise<CheckedFile[]> {\n const measuredByPath = new Map(measuredFiles.map((measured) => [measured.file, measured]));\n const files = await mapConcurrently(changedFiles, os.availableParallelism() * 2, async (changed) => {\n const measured = measuredByPath.get(changed.headPath);\n // A changed file the scan left out (unsupported, excluded, or failed) has nothing to check.\n if (measured === undefined) {\n return [];\n }\n const { metrics } = measured;\n const wholeFile: LineHunk = { headStart: 1, headCount: metrics.lines.total };\n const hunks =\n changed.basePath === undefined\n ? [wholeFile]\n : await listLineHunks(repoRoot, mergeBase, changed.basePath, changed.headPath);\n if (changed.basePath !== undefined) {\n await markDeletedDirectives(hunks, metrics.language, () =>\n readFileAt(repoRoot, mergeBase, changed.basePath ?? '')\n );\n }\n if (metrics.language === 'python' && hunks.some(({ headCount }) => headCount === 0)) {\n anchorDeletionsToCode(hunks, await readFile(path.join(repoRoot, changed.headPath), 'utf8'));\n }\n return [{ ...measured, hunks }];\n });\n return files.flat();\n}\n\n/**\n * Marks the hunks deleting a directive. A deleted line reads like one when it holds the directive\n * syntax, but only a comment-only line of the base file is one, not a line of a multi-line string,\n * so the base file is measured when a deleted line reads like one.\n */\nasync function markDeletedDirectives(\n hunks: LineHunk[],\n language: string,\n readBase: () => Promise<string>\n): Promise<void> {\n const candidates = hunks.filter(({ deletedLines }) => deletedLines?.some((line) => isDirectiveLine(line)));\n if (candidates.length === 0) return;\n const baseCodeLines = new Set(measureCode(await readBase(), { language }).codeLineNumbers);\n for (const hunk of candidates) {\n hunk.deletesDirective = (hunk.deletedLines ?? []).some(\n (line, index) => isDirectiveLine(line) && !baseCodeLines.has((hunk.baseStart ?? 0) + index)\n );\n }\n}\n\n/**\n * Moves each pure deletion up to the last code line or directive before it. A Python function ends at its last\n * statement, so the blank and comment-only lines left above a deleted tail lie outside the span the\n * deletion shortened; anchored to that statement, the deletion counts as touching the function.\n */\nfunction anchorDeletionsToCode(hunks: LineHunk[], content: string): void {\n const lines = content.split(/\\r\\n|\\n|\\r/u);\n for (const hunk of hunks) {\n if (hunk.headCount > 0) continue;\n while (hunk.headStart > 0 && isPassedByAnchoring(lines[hunk.headStart - 1] ?? '')) {\n hunk.headStart -= 1;\n }\n }\n}\n\n// A deletion right below a directive stays there, where it may have attached or detached it.\nfunction isPassedByAnchoring(line: string): boolean {\n return /^\\s*(?:#.*)?$/u.test(line) && !isDirectiveLine(line);\n}\n\n/** A run-wide failure (a missing target or native addon) leaves nothing to check. */\nfunction measureDuplication(scan: ScanResult, options: ResolvedOptions): CheckedDuplication {\n if (scan.fatalError) {\n throw new Error(scan.fatalError);\n }\n addCrossFileDuplication(scan, options);\n return {\n crossFile: scan.crossFileDuplication,\n minSimilarityPercent: options.duplication.minSimilarityPercent,\n };\n}\n\nfunction printTextReport(cliOptions: CheckCliOptions, scope: CheckScope, result: CheckResult): void {\n const { violations } = result;\n const allowed =\n result.allowedLimitCount > 0 ? `, ${result.allowedLimitCount} exceeded limits allowed by directives` : '';\n const checked = `${scope.files.length} ${cliOptions.base === undefined ? '' : 'changed '}files, ${result.checkedFunctionCount} functions checked${allowed}${describeBase(cliOptions, scope)}`;\n if (scope.errors.length > 0) {\n // Unmeasured files were not checked, so \"no violations\" would be vacuous; never claim a pass.\n writeStdout(\n `Check could not complete: ${scope.errors.length} measurement failures (details on stderr); ${describeCounts(violations)} in the measured files (${checked}).\\n`\n );\n } else if (violations.length === 0) {\n writeStdout(`No threshold violations: ${checked}.\\n`);\n } else {\n writeStdout(`Threshold violations: ${describeCounts(violations)} (${checked}).\\n`);\n }\n for (const violation of violations) {\n writeStdout(`${formatViolation(violation)}\\n`);\n }\n for (const problem of result.directiveProblems) {\n writeStdout(`${formatDirectiveProblem(problem)}\\n`);\n }\n\n const violatedMetrics = new Set(violations.flatMap(({ exceeded }) => exceeded.map(({ metric }) => metric)));\n const violatedThresholds = thresholds.filter((threshold) => violatedMetrics.has(metricNameOf(threshold)));\n if (violatedThresholds.length > 0) {\n writeStdout('\\nHow to fix:\\n');\n for (const { label, hint } of violatedThresholds) {\n writeStdout(`- ${label}: ${hint}\\n`);\n }\n }\n\n for (const warning of scope.warnings) {\n writeStderr(`Warning: ${warning}\\n`);\n }\n for (const error of scope.errors) {\n writeStderr(`Error: ${error}\\n`);\n }\n}\n\nfunction formatDirectiveProblem({ file, line, message }: LocatedDirectiveProblem): string {\n return `warning: ${file}:${line}: ${message}`;\n}\n\nfunction describeBase(cliOptions: CheckCliOptions, scope: CheckScope): string {\n return scope.mergeBase === undefined ? '' : `; base ${cliOptions.base}, merge-base ${scope.mergeBase.slice(0, 12)}`;\n}\n\nfunction describeCounts(violations: readonly Violation[]): string {\n const counts = countViolations(violations);\n return `${counts.errorViolationCount} errors, ${counts.warningViolationCount} warnings (${counts.functionViolationCount} functions, ${counts.fileViolationCount} files, ${counts.duplicationViolationCount} duplicated blocks)`;\n}\n\nfunction countViolations(violations: readonly Violation[]): {\n violationCount: number;\n errorViolationCount: number;\n warningViolationCount: number;\n functionViolationCount: number;\n fileViolationCount: number;\n duplicationViolationCount: number;\n} {\n const count = (kind: Violation['kind']): number => violations.filter((violation) => violation.kind === kind).length;\n const errorViolationCount = violations.filter(({ level }) => level === 'error').length;\n return {\n violationCount: violations.length,\n errorViolationCount,\n warningViolationCount: violations.length - errorViolationCount,\n functionViolationCount: count('function'),\n fileViolationCount: count('file'),\n duplicationViolationCount: count('duplication'),\n };\n}\n\n/** One violation as a single line: its level, the location, then every limit it exceeds. */\nfunction formatViolation(violation: Violation): string {\n const exceeded = violation.exceeded\n .map(({ metric, label, value, level, limit }) => {\n // Halstead values are fractional; rounding up keeps a violating value above the printed maximum.\n const roundedValue = Math.ceil(value * 10) / 10;\n const levelPrefix = level === violation.level ? '' : `${level} `;\n // A duplicated block violates from its limit on, so the largest allowed code-line count is\n // the last whole number below it; a limit of 0 allows none.\n const maxAllowed = violation.kind === 'duplication' ? Math.max(Math.ceil(limit) - 1, 0) : limit;\n const blocks = metric === 'functionCognitiveComplexity' ? describeLargestBlocks(violation) : '';\n return `${label} ${roundedValue} (${levelPrefix}max ${maxAllowed}${blocks})`;\n })\n .join(', ');\n return `${violation.level}: ${describeViolation(violation, exceeded)}`;\n}\n\nfunction describeLargestBlocks({ largestBlocks = [] }: Violation): string {\n const blocks = largestBlocks.map(\n ({ startLine, endLine, name, cognitiveComplexity }) =>\n `L${startLine}${endLine === startLine ? '' : `-${endLine}`}${name ? ` ${toOneLine(name)}` : ''} ${cognitiveComplexity}`\n );\n return blocks.length > 0 ? `; largest parts ${blocks.join(', ')}` : '';\n}\n\nfunction describeViolation(violation: Violation, exceeded: string): string {\n switch (violation.kind) {\n case 'file': {\n return `${violation.file}: ${exceeded}`;\n }\n case 'function': {\n return `${formatLocation(violation)} ${toOneLine(violation.name ?? '')}: ${exceeded}`;\n }\n case 'duplication': {\n const partners = (violation.partners ?? []).map((partner) => formatLocation(partner));\n const listed = [...partners.slice(0, maxListedPartners), ...(partners.length > maxListedPartners ? ['...'] : [])];\n return `${formatLocation(violation)}: ${exceeded}${listed.length > 0 ? `, also at ${listed.join(', ')}` : ''}`;\n }\n }\n}\n\n/** A computed name can span lines in the source; the report keeps one line per function. */\nfunction toOneLine(name: string): string {\n return name.replaceAll(/\\s*[\\n\\r]\\s*/gu, ' ');\n}\n\nfunction formatLocation({ file, startLine, endLine }: BlockLocation): string {\n return `${file}:${startLine}-${endLine}`;\n}\n\nfunction printJsonReport(cliOptions: CheckCliOptions, scope: CheckScope, result: CheckResult): void {\n writeStdout(\n JSON.stringify(\n {\n passed: !hasErrors(result.violations) && scope.errors.length === 0,\n base: cliOptions.base,\n mergeBase: scope.mergeBase,\n summary: {\n ...countViolations(result.violations),\n checkedFileCount: scope.files.length,\n checkedFunctionCount: result.checkedFunctionCount,\n allowedLimitCount: result.allowedLimitCount,\n },\n violations: result.violations,\n directiveProblems: result.directiveProblems,\n errors: scope.errors,\n warnings: scope.warnings,\n },\n undefined,\n 2\n ) + '\\n'\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { type CliOptions, type ResolvedOptions } from './cliConfig.js';
2
- import { type CheckedFile, type CheckedDuplication } from './thresholdCheck.js';
2
+ import { type CheckedDuplication, type CheckedFile } from './thresholdCheck.js';
3
3
  import { type LimitsByLevel } from './thresholds.js';
4
4
  /** Raw options of the `check` subcommand; every field is undefined unless the flag was passed. */
5
5
  export interface CheckCliOptions extends CliOptions {