code-gauge 10.2.2 → 10.2.4
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.
- package/README.md +2 -2
- package/THIRD-PARTY-NOTICES.txt +116 -74
- package/dist/checkCommand.cjs.map +1 -1
- package/dist/checkCommand.js.map +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/cliConfig.cjs.map +1 -1
- package/dist/cliConfig.js.map +1 -1
- package/dist/crossFileDuplication.cjs.map +1 -1
- package/dist/crossFileDuplication.js.map +1 -1
- package/dist/crossFileNearMiss.cjs.map +1 -1
- package/dist/crossFileNearMiss.js.map +1 -1
- package/dist/duplicateSelection.cjs.map +1 -1
- package/dist/duplicateSelection.js.map +1 -1
- package/dist/duplication.cjs.map +1 -1
- package/dist/duplication.js.map +1 -1
- package/dist/exclusion.cjs.map +1 -1
- package/dist/exclusion.js.map +1 -1
- package/dist/git.cjs.map +1 -1
- package/dist/git.js.map +1 -1
- package/dist/languages.cjs.map +1 -1
- package/dist/languages.js.map +1 -1
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.js.map +1 -1
- package/dist/nativeMetrics.cjs.map +1 -1
- package/dist/nativeMetrics.js.map +1 -1
- package/dist/scan.cjs.map +1 -1
- package/dist/scan.js.map +1 -1
- package/dist/thresholdCheck.cjs.map +1 -1
- package/dist/thresholdCheck.js.map +1 -1
- package/dist/thresholds.cjs.map +1 -1
- package/dist/thresholds.js.map +1 -1
- package/dist/wasmBinding.cjs +1 -1
- package/dist/wasmBinding.cjs.map +1 -1
- package/dist/wasmBinding.js +1 -1
- package/dist/wasmBinding.js.map +1 -1
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.js.map +1 -1
- package/native/Cargo.lock +58 -58
- package/native/Cargo.toml +15 -13
- package/native/code-gauge.wasm +0 -0
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/WillBooster/code-gauge/actions/workflows/test.yml)
|
|
6
6
|
[](https://github.com/WillBooster/code-gauge/actions/workflows/test-rust.yml)
|
|
7
7
|
[](https://github.com/semantic-release/semantic-release)
|
|
8
|
-
[](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
|
|
@@ -511,7 +511,7 @@ does not skip generated files (e.g. `.d.ts`, `.min.js`) or test files.
|
|
|
511
511
|
### Cloudflare Workers
|
|
512
512
|
|
|
513
513
|
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 2
|
|
514
|
+
WebAssembly build of the Rust engine (about 3.2 MB gzipped). Wrangler resolves that export
|
|
515
515
|
automatically, and no `nodejs_compat` flag is needed:
|
|
516
516
|
|
|
517
517
|
```ts
|
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -7859,39 +7859,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
|
7859
7859
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
7860
7860
|
DEALINGS IN THE SOFTWARE.
|
|
7861
7861
|
|
|
7862
|
-
====================================================================================================
|
|
7863
|
-
tree-sitter-go 0.25.0 — MIT — https://github.com/tree-sitter/tree-sitter-go
|
|
7864
|
-
====================================================================================================
|
|
7865
|
-
----- LICENSE -----
|
|
7866
|
-
The MIT License (MIT)
|
|
7867
|
-
|
|
7868
|
-
Copyright (c) 2014 Max Brunsfeld
|
|
7869
|
-
|
|
7870
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7871
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7872
|
-
in the Software without restriction, including without limitation the rights
|
|
7873
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7874
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
7875
|
-
furnished to do so, subject to the following conditions:
|
|
7876
|
-
|
|
7877
|
-
The above copyright notice and this permission notice shall be included in all
|
|
7878
|
-
copies or substantial portions of the Software.
|
|
7879
|
-
|
|
7880
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
7881
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
7882
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
7883
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
7884
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
7885
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
7886
|
-
SOFTWARE.
|
|
7887
|
-
|
|
7888
|
-
====================================================================================================
|
|
7889
|
-
tree-sitter-java 0.23.5 — MIT — https://github.com/tree-sitter/tree-sitter-java
|
|
7890
|
-
====================================================================================================
|
|
7891
|
-
This crate's published sources bundle no root license file; the upstream notice is:
|
|
7892
|
-
Copyright (c) 2017 Ayman Nadeem
|
|
7893
|
-
The canonical text of the MIT license is reproduced in the appendix at the end of this document.
|
|
7894
|
-
|
|
7895
7862
|
====================================================================================================
|
|
7896
7863
|
tree-sitter-language 0.1.8 — MIT — https://github.com/tree-sitter/tree-sitter
|
|
7897
7864
|
====================================================================================================
|
|
@@ -7918,39 +7885,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
7918
7885
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
7919
7886
|
SOFTWARE.
|
|
7920
7887
|
|
|
7921
|
-
====================================================================================================
|
|
7922
|
-
tree-sitter-python 0.25.0 — MIT — https://github.com/tree-sitter/tree-sitter-python
|
|
7923
|
-
====================================================================================================
|
|
7924
|
-
----- LICENSE -----
|
|
7925
|
-
The MIT License (MIT)
|
|
7926
|
-
|
|
7927
|
-
Copyright (c) 2016 Max Brunsfeld
|
|
7928
|
-
|
|
7929
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7930
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7931
|
-
in the Software without restriction, including without limitation the rights
|
|
7932
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7933
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
7934
|
-
furnished to do so, subject to the following conditions:
|
|
7935
|
-
|
|
7936
|
-
The above copyright notice and this permission notice shall be included in all
|
|
7937
|
-
copies or substantial portions of the Software.
|
|
7938
|
-
|
|
7939
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
7940
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
7941
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
7942
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
7943
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
7944
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
7945
|
-
SOFTWARE.
|
|
7946
|
-
|
|
7947
|
-
====================================================================================================
|
|
7948
|
-
tree-sitter-ruby 0.23.1 — MIT — https://github.com/tree-sitter/tree-sitter-ruby
|
|
7949
|
-
====================================================================================================
|
|
7950
|
-
This crate's published sources bundle no root license file; the upstream notice is:
|
|
7951
|
-
Copyright (c) 2016 Rob Rix
|
|
7952
|
-
The canonical text of the MIT license is reproduced in the appendix at the end of this document.
|
|
7953
|
-
|
|
7954
7888
|
====================================================================================================
|
|
7955
7889
|
unicode-ident 1.0.24 — (MIT OR Apache-2.0) AND Unicode-3.0 — https://github.com/dtolnay/unicode-ident
|
|
7956
7890
|
====================================================================================================
|
|
@@ -8432,7 +8366,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
8432
8366
|
DEALINGS IN THE SOFTWARE.
|
|
8433
8367
|
|
|
8434
8368
|
====================================================================================================
|
|
8435
|
-
willbooster-tree-sitter
|
|
8369
|
+
willbooster-tree-sitter 2.0.1 — MIT — https://github.com/WillBooster/tree-sitter
|
|
8436
8370
|
====================================================================================================
|
|
8437
8371
|
----- LICENSE -----
|
|
8438
8372
|
The MIT License (MIT)
|
|
@@ -9070,7 +9004,7 @@ permissive licensing, and of not having licensing issues being an
|
|
|
9070
9004
|
obstacle to adoption, that text has been removed.
|
|
9071
9005
|
|
|
9072
9006
|
====================================================================================================
|
|
9073
|
-
willbooster-tree-sitter-c
|
|
9007
|
+
willbooster-tree-sitter-c 5.0.0 — MIT — https://github.com/WillBooster/tree-sitter-c
|
|
9074
9008
|
====================================================================================================
|
|
9075
9009
|
----- LICENSE -----
|
|
9076
9010
|
The MIT License (MIT)
|
|
@@ -9097,7 +9031,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
9097
9031
|
SOFTWARE.
|
|
9098
9032
|
|
|
9099
9033
|
====================================================================================================
|
|
9100
|
-
willbooster-tree-sitter-c-sharp
|
|
9034
|
+
willbooster-tree-sitter-c-sharp 4.0.0 — MIT — https://github.com/WillBooster/tree-sitter-c-sharp
|
|
9101
9035
|
====================================================================================================
|
|
9102
9036
|
----- LICENSE -----
|
|
9103
9037
|
The MIT License (MIT)
|
|
@@ -9124,7 +9058,34 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
9124
9058
|
SOFTWARE.
|
|
9125
9059
|
|
|
9126
9060
|
====================================================================================================
|
|
9127
|
-
willbooster-tree-sitter-cpp
|
|
9061
|
+
willbooster-tree-sitter-cpp 3.0.0 — MIT — https://github.com/WillBooster/tree-sitter-cpp
|
|
9062
|
+
====================================================================================================
|
|
9063
|
+
----- LICENSE -----
|
|
9064
|
+
The MIT License (MIT)
|
|
9065
|
+
|
|
9066
|
+
Copyright (c) 2014 Max Brunsfeld
|
|
9067
|
+
Copyright (c) 2026 WillBooster Inc.
|
|
9068
|
+
|
|
9069
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9070
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9071
|
+
in the Software without restriction, including without limitation the rights
|
|
9072
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9073
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9074
|
+
furnished to do so, subject to the following conditions:
|
|
9075
|
+
|
|
9076
|
+
The above copyright notice and this permission notice shall be included in all
|
|
9077
|
+
copies or substantial portions of the Software.
|
|
9078
|
+
|
|
9079
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
9080
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
9081
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
9082
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
9083
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
9084
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
9085
|
+
SOFTWARE.
|
|
9086
|
+
|
|
9087
|
+
====================================================================================================
|
|
9088
|
+
willbooster-tree-sitter-go 2.0.0 — MIT — https://github.com/WillBooster/tree-sitter-go
|
|
9128
9089
|
====================================================================================================
|
|
9129
9090
|
----- LICENSE -----
|
|
9130
9091
|
The MIT License (MIT)
|
|
@@ -9151,7 +9112,34 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
9151
9112
|
SOFTWARE.
|
|
9152
9113
|
|
|
9153
9114
|
====================================================================================================
|
|
9154
|
-
willbooster-tree-sitter-
|
|
9115
|
+
willbooster-tree-sitter-java 2.0.0 — MIT — https://github.com/WillBooster/tree-sitter-java
|
|
9116
|
+
====================================================================================================
|
|
9117
|
+
----- LICENSE -----
|
|
9118
|
+
MIT License
|
|
9119
|
+
|
|
9120
|
+
Copyright (c) 2017 Ayman Nadeem
|
|
9121
|
+
Copyright (c) 2026 WillBooster Inc.
|
|
9122
|
+
|
|
9123
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9124
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9125
|
+
in the Software without restriction, including without limitation the rights
|
|
9126
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9127
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9128
|
+
furnished to do so, subject to the following conditions:
|
|
9129
|
+
|
|
9130
|
+
The above copyright notice and this permission notice shall be included in all
|
|
9131
|
+
copies or substantial portions of the Software.
|
|
9132
|
+
|
|
9133
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
9134
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
9135
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
9136
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
9137
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
9138
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
9139
|
+
SOFTWARE.
|
|
9140
|
+
|
|
9141
|
+
====================================================================================================
|
|
9142
|
+
willbooster-tree-sitter-javascript 5.0.0 — MIT — https://github.com/WillBooster/tree-sitter-javascript
|
|
9155
9143
|
====================================================================================================
|
|
9156
9144
|
----- LICENSE -----
|
|
9157
9145
|
The MIT License (MIT)
|
|
@@ -9178,7 +9166,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
9178
9166
|
SOFTWARE.
|
|
9179
9167
|
|
|
9180
9168
|
====================================================================================================
|
|
9181
|
-
willbooster-tree-sitter-kotlin
|
|
9169
|
+
willbooster-tree-sitter-kotlin 5.0.0 — MIT — https://github.com/WillBooster/tree-sitter-kotlin
|
|
9182
9170
|
====================================================================================================
|
|
9183
9171
|
----- LICENSE -----
|
|
9184
9172
|
The MIT License (MIT)
|
|
@@ -9205,7 +9193,61 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
9205
9193
|
SOFTWARE.
|
|
9206
9194
|
|
|
9207
9195
|
====================================================================================================
|
|
9208
|
-
willbooster-tree-sitter-
|
|
9196
|
+
willbooster-tree-sitter-python 2.0.0 — MIT — https://github.com/WillBooster/tree-sitter-python
|
|
9197
|
+
====================================================================================================
|
|
9198
|
+
----- LICENSE -----
|
|
9199
|
+
The MIT License (MIT)
|
|
9200
|
+
|
|
9201
|
+
Copyright (c) 2016 Max Brunsfeld
|
|
9202
|
+
Copyright (c) 2026 WillBooster Inc.
|
|
9203
|
+
|
|
9204
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9205
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9206
|
+
in the Software without restriction, including without limitation the rights
|
|
9207
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9208
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9209
|
+
furnished to do so, subject to the following conditions:
|
|
9210
|
+
|
|
9211
|
+
The above copyright notice and this permission notice shall be included in all
|
|
9212
|
+
copies or substantial portions of the Software.
|
|
9213
|
+
|
|
9214
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
9215
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
9216
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
9217
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
9218
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
9219
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
9220
|
+
SOFTWARE.
|
|
9221
|
+
|
|
9222
|
+
====================================================================================================
|
|
9223
|
+
willbooster-tree-sitter-ruby 2.0.0 — MIT — https://github.com/WillBooster/tree-sitter-ruby
|
|
9224
|
+
====================================================================================================
|
|
9225
|
+
----- LICENSE -----
|
|
9226
|
+
The MIT License (MIT)
|
|
9227
|
+
|
|
9228
|
+
Copyright (c) 2016 Rob Rix
|
|
9229
|
+
Copyright (c) 2026 WillBooster Inc.
|
|
9230
|
+
|
|
9231
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9232
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9233
|
+
in the Software without restriction, including without limitation the rights
|
|
9234
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9235
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9236
|
+
furnished to do so, subject to the following conditions:
|
|
9237
|
+
|
|
9238
|
+
The above copyright notice and this permission notice shall be included in all
|
|
9239
|
+
copies or substantial portions of the Software.
|
|
9240
|
+
|
|
9241
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
9242
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
9243
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
9244
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
9245
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
9246
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
9247
|
+
SOFTWARE.
|
|
9248
|
+
|
|
9249
|
+
====================================================================================================
|
|
9250
|
+
willbooster-tree-sitter-rust 3.0.0 — MIT — https://github.com/WillBooster/tree-sitter-rust
|
|
9209
9251
|
====================================================================================================
|
|
9210
9252
|
----- LICENSE -----
|
|
9211
9253
|
The MIT License (MIT)
|
|
@@ -9232,7 +9274,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
9232
9274
|
SOFTWARE.
|
|
9233
9275
|
|
|
9234
9276
|
====================================================================================================
|
|
9235
|
-
willbooster-tree-sitter-typescript
|
|
9277
|
+
willbooster-tree-sitter-typescript 8.0.0 — MIT — https://github.com/WillBooster/tree-sitter-typescript
|
|
9236
9278
|
====================================================================================================
|
|
9237
9279
|
----- LICENSE -----
|
|
9238
9280
|
The MIT License (MIT)
|
|
@@ -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"],"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","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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"}
|
package/dist/checkCommand.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkCommand.js","names":[],"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.js","names":["path","os","runCheckCommand","resolvedTarget","resolveTarget","target","config","loadConfig","cliOptions","configSearchDirectory","options","resolveOptions","scope","scanCheckScope","result","checkThresholds","resolveLimits","cliLimits","endFileViolationsAtLastLine","printJsonReport","printTextReport","hasErrors","error","writeStderr","getErrorMessage","violations","level","violation","kind","content","readFile","root","canonicalTarget","realpath","targetFile","stat","getLanguage","repoRoot","resolveRepoRoot","base","scanDirectoryWalk","scanRepository","searchDirectory","scan","scanTarget","loadExclusion","absolutePaths","formatPath","file","measureDuplication","isIgnored","repositoryFiles","listRepositoryFiles","explicitFiles","unlistedTarget","exclusion","keepPaths","loadRepositoryExclusion","targetPath","scanListedFiles","duplication","isInTarget","isWithinDirectory","relativePath","measuredFiles","files","isCovered","mergeBase","resolveMergeBase","changedFiles","listChangedFiles","changesInTarget","status","headPath","attachHunks","partitionErrors","measuredByPath","measured","mapConcurrently","changed","wholeFile","metrics","hunks","listLineHunks","headCount","anchorDeletionsToCode","lines","hunk","addCrossFileDuplication","checked","describeBase","writeStdout","describeCounts","formatViolation","violatedMetrics","exceeded","metric","violatedThresholds","thresholds","metricNameOf","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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"}
|