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.
Files changed (42) hide show
  1. package/README.md +2 -2
  2. package/THIRD-PARTY-NOTICES.txt +116 -74
  3. package/dist/checkCommand.cjs.map +1 -1
  4. package/dist/checkCommand.js.map +1 -1
  5. package/dist/cli.cjs.map +1 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/cliConfig.cjs.map +1 -1
  8. package/dist/cliConfig.js.map +1 -1
  9. package/dist/crossFileDuplication.cjs.map +1 -1
  10. package/dist/crossFileDuplication.js.map +1 -1
  11. package/dist/crossFileNearMiss.cjs.map +1 -1
  12. package/dist/crossFileNearMiss.js.map +1 -1
  13. package/dist/duplicateSelection.cjs.map +1 -1
  14. package/dist/duplicateSelection.js.map +1 -1
  15. package/dist/duplication.cjs.map +1 -1
  16. package/dist/duplication.js.map +1 -1
  17. package/dist/exclusion.cjs.map +1 -1
  18. package/dist/exclusion.js.map +1 -1
  19. package/dist/git.cjs.map +1 -1
  20. package/dist/git.js.map +1 -1
  21. package/dist/languages.cjs.map +1 -1
  22. package/dist/languages.js.map +1 -1
  23. package/dist/metrics.cjs.map +1 -1
  24. package/dist/metrics.js.map +1 -1
  25. package/dist/nativeMetrics.cjs.map +1 -1
  26. package/dist/nativeMetrics.js.map +1 -1
  27. package/dist/scan.cjs.map +1 -1
  28. package/dist/scan.js.map +1 -1
  29. package/dist/thresholdCheck.cjs.map +1 -1
  30. package/dist/thresholdCheck.js.map +1 -1
  31. package/dist/thresholds.cjs.map +1 -1
  32. package/dist/thresholds.js.map +1 -1
  33. package/dist/wasmBinding.cjs +1 -1
  34. package/dist/wasmBinding.cjs.map +1 -1
  35. package/dist/wasmBinding.js +1 -1
  36. package/dist/wasmBinding.js.map +1 -1
  37. package/dist/worker.cjs.map +1 -1
  38. package/dist/worker.js.map +1 -1
  39. package/native/Cargo.lock +58 -58
  40. package/native/Cargo.toml +15 -13
  41. package/native/code-gauge.wasm +0 -0
  42. package/package.json +14 -14
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Test](https://github.com/WillBooster/code-gauge/actions/workflows/test.yml/badge.svg)](https://github.com/WillBooster/code-gauge/actions/workflows/test.yml)
6
6
  [![Test rust](https://github.com/WillBooster/code-gauge/actions/workflows/test-rust.yml/badge.svg)](https://github.com/WillBooster/code-gauge/actions/workflows/test-rust.yml)
7
7
  [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
8
- [![wbfy](https://img.shields.io/badge/wbfy-20.23.1-1e90ff.svg)](https://github.com/WillBooster/shared/tree/main/packages/wbfy)
8
+ [![wbfy](https://img.shields.io/badge/wbfy-20.36.11-1e90ff.svg)](https://github.com/WillBooster/shared/tree/main/packages/wbfy)
9
9
 
10
10
  A command-line tool that ranks the files of a project by refactoring priority and checks code
11
11
  against metric thresholds, built for AI-agent workflows: an agent asked to "refactor this
@@ -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.3 MB gzipped). Wrangler resolves that export
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
@@ -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 1.4.3 — MIT — https://github.com/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 4.0.2 — MIT — https://github.com/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 3.0.1 — MIT — https://github.com/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 2.0.2 — MIT — https://github.com/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-javascript 4.0.2 — MIT — https://github.com/WillBooster/tree-sitter-javascript
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 4.0.2 — MIT — https://github.com/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-rust 2.0.2 — MIT — https://github.com/WillBooster/tree-sitter-rust
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 7.0.0 — MIT — https://github.com/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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"}
@@ -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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"}