code-gauge 5.1.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +204 -72
  2. package/dist/checkCommand.cjs +5 -0
  3. package/dist/checkCommand.cjs.map +1 -0
  4. package/dist/checkCommand.d.ts +12 -0
  5. package/dist/checkCommand.js +5 -0
  6. package/dist/checkCommand.js.map +1 -0
  7. package/dist/cli.cjs +3 -3
  8. package/dist/cli.cjs.map +1 -1
  9. package/dist/cli.js +3 -3
  10. package/dist/cli.js.map +1 -1
  11. package/dist/cliConfig.cjs +1 -1
  12. package/dist/cliConfig.cjs.map +1 -1
  13. package/dist/cliConfig.d.ts +3 -11
  14. package/dist/cliConfig.js +1 -1
  15. package/dist/cliConfig.js.map +1 -1
  16. package/dist/exclusion.cjs +1 -1
  17. package/dist/exclusion.cjs.map +1 -1
  18. package/dist/exclusion.d.ts +2 -2
  19. package/dist/exclusion.js +1 -1
  20. package/dist/exclusion.js.map +1 -1
  21. package/dist/git.cjs +1 -1
  22. package/dist/git.cjs.map +1 -1
  23. package/dist/git.d.ts +13 -12
  24. package/dist/git.js +1 -1
  25. package/dist/git.js.map +1 -1
  26. package/dist/index.cjs +1 -1
  27. package/dist/index.d.ts +0 -4
  28. package/dist/index.js +1 -1
  29. package/dist/metrics.cjs +1 -1
  30. package/dist/metrics.cjs.map +1 -1
  31. package/dist/metrics.d.ts +1 -2
  32. package/dist/metrics.js +1 -1
  33. package/dist/metrics.js.map +1 -1
  34. package/dist/scan.cjs +1 -1
  35. package/dist/scan.cjs.map +1 -1
  36. package/dist/scan.d.ts +10 -25
  37. package/dist/scan.js +1 -1
  38. package/dist/scan.js.map +1 -1
  39. package/dist/thresholdCheck.cjs +2 -0
  40. package/dist/thresholdCheck.cjs.map +1 -0
  41. package/dist/thresholdCheck.d.ts +36 -0
  42. package/dist/thresholdCheck.js +2 -0
  43. package/dist/thresholdCheck.js.map +1 -0
  44. package/dist/thresholds.cjs +2 -0
  45. package/dist/thresholds.cjs.map +1 -0
  46. package/dist/thresholds.d.ts +38 -0
  47. package/dist/thresholds.js +2 -0
  48. package/dist/thresholds.js.map +1 -0
  49. package/dist/types.d.ts +8 -4
  50. package/dist/worker.cjs +1 -1
  51. package/dist/worker.cjs.map +1 -1
  52. package/dist/worker.js +1 -1
  53. package/dist/worker.js.map +1 -1
  54. package/package.json +9 -9
  55. package/dist/diffCommand.cjs +0 -5
  56. package/dist/diffCommand.cjs.map +0 -1
  57. package/dist/diffCommand.d.ts +0 -17
  58. package/dist/diffCommand.js +0 -5
  59. package/dist/diffCommand.js.map +0 -1
  60. package/dist/newDuplication.cjs +0 -2
  61. package/dist/newDuplication.cjs.map +0 -1
  62. package/dist/newDuplication.d.ts +0 -40
  63. package/dist/newDuplication.js +0 -2
  64. package/dist/newDuplication.js.map +0 -1
  65. package/dist/regressionGate.cjs +0 -2
  66. package/dist/regressionGate.cjs.map +0 -1
  67. package/dist/regressionGate.d.ts +0 -108
  68. package/dist/regressionGate.js +0 -2
  69. package/dist/regressionGate.js.map +0 -1
@@ -25,6 +25,6 @@ export declare function keepPaths(exclusion: Exclusion, keptPaths: ReadonlySet<s
25
25
  export declare function loadExclusion(directory: string, excludePatterns: ExcludePatterns, absolutePaths: readonly string[]): Promise<Exclusion>;
26
26
  /**
27
27
  * The exclusion for repository-relative `paths` of the canonical `repoRoot`, with git attributes
28
- * from the working tree or, with `source`, from that revision.
28
+ * from the working tree.
29
29
  */
30
- export declare function loadRepositoryExclusion(repoRoot: string, paths: Iterable<string>, excludePatterns: ExcludePatterns, source?: string): Promise<Exclusion>;
30
+ export declare function loadRepositoryExclusion(repoRoot: string, paths: Iterable<string>, excludePatterns: ExcludePatterns): Promise<Exclusion>;
package/dist/exclusion.js CHANGED
@@ -1,2 +1,2 @@
1
- import{readLinguistAttributes as e,resolveRepoRoot as t}from"./git.js";import{realpath as n}from"node:fs/promises";import r from"node:path";const i=/(?:^|[^\w@])@generated\b/u,a=/\bgenerated\b.*\bdo not (?:edit|modify)\b/iu;function o(e,t=/* @__PURE__ */ new Map){return{isExcludedPath(n){let i=t.get(n);if(i?.generated===!0||i?.vendored===!0)return!0;let a=r.relative(e.root,n),o=a.split(r.sep).join(`/`),s=o===`..`||o.startsWith(`../`)||r.isAbsolute(a);return e.patterns.some(e=>(!s||e.startsWith(`..`))&&r.posix.matchesGlob(o,e))},isGeneratedCode(e,n){return t.get(e)?.generated!==!1&&n.split(/\r\n|\n|\r/u,5).some(e=>i.test(e)||a.test(e))}}}function s(e,t){return{isExcludedPath:n=>!t.has(n)&&e.isExcludedPath(n),isGeneratedCode:(n,r)=>!t.has(n)&&e.isGeneratedCode(n,r)}}async function c(e,i,a){let s;try{s=await n(await t(e))}catch{return o(await u(i))}let c=a.map(e=>r.relative(s,e).split(r.sep).join(`/`)).filter(e=>e!==`..`&&!e.startsWith(`../`));return await l(s,c,i)}async function l(t,n,i,a){let s=await e(t,n,a);return o(await u(i),new Map([...s].map(([e,n])=>[r.join(t,e),n])))}async function u(e){return{...e,root:await n(e.root).catch(()=>e.root)}}export{o as createExclusion,s as keepPaths,c as loadExclusion,l as loadRepositoryExclusion};
1
+ import{readLinguistAttributes as e,resolveRepoRoot as t}from"./git.js";import{realpath as n}from"node:fs/promises";import r from"node:path";const i=/(?:^|[^\w@])@generated\b/u,a=/\bgenerated\b.*\bdo not (?:edit|modify)\b/iu;function o(e,t=/* @__PURE__ */ new Map){return{isExcludedPath(n){let i=t.get(n);if(i?.generated===!0||i?.vendored===!0)return!0;let a=r.relative(e.root,n),o=a.split(r.sep).join(`/`),s=o===`..`||o.startsWith(`../`)||r.isAbsolute(a);return e.patterns.some(e=>(!s||e.startsWith(`..`))&&r.posix.matchesGlob(o,e))},isGeneratedCode(e,n){return t.get(e)?.generated!==!1&&n.split(/\r\n|\n|\r/u,5).some(e=>i.test(e)||a.test(e))}}}function s(e,t){return{isExcludedPath:n=>!t.has(n)&&e.isExcludedPath(n),isGeneratedCode:(n,r)=>!t.has(n)&&e.isGeneratedCode(n,r)}}async function c(e,i,a){let s;try{s=await n(await t(e))}catch{return o(await u(i))}let c=a.map(e=>r.relative(s,e).split(r.sep).join(`/`)).filter(e=>e!==`..`&&!e.startsWith(`../`));return await l(s,c,i)}async function l(t,n,i){let a=await e(t,n);return o(await u(i),new Map([...a].map(([e,n])=>[r.join(t,e),n])))}async function u(e){return{...e,root:await n(e.root).catch(()=>e.root)}}export{o as createExclusion,s as keepPaths,c as loadExclusion,l as loadRepositoryExclusion};
2
2
  //# sourceMappingURL=exclusion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"exclusion.js","names":[],"sources":["../src/exclusion.ts"],"sourcesContent":["import { realpath } from 'node:fs/promises';\nimport path from 'node:path';\nimport { readLinguistAttributes, resolveRepoRoot, type LinguistAttributes } from './git.js';\n\n/**\n * Files left out of measurement beyond the built-in directory and file-name rules: configured\n * glob patterns, files git attributes mark `linguist-generated` or `linguist-vendored`, and files\n * whose header carries a generated-code marker. All paths are absolute.\n */\nexport interface Exclusion {\n isExcludedPath(absolutePath: string): boolean;\n /** Whether the content is generated code, unless git attributes mark the file `-linguist-generated`. */\n isGeneratedCode(absolutePath: string, code: string): boolean;\n}\n\n/** Glob patterns relative to `root` (the directory of the config file that declared them). */\nexport interface ExcludePatterns {\n patterns: string[];\n root: string;\n}\n\nconst generatedMarkerLineCount = 5;\n// Meta's `@generated` convention (used by tree-sitter, Relay, and Buck) and the wording of Go's\n// `// Code generated ... DO NOT EDIT.` convention, which protoc, Prisma, and others share.\nconst generatedTagPattern = /(?:^|[^\\w@])@generated\\b/u;\nconst doNotEditPattern = /\\bgenerated\\b.*\\bdo not (?:edit|modify)\\b/iu;\n\nexport function createExclusion(\n excludePatterns: ExcludePatterns,\n attributesByPath: ReadonlyMap<string, LinguistAttributes> = new Map()\n): Exclusion {\n return {\n isExcludedPath(absolutePath) {\n const attributes = attributesByPath.get(absolutePath);\n if (attributes?.generated === true || attributes?.vendored === true) {\n return true;\n }\n const nativeRelativePath = path.relative(excludePatterns.root, absolutePath);\n const relativePath = nativeRelativePath.split(path.sep).join('/');\n // Only a pattern spelling out `..` reaches outside the root: whether `*` and `**` match `..`\n // segments differs between runtimes (Node's matchesGlob says no, Bun's says yes). A path on\n // another Windows drive has no relative form, so path.relative returns it absolute.\n const outsideRoot =\n relativePath === '..' || relativePath.startsWith('../') || path.isAbsolute(nativeRelativePath);\n return excludePatterns.patterns.some(\n (pattern) => (!outsideRoot || pattern.startsWith('..')) && path.posix.matchesGlob(relativePath, pattern)\n );\n },\n isGeneratedCode(absolutePath, code) {\n if (attributesByPath.get(absolutePath)?.generated === false) {\n return false;\n }\n return code\n .split(/\\r\\n|\\n|\\r/u, generatedMarkerLineCount)\n .some((line) => generatedTagPattern.test(line) || doNotEditPattern.test(line));\n },\n };\n}\n\n/** The exclusion that never excludes the given absolute paths (explicitly targeted files). */\nexport function keepPaths(exclusion: Exclusion, keptPaths: ReadonlySet<string>): Exclusion {\n return {\n isExcludedPath: (absolutePath) => !keptPaths.has(absolutePath) && exclusion.isExcludedPath(absolutePath),\n isGeneratedCode: (absolutePath, code) =>\n !keptPaths.has(absolutePath) && exclusion.isGeneratedCode(absolutePath, code),\n };\n}\n\n/**\n * The exclusion for the given absolute paths found under `directory`, with git attributes from\n * the enclosing repository (which apply to ignored files too); outside any repository only\n * patterns and markers apply.\n */\nexport async function loadExclusion(\n directory: string,\n excludePatterns: ExcludePatterns,\n absolutePaths: readonly string[]\n): Promise<Exclusion> {\n let repoRoot;\n try {\n repoRoot = await realpath(await resolveRepoRoot(directory));\n } catch {\n return createExclusion(await canonicalizeRoot(excludePatterns));\n }\n const relativePaths = absolutePaths\n .map((absolutePath) => path.relative(repoRoot, absolutePath).split(path.sep).join('/'))\n .filter((relativePath) => relativePath !== '..' && !relativePath.startsWith('../'));\n return await loadRepositoryExclusion(repoRoot, relativePaths, excludePatterns);\n}\n\n/**\n * The exclusion for repository-relative `paths` of the canonical `repoRoot`, with git attributes\n * from the working tree or, with `source`, from that revision.\n */\nexport async function loadRepositoryExclusion(\n repoRoot: string,\n paths: Iterable<string>,\n excludePatterns: ExcludePatterns,\n source?: string\n): Promise<Exclusion> {\n const attributes = await readLinguistAttributes(repoRoot, paths, source);\n return createExclusion(\n await canonicalizeRoot(excludePatterns),\n new Map([...attributes].map(([relativePath, value]) => [path.join(repoRoot, relativePath), value]))\n );\n}\n\n/** Scanned paths are canonical, so a pattern root reached through a symbolic link must be too. */\nasync function canonicalizeRoot(excludePatterns: ExcludePatterns): Promise<ExcludePatterns> {\n return { ...excludePatterns, root: await realpath(excludePatterns.root).catch(() => excludePatterns.root) };\n}\n"],"mappings":"4IAqBA,MAGM,EAAsB,4BACtB,EAAmB,8CAEzB,SAAgB,EACd,EACA,kBAA4D,IAAI,IACrD,CACX,MAAO,CACL,eAAe,EAAc,CAC3B,IAAM,EAAa,EAAiB,IAAI,CAAY,EACpD,GAAI,GAAY,YAAc,IAAQ,GAAY,WAAa,GAC7D,MAAO,GAET,IAAM,EAAqB,EAAK,SAAS,EAAgB,KAAM,CAAY,EACrE,EAAe,EAAmB,MAAM,EAAK,GAAG,CAAC,CAAC,KAAK,GAAG,EAI1D,EACJ,IAAiB,MAAQ,EAAa,WAAW,KAAK,GAAK,EAAK,WAAW,CAAkB,EAC/F,OAAO,EAAgB,SAAS,KAC7B,IAAa,CAAC,GAAe,EAAQ,WAAW,IAAI,IAAM,EAAK,MAAM,YAAY,EAAc,CAAO,CACzG,CACF,EACA,gBAAgB,EAAc,EAAM,CAIlC,OAHI,EAAiB,IAAI,CAAY,CAAC,EAAE,YAAc,IAG/C,EACJ,MAAM,cAAe,CAAwB,CAAC,CAC9C,KAAM,GAAS,EAAoB,KAAK,CAAI,GAAK,EAAiB,KAAK,CAAI,CAAC,CACjF,CACF,CACF,CAGA,SAAgB,EAAU,EAAsB,EAA2C,CACzF,MAAO,CACL,eAAiB,GAAiB,CAAC,EAAU,IAAI,CAAY,GAAK,EAAU,eAAe,CAAY,EACvG,iBAAkB,EAAc,IAC9B,CAAC,EAAU,IAAI,CAAY,GAAK,EAAU,gBAAgB,EAAc,CAAI,CAChF,CACF,CAOA,eAAsB,EACpB,EACA,EACA,EACoB,CACpB,IAAI,EACJ,GAAI,CACF,EAAW,MAAM,EAAS,MAAM,EAAgB,CAAS,CAAC,CAC5D,MAAQ,CACN,OAAO,EAAgB,MAAM,EAAiB,CAAe,CAAC,CAChE,CACA,IAAM,EAAgB,EACnB,IAAK,GAAiB,EAAK,SAAS,EAAU,CAAY,CAAC,CAAC,MAAM,EAAK,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CACtF,OAAQ,GAAiB,IAAiB,MAAQ,CAAC,EAAa,WAAW,KAAK,CAAC,EACpF,OAAO,MAAM,EAAwB,EAAU,EAAe,CAAe,CAC/E,CAMA,eAAsB,EACpB,EACA,EACA,EACA,EACoB,CACpB,IAAM,EAAa,MAAM,EAAuB,EAAU,EAAO,CAAM,EACvE,OAAO,EACL,MAAM,EAAiB,CAAe,EACtC,IAAI,IAAI,CAAC,GAAG,CAAU,CAAC,CAAC,KAAK,CAAC,EAAc,KAAW,CAAC,EAAK,KAAK,EAAU,CAAY,EAAG,CAAK,CAAC,CAAC,CACpG,CACF,CAGA,eAAe,EAAiB,EAA4D,CAC1F,MAAO,CAAE,GAAG,EAAiB,KAAM,MAAM,EAAS,EAAgB,IAAI,CAAC,CAAC,UAAY,EAAgB,IAAI,CAAE,CAC5G"}
1
+ {"version":3,"file":"exclusion.js","names":[],"sources":["../src/exclusion.ts"],"sourcesContent":["import { realpath } from 'node:fs/promises';\nimport path from 'node:path';\nimport { readLinguistAttributes, resolveRepoRoot, type LinguistAttributes } from './git.js';\n\n/**\n * Files left out of measurement beyond the built-in directory and file-name rules: configured\n * glob patterns, files git attributes mark `linguist-generated` or `linguist-vendored`, and files\n * whose header carries a generated-code marker. All paths are absolute.\n */\nexport interface Exclusion {\n isExcludedPath(absolutePath: string): boolean;\n /** Whether the content is generated code, unless git attributes mark the file `-linguist-generated`. */\n isGeneratedCode(absolutePath: string, code: string): boolean;\n}\n\n/** Glob patterns relative to `root` (the directory of the config file that declared them). */\nexport interface ExcludePatterns {\n patterns: string[];\n root: string;\n}\n\nconst generatedMarkerLineCount = 5;\n// Meta's `@generated` convention (used by tree-sitter, Relay, and Buck) and the wording of Go's\n// `// Code generated ... DO NOT EDIT.` convention, which protoc, Prisma, and others share.\nconst generatedTagPattern = /(?:^|[^\\w@])@generated\\b/u;\nconst doNotEditPattern = /\\bgenerated\\b.*\\bdo not (?:edit|modify)\\b/iu;\n\nexport function createExclusion(\n excludePatterns: ExcludePatterns,\n attributesByPath: ReadonlyMap<string, LinguistAttributes> = new Map()\n): Exclusion {\n return {\n isExcludedPath(absolutePath) {\n const attributes = attributesByPath.get(absolutePath);\n if (attributes?.generated === true || attributes?.vendored === true) {\n return true;\n }\n const nativeRelativePath = path.relative(excludePatterns.root, absolutePath);\n const relativePath = nativeRelativePath.split(path.sep).join('/');\n // Only a pattern spelling out `..` reaches outside the root: whether `*` and `**` match `..`\n // segments differs between runtimes (Node's matchesGlob says no, Bun's says yes). A path on\n // another Windows drive has no relative form, so path.relative returns it absolute.\n const outsideRoot =\n relativePath === '..' || relativePath.startsWith('../') || path.isAbsolute(nativeRelativePath);\n return excludePatterns.patterns.some(\n (pattern) => (!outsideRoot || pattern.startsWith('..')) && path.posix.matchesGlob(relativePath, pattern)\n );\n },\n isGeneratedCode(absolutePath, code) {\n if (attributesByPath.get(absolutePath)?.generated === false) {\n return false;\n }\n return code\n .split(/\\r\\n|\\n|\\r/u, generatedMarkerLineCount)\n .some((line) => generatedTagPattern.test(line) || doNotEditPattern.test(line));\n },\n };\n}\n\n/** The exclusion that never excludes the given absolute paths (explicitly targeted files). */\nexport function keepPaths(exclusion: Exclusion, keptPaths: ReadonlySet<string>): Exclusion {\n return {\n isExcludedPath: (absolutePath) => !keptPaths.has(absolutePath) && exclusion.isExcludedPath(absolutePath),\n isGeneratedCode: (absolutePath, code) =>\n !keptPaths.has(absolutePath) && exclusion.isGeneratedCode(absolutePath, code),\n };\n}\n\n/**\n * The exclusion for the given absolute paths found under `directory`, with git attributes from\n * the enclosing repository (which apply to ignored files too); outside any repository only\n * patterns and markers apply.\n */\nexport async function loadExclusion(\n directory: string,\n excludePatterns: ExcludePatterns,\n absolutePaths: readonly string[]\n): Promise<Exclusion> {\n let repoRoot;\n try {\n repoRoot = await realpath(await resolveRepoRoot(directory));\n } catch {\n return createExclusion(await canonicalizeRoot(excludePatterns));\n }\n const relativePaths = absolutePaths\n .map((absolutePath) => path.relative(repoRoot, absolutePath).split(path.sep).join('/'))\n .filter((relativePath) => relativePath !== '..' && !relativePath.startsWith('../'));\n return await loadRepositoryExclusion(repoRoot, relativePaths, excludePatterns);\n}\n\n/**\n * The exclusion for repository-relative `paths` of the canonical `repoRoot`, with git attributes\n * from the working tree.\n */\nexport async function loadRepositoryExclusion(\n repoRoot: string,\n paths: Iterable<string>,\n excludePatterns: ExcludePatterns\n): Promise<Exclusion> {\n const attributes = await readLinguistAttributes(repoRoot, paths);\n return createExclusion(\n await canonicalizeRoot(excludePatterns),\n new Map([...attributes].map(([relativePath, value]) => [path.join(repoRoot, relativePath), value]))\n );\n}\n\n/** Scanned paths are canonical, so a pattern root reached through a symbolic link must be too. */\nasync function canonicalizeRoot(excludePatterns: ExcludePatterns): Promise<ExcludePatterns> {\n return { ...excludePatterns, root: await realpath(excludePatterns.root).catch(() => excludePatterns.root) };\n}\n"],"mappings":"4IAqBA,MAGM,EAAsB,4BACtB,EAAmB,8CAEzB,SAAgB,EACd,EACA,kBAA4D,IAAI,IACrD,CACX,MAAO,CACL,eAAe,EAAc,CAC3B,IAAM,EAAa,EAAiB,IAAI,CAAY,EACpD,GAAI,GAAY,YAAc,IAAQ,GAAY,WAAa,GAC7D,MAAO,GAET,IAAM,EAAqB,EAAK,SAAS,EAAgB,KAAM,CAAY,EACrE,EAAe,EAAmB,MAAM,EAAK,GAAG,CAAC,CAAC,KAAK,GAAG,EAI1D,EACJ,IAAiB,MAAQ,EAAa,WAAW,KAAK,GAAK,EAAK,WAAW,CAAkB,EAC/F,OAAO,EAAgB,SAAS,KAC7B,IAAa,CAAC,GAAe,EAAQ,WAAW,IAAI,IAAM,EAAK,MAAM,YAAY,EAAc,CAAO,CACzG,CACF,EACA,gBAAgB,EAAc,EAAM,CAIlC,OAHI,EAAiB,IAAI,CAAY,CAAC,EAAE,YAAc,IAG/C,EACJ,MAAM,cAAe,CAAwB,CAAC,CAC9C,KAAM,GAAS,EAAoB,KAAK,CAAI,GAAK,EAAiB,KAAK,CAAI,CAAC,CACjF,CACF,CACF,CAGA,SAAgB,EAAU,EAAsB,EAA2C,CACzF,MAAO,CACL,eAAiB,GAAiB,CAAC,EAAU,IAAI,CAAY,GAAK,EAAU,eAAe,CAAY,EACvG,iBAAkB,EAAc,IAC9B,CAAC,EAAU,IAAI,CAAY,GAAK,EAAU,gBAAgB,EAAc,CAAI,CAChF,CACF,CAOA,eAAsB,EACpB,EACA,EACA,EACoB,CACpB,IAAI,EACJ,GAAI,CACF,EAAW,MAAM,EAAS,MAAM,EAAgB,CAAS,CAAC,CAC5D,MAAQ,CACN,OAAO,EAAgB,MAAM,EAAiB,CAAe,CAAC,CAChE,CACA,IAAM,EAAgB,EACnB,IAAK,GAAiB,EAAK,SAAS,EAAU,CAAY,CAAC,CAAC,MAAM,EAAK,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CACtF,OAAQ,GAAiB,IAAiB,MAAQ,CAAC,EAAa,WAAW,KAAK,CAAC,EACpF,OAAO,MAAM,EAAwB,EAAU,EAAe,CAAe,CAC/E,CAMA,eAAsB,EACpB,EACA,EACA,EACoB,CACpB,IAAM,EAAa,MAAM,EAAuB,EAAU,CAAK,EAC/D,OAAO,EACL,MAAM,EAAiB,CAAe,EACtC,IAAI,IAAI,CAAC,GAAG,CAAU,CAAC,CAAC,KAAK,CAAC,EAAc,KAAW,CAAC,EAAK,KAAK,EAAU,CAAY,EAAG,CAAK,CAAC,CAAC,CACpG,CACF,CAGA,eAAe,EAAiB,EAA4D,CAC1F,MAAO,CAAE,GAAG,EAAiB,KAAM,MAAM,EAAS,EAAgB,IAAI,CAAC,CAAC,UAAY,EAAgB,IAAI,CAAE,CAC5G"}
package/dist/git.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./_virtual/_rolldown/runtime.cjs");let t=require("node:fs/promises"),n=require("node:path");n=e.__toESM(n,1);let r=require("node:os");r=e.__toESM(r,1);let i=require("node:child_process");const a=(0,require("node:util").promisify)(i.execFile);async function o(e){return(await v(e,[`rev-parse`,`--show-toplevel`])).trim()}async function s(e,t){return(await v(e,[`merge-base`,t,`HEAD`])).trim()}async function c(e,t){let[n,r]=await Promise.all([v(e,[`diff`,`--name-status`,`--find-renames`,`-z`,t]),v(e,[`ls-files`,`--others`,`--exclude-standard`,`-z`])]),i=l(n);for(let e of r.split(`\0`))e!==``&&i.push({status:`added`,headPath:e});return i}function l(e){let t=[],n=e.split(`\0`),r=0;for(;r<n.length-1;){let e=n[r].charAt(0),i=e===`R`||e===`C`?2:1,a=u(e,n.slice(r+1,r+1+i));a&&t.push(a),r+=1+i}return t}function u(e,t){let[n,r]=t;if(n!==void 0)switch(e){case`R`:return r===void 0?void 0:{status:`renamed`,basePath:n,headPath:r};case`C`:return r===void 0?void 0:{status:`added`,headPath:r};case`A`:return{status:`added`,headPath:n};case`D`:return{status:`deleted`,basePath:n,headPath:n};case`M`:case`T`:return{status:`modified`,basePath:n,headPath:n};default:return}}async function d(e){let[t,n]=await Promise.all([v(e,[`ls-files`,`--cached`,`--others`,`--exclude-standard`,`-z`]),v(e,[`ls-files`,`--deleted`,`-z`])]),r=new Set(t.split(`\0`).filter(e=>e!==``));for(let e of n.split(`\0`))r.delete(e);return r}async function f(e,t){let n=await v(e,[`ls-tree`,`-r`,`-z`,t]),r=/* @__PURE__ */ new Set;for(let e of n.split(`\0`)){let t=e.indexOf(` `);e.startsWith(`120000 `)&&t!==-1&&r.add(e.slice(t+1))}return r}async function p(e,t,n,r){let i=await v(e,[`-c`,`diff.algorithm=myers`,`-c`,`diff.indentHeuristic=true`,`diff`,`--unified=0`,`--inter-hunk-context=0`,`--text`,`--no-color`,`--no-ext-diff`,`--no-textconv`,`--find-renames`,t,`--`,...(n===r?[r]:[n,r]).map(e=>`:(literal)${e}`)]),a=[];for(let e of i.matchAll(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/gmu)){let[,t,n,r,i]=e;a.push({baseStart:Number(t),baseCount:n===void 0?1:Number(n),headStart:Number(r),headCount:i===void 0?1:Number(i)})}return a}async function m(e,i,a){let o=[...i].join(`\0`);if(o===``)return/* @__PURE__ */ new Map;let s=[`--stdin`,`-z`,`linguist-generated`,`linguist-vendored`],c;if(a===void 0)c=await _(e,[`check-attr`,...s],o);else{let i=await(0,t.mkdtemp)(n.default.join(r.default.tmpdir(),`code-gauge-index-`));try{let t={...process.env,GIT_INDEX_FILE:n.default.join(i,`index`)};await _(e,[`read-tree`,a],``,t),c=await _(e,[`check-attr`,`--cached`,...s],o,t)}finally{await(0,t.rm)(i,{recursive:!0,force:!0})}}let l=/* @__PURE__ */ new Map,u=c.split(`\0`);for(let e=0;e+2<u.length;e+=3){let[t,n,r]=u.slice(e,e+3),i=h(r);if(i===void 0)continue;let a=l.get(t)??{};n===`linguist-generated`?a.generated=i:a.vendored=i,l.set(t,a)}return l}function h(e){if(e===`set`||e===`true`)return!0;if(e===`unset`||e===`false`)return!1}async function g(e,t,n){return await v(e,[`cat-file`,`blob`,`${t}:${n}`])}async function _(e,t,n,r=process.env){return await new Promise((a,o)=>{let s=(0,i.spawn)(`git`,t,{cwd:e,env:r});s.stdin.on(`error`,()=>{});let c=[],l=[];s.stdout.on(`data`,e=>c.push(e)),s.stderr.on(`data`,e=>l.push(e)),s.on(`error`,o),s.on(`close`,e=>{if(e===0){a(Buffer.concat(c).toString(`utf8`));return}let n=Buffer.concat(l).toString(`utf8`).trim();o(/* @__PURE__ */ Error(`git ${y(t)} failed${n?`: ${n}`:``}`))}),s.stdin.end(n)})}async function v(e,t){try{let{stdout:n}=await a(`git`,t,{cwd:e,maxBuffer:536870912,encoding:`utf8`});return n}catch(e){let n=e.stderr?.trim();throw Error(`git ${y(t)} failed${n?`: ${n}`:``}`)}}function y(e){let t=0;for(;e[t]===`-c`;)t+=2;return e.slice(t,t+2).join(` `)}exports.listChangedFiles=c,exports.listLineHunks=p,exports.listRepositoryFiles=d,exports.listSymlinkPathsAtRevision=f,exports.readFileAtRevision=g,exports.readLinguistAttributes=m,exports.resolveMergeBase=s,exports.resolveRepoRoot=o;
1
+ "use strict";let e=require("node:child_process");const t=(0,require("node:util").promisify)(e.execFile);async function n(e){return(await p(e,[`rev-parse`,`--show-toplevel`])).trim()}async function r(e,n){try{return await t(`git`,[`check-ignore`,`--quiet`,`--`,n],{cwd:e}),!0}catch(e){if(e.code===1)return!1;throw e}}async function i(e,t){return(await p(e,[`merge-base`,t,`HEAD`])).trim()}async function a(e,t){let[n,r]=await Promise.all([p(e,[`diff`,`--name-status`,`--find-renames`,`-z`,t]),p(e,[`ls-files`,`--others`,`--exclude-standard`,`-z`])]),i=o(n);for(let e of r.split(`\0`))e!==``&&i.push({status:`added`,headPath:e});return i}function o(e){let t=[],n=e.split(`\0`),r=0;for(;r<n.length-1;){let e=n[r].charAt(0),i=e===`R`||e===`C`?2:1,a=s(e,n.slice(r+1,r+1+i));a&&t.push(a),r+=1+i}return t}function s(e,t){let[n,r]=t;if(n!==void 0)switch(e){case`R`:return r===void 0?void 0:{status:`renamed`,basePath:n,headPath:r};case`C`:return r===void 0?void 0:{status:`added`,headPath:r};case`A`:return{status:`added`,headPath:n};case`D`:return{status:`deleted`,basePath:n,headPath:n};case`M`:case`T`:return{status:`modified`,basePath:n,headPath:n};default:return}}async function c(e){let[t,n]=await Promise.all([p(e,[`ls-files`,`--cached`,`--others`,`--exclude-standard`,`-z`]),p(e,[`ls-files`,`--deleted`,`-z`])]),r=new Set(t.split(`\0`).filter(e=>e!==``));for(let e of n.split(`\0`))r.delete(e);return r}async function l(e,t,n,r){let i=await p(e,[`-c`,`diff.algorithm=myers`,`-c`,`diff.indentHeuristic=true`,`diff`,`--unified=0`,`--inter-hunk-context=0`,`--text`,`--no-color`,`--no-ext-diff`,`--no-textconv`,`--find-renames`,t,`--`,...(n===r?[r]:[n,r]).map(e=>`:(literal)${e}`)]),a=[];for(let e of i.matchAll(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/gmu)){let[,t,n]=e;a.push({headStart:Number(t),headCount:n===void 0?1:Number(n)})}return a}async function u(e,t){let n=[...t].join(`\0`);if(n===``)return/* @__PURE__ */ new Map;let r=await f(e,[`check-attr`,`--stdin`,`-z`,`linguist-generated`,`linguist-vendored`],n),i=/* @__PURE__ */ new Map,a=r.split(`\0`);for(let e=0;e+2<a.length;e+=3){let[t,n,r]=a.slice(e,e+3),o=d(r);if(o===void 0)continue;let s=i.get(t)??{};n===`linguist-generated`?s.generated=o:s.vendored=o,i.set(t,s)}return i}function d(e){if(e===`set`||e===`true`)return!0;if(e===`unset`||e===`false`)return!1}async function f(t,n,r){return await new Promise((i,a)=>{let o=(0,e.spawn)(`git`,n,{cwd:t});o.stdin.on(`error`,()=>{});let s=[],c=[];o.stdout.on(`data`,e=>s.push(e)),o.stderr.on(`data`,e=>c.push(e)),o.on(`error`,a),o.on(`close`,e=>{if(e===0){i(Buffer.concat(s).toString(`utf8`));return}let t=Buffer.concat(c).toString(`utf8`).trim();a(/* @__PURE__ */ Error(`git ${m(n)} failed${t?`: ${t}`:``}`))}),o.stdin.end(r)})}async function p(e,n){try{let{stdout:r}=await t(`git`,n,{cwd:e,maxBuffer:536870912,encoding:`utf8`});return r}catch(e){let t=e.stderr?.trim();throw Error(`git ${m(n)} failed${t?`: ${t}`:``}`)}}function m(e){let t=0;for(;e[t]===`-c`;)t+=2;return e.slice(t,t+2).join(` `)}exports.isIgnored=r,exports.listChangedFiles=a,exports.listLineHunks=l,exports.listRepositoryFiles=c,exports.readLinguistAttributes=u,exports.resolveMergeBase=i,exports.resolveRepoRoot=n;
2
2
  //# sourceMappingURL=git.cjs.map
package/dist/git.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"git.cjs","names":["promisify","execFile","mkdtemp","path","os","rm","spawn"],"sources":["../src/git.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { mkdtemp, rm } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\nimport type { LineHunk } from './newDuplication.js';\n\nconst execFileAsync = promisify(execFile);\n\n/** Large enough for whole-file blobs; execFile's 1 MiB default truncates real sources. */\nconst maxOutputBytes = 512 * 1024 * 1024;\n\n/** One entry of the working tree's diff against the merge-base commit. */\nexport interface ChangedFile {\n status: 'added' | 'modified' | 'deleted' | 'renamed';\n /** Path at the merge-base, absent for added files; differs from headPath only for renames. */\n basePath?: string;\n /** Path in the working tree; for deleted files, the (now missing) base path. */\n headPath: string;\n}\n\nexport async function resolveRepoRoot(directory: string): Promise<string> {\n const output = await runGit(directory, ['rev-parse', '--show-toplevel']);\n return output.trim();\n}\n\nexport async function resolveMergeBase(repoRoot: string, baseRef: string): Promise<string> {\n const output = await runGit(repoRoot, ['merge-base', baseRef, 'HEAD']);\n return output.trim();\n}\n\n/**\n * Files whose working-tree content differs from the merge-base commit (staged or not), with\n * rename detection, plus untracked (non-ignored) files as additions. Copies gate like additions:\n * only the new path is measured against the new-code thresholds.\n */\nexport async function listChangedFiles(repoRoot: string, mergeBase: string): Promise<ChangedFile[]> {\n const [diffOutput, untrackedOutput] = await Promise.all([\n runGit(repoRoot, ['diff', '--name-status', '--find-renames', '-z', mergeBase]),\n runGit(repoRoot, ['ls-files', '--others', '--exclude-standard', '-z']),\n ]);\n\n const files = parseNameStatusEntries(diffOutput);\n for (const path of untrackedOutput.split('\\0')) {\n if (path !== '') {\n files.push({ status: 'added', headPath: path });\n }\n }\n return files;\n}\n\n/** Parses `git diff --name-status -z` output: `<status>\\0<path>\\0`, with two paths for R/C. */\nfunction parseNameStatusEntries(output: string): ChangedFile[] {\n const files: ChangedFile[] = [];\n const parts = output.split('\\0');\n let index = 0;\n while (index < parts.length - 1) {\n const kind = (parts[index] as string).charAt(0);\n const pathCount = kind === 'R' || kind === 'C' ? 2 : 1;\n const changed = toChangedFile(kind, parts.slice(index + 1, index + 1 + pathCount));\n if (changed) {\n files.push(changed);\n }\n index += 1 + pathCount;\n }\n return files;\n}\n\n/** Undefined for unmerged (U) and unknown statuses, which have nothing to gate. */\nfunction toChangedFile(kind: string, paths: (string | undefined)[]): ChangedFile | undefined {\n const [first, second] = paths;\n if (first === undefined) {\n return undefined;\n }\n switch (kind) {\n case 'R': {\n return second === undefined ? undefined : { status: 'renamed', basePath: first, headPath: second };\n }\n // A copy gates like an addition: only the new path is measured against the new-code thresholds.\n case 'C': {\n return second === undefined ? undefined : { status: 'added', headPath: second };\n }\n case 'A': {\n return { status: 'added', headPath: first };\n }\n case 'D': {\n return { status: 'deleted', basePath: first, headPath: first };\n }\n case 'M':\n case 'T': {\n return { status: 'modified', basePath: first, headPath: first };\n }\n default: {\n return undefined;\n }\n }\n}\n\n/**\n * Repository-relative paths git considers part of the project AND that exist in the working tree:\n * tracked files (minus worktree deletions) plus untracked non-ignored ones. The diff gate scans\n * exactly these, so local ignored artifacts (build output, generated copies) are neither measured\n * nor allowed to skew the duplication universes.\n */\nexport async function listRepositoryFiles(repoRoot: string): Promise<Set<string>> {\n const [listed, deleted] = await Promise.all([\n runGit(repoRoot, ['ls-files', '--cached', '--others', '--exclude-standard', '-z']),\n runGit(repoRoot, ['ls-files', '--deleted', '-z']),\n ]);\n const files = new Set(listed.split('\\0').filter((path) => path !== ''));\n for (const path of deleted.split('\\0')) {\n files.delete(path);\n }\n return files;\n}\n\n/** Paths that are symbolic links (blob mode 120000) at the given revision. */\nexport async function listSymlinkPathsAtRevision(repoRoot: string, revision: string): Promise<Set<string>> {\n const output = await runGit(repoRoot, ['ls-tree', '-r', '-z', revision]);\n const links = new Set<string>();\n for (const entry of output.split('\\0')) {\n const tabIndex = entry.indexOf('\\t');\n if (entry.startsWith('120000 ') && tabIndex !== -1) {\n links.add(entry.slice(tabIndex + 1));\n }\n }\n return links;\n}\n\n/**\n * The line hunks turning the merge-base blob at `basePath` into the working-tree file at\n * `headPath` (equal unless renamed), as `git diff` computes them for review.\n */\nexport async function listLineHunks(\n repoRoot: string,\n mergeBase: string,\n basePath: string,\n headPath: string\n): Promise<LineHunk[]> {\n // Literal pathspecs: a name like `app/[slug]/page.tsx` must not glob-match other files.\n const paths = (basePath === headPath ? [headPath] : [basePath, headPath]).map((file) => `:(literal)${file}`);\n const output = await runGit(repoRoot, [\n // The algorithm and indent heuristic decide which lines count as added, so user config must\n // not change them; `-c` values an older git does not know are ignored rather than rejected.\n '-c',\n 'diff.algorithm=myers',\n '-c',\n 'diff.indentHeuristic=true',\n 'diff',\n '--unified=0',\n // diff.interHunkContext would otherwise merge nearby hunks, and the unchanged lines between\n // them would count as added.\n '--inter-hunk-context=0',\n // A file git would show as binary (a `-diff` attribute, a NUL byte) must still yield hunks.\n '--text',\n '--no-color',\n '--no-ext-diff',\n '--no-textconv',\n '--find-renames',\n mergeBase,\n '--',\n ...paths,\n ]);\n const hunks: LineHunk[] = [];\n for (const match of output.matchAll(/^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@/gmu)) {\n const [, baseStart, baseCount, headStart, headCount] = match;\n hunks.push({\n baseStart: Number(baseStart),\n baseCount: baseCount === undefined ? 1 : Number(baseCount),\n headStart: Number(headStart),\n headCount: headCount === undefined ? 1 : Number(headCount),\n });\n }\n return hunks;\n}\n\n/** A file's `linguist-generated` / `linguist-vendored` attributes; undefined when unspecified. */\nexport interface LinguistAttributes {\n generated?: boolean;\n vendored?: boolean;\n}\n\n/**\n * The linguist attributes of repository-relative paths, from the working tree's `.gitattributes`\n * or, with `source`, from that revision's. Paths with neither attribute specified are omitted.\n */\nexport async function readLinguistAttributes(\n repoRoot: string,\n paths: Iterable<string>,\n source?: string\n): Promise<Map<string, LinguistAttributes>> {\n const input = [...paths].join('\\0');\n if (input === '') {\n return new Map();\n }\n const attributeArgs = ['--stdin', '-z', 'linguist-generated', 'linguist-vendored'];\n let output;\n if (source === undefined) {\n output = await runGitWithInput(repoRoot, ['check-attr', ...attributeArgs], input);\n } else {\n // `check-attr --source` needs git 2.40, so the revision's attributes are read from a\n // throwaway index holding its tree instead, which `--cached` supports on any git.\n const indexDirectory = await mkdtemp(path.join(os.tmpdir(), 'code-gauge-index-'));\n try {\n const env = { ...process.env, GIT_INDEX_FILE: path.join(indexDirectory, 'index') };\n await runGitWithInput(repoRoot, ['read-tree', source], '', env);\n output = await runGitWithInput(repoRoot, ['check-attr', '--cached', ...attributeArgs], input, env);\n } finally {\n await rm(indexDirectory, { recursive: true, force: true });\n }\n }\n const attributesByPath = new Map<string, LinguistAttributes>();\n const fields = output.split('\\0');\n for (let index = 0; index + 2 < fields.length; index += 3) {\n const [file, attribute, value] = fields.slice(index, index + 3) as [string, string, string];\n const enabled = toAttributeFlag(value);\n if (enabled === undefined) {\n continue;\n }\n const attributes = attributesByPath.get(file) ?? {};\n if (attribute === 'linguist-generated') {\n attributes.generated = enabled;\n } else {\n attributes.vendored = enabled;\n }\n attributesByPath.set(file, attributes);\n }\n return attributesByPath;\n}\n\n/** Linguist reads `attr`/`attr=true` as set and `-attr`/`attr=false` as explicitly unset. */\nfunction toAttributeFlag(value: string): boolean | undefined {\n if (value === 'set' || value === 'true') {\n return true;\n }\n if (value === 'unset' || value === 'false') {\n return false;\n }\n return undefined;\n}\n\n/** The file's content at the given commit; the path is repository-relative with forward slashes. */\nexport async function readFileAtRevision(repoRoot: string, revision: string, path: string): Promise<string> {\n return await runGit(repoRoot, ['cat-file', 'blob', `${revision}:${path}`]);\n}\n\nasync function runGitWithInput(\n cwd: string,\n args: string[],\n input: string,\n env: NodeJS.ProcessEnv = process.env\n): Promise<string> {\n return await new Promise((resolve, reject) => {\n const child = spawn('git', args, { cwd, env });\n // git exiting before it reads all input (e.g. on a usage error) makes the write fail with\n // EPIPE; its exit code and stderr, reported on close, already describe the failure.\n child.stdin.on('error', () => {});\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) {\n resolve(Buffer.concat(stdout).toString('utf8'));\n return;\n }\n const message = Buffer.concat(stderr).toString('utf8').trim();\n reject(new Error(`git ${describeCommand(args)} failed${message ? `: ${message}` : ''}`));\n });\n child.stdin.end(input);\n });\n}\n\nasync function runGit(cwd: string, args: string[]): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, { cwd, maxBuffer: maxOutputBytes, encoding: 'utf8' });\n return stdout;\n } catch (error) {\n const stderr = (error as { stderr?: string }).stderr?.trim();\n throw new Error(`git ${describeCommand(args)} failed${stderr ? `: ${stderr}` : ''}`);\n }\n}\n\n/** The subcommand and its first argument, skipping leading `-c key=value` overrides. */\nfunction describeCommand(args: readonly string[]): string {\n let start = 0;\n while (args[start] === '-c') {\n start += 2;\n }\n return args.slice(start, start + 2).join(' ');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"git.cjs","names":["promisify","execFile","spawn"],"sources":["../src/git.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\n/** Large enough for whole-file blobs; execFile's 1 MiB default truncates real sources. */\nconst maxOutputBytes = 512 * 1024 * 1024;\n\n/** The head side of one region of a line diff: lines [headStart, headStart + headCount); a pure deletion has headCount 0. */\nexport interface LineHunk {\n headStart: number;\n headCount: number;\n}\n\n/** One entry of the working tree's diff against the merge-base commit. */\nexport interface ChangedFile {\n status: 'added' | 'modified' | 'deleted' | 'renamed';\n /** Path at the merge-base, absent for added files; differs from headPath only for renames. */\n basePath?: string;\n /** Path in the working tree; for deleted files, the (now missing) base path. */\n headPath: string;\n}\n\nexport async function resolveRepoRoot(directory: string): Promise<string> {\n const output = await runGit(directory, ['rev-parse', '--show-toplevel']);\n return output.trim();\n}\n\n/** Whether git ignores the path or a directory above it (`git check-ignore` exits 1 otherwise). */\nexport async function isIgnored(repoRoot: string, path: string): Promise<boolean> {\n try {\n await execFileAsync('git', ['check-ignore', '--quiet', '--', path], { cwd: repoRoot });\n return true;\n } catch (error) {\n if ((error as { code?: unknown }).code === 1) return false;\n throw error;\n }\n}\n\nexport async function resolveMergeBase(repoRoot: string, baseRef: string): Promise<string> {\n const output = await runGit(repoRoot, ['merge-base', baseRef, 'HEAD']);\n return output.trim();\n}\n\n/**\n * Files whose working-tree content differs from the merge-base commit (staged or not), with\n * rename detection, plus untracked (non-ignored) files as additions.\n */\nexport async function listChangedFiles(repoRoot: string, mergeBase: string): Promise<ChangedFile[]> {\n const [diffOutput, untrackedOutput] = await Promise.all([\n runGit(repoRoot, ['diff', '--name-status', '--find-renames', '-z', mergeBase]),\n runGit(repoRoot, ['ls-files', '--others', '--exclude-standard', '-z']),\n ]);\n\n const files = parseNameStatusEntries(diffOutput);\n for (const path of untrackedOutput.split('\\0')) {\n if (path !== '') {\n files.push({ status: 'added', headPath: path });\n }\n }\n return files;\n}\n\n/** Parses `git diff --name-status -z` output: `<status>\\0<path>\\0`, with two paths for R/C. */\nfunction parseNameStatusEntries(output: string): ChangedFile[] {\n const files: ChangedFile[] = [];\n const parts = output.split('\\0');\n let index = 0;\n while (index < parts.length - 1) {\n const kind = (parts[index] as string).charAt(0);\n const pathCount = kind === 'R' || kind === 'C' ? 2 : 1;\n const changed = toChangedFile(kind, parts.slice(index + 1, index + 1 + pathCount));\n if (changed) {\n files.push(changed);\n }\n index += 1 + pathCount;\n }\n return files;\n}\n\n/** Undefined for unmerged (U) and unknown statuses, which have nothing to check. */\nfunction toChangedFile(kind: string, paths: (string | undefined)[]): ChangedFile | undefined {\n const [first, second] = paths;\n if (first === undefined) {\n return undefined;\n }\n switch (kind) {\n case 'R': {\n return second === undefined ? undefined : { status: 'renamed', basePath: first, headPath: second };\n }\n // A copy is an addition: every line of the new path is new.\n case 'C': {\n return second === undefined ? undefined : { status: 'added', headPath: second };\n }\n case 'A': {\n return { status: 'added', headPath: first };\n }\n case 'D': {\n return { status: 'deleted', basePath: first, headPath: first };\n }\n case 'M':\n case 'T': {\n return { status: 'modified', basePath: first, headPath: first };\n }\n default: {\n return undefined;\n }\n }\n}\n\n/**\n * Repository-relative paths git considers part of the project AND that exist in the working tree:\n * tracked files (minus worktree deletions) plus untracked non-ignored ones. `check` scans\n * exactly these, so local ignored artifacts (build output, generated copies) are neither measured\n * nor allowed to skew duplication detection.\n */\nexport async function listRepositoryFiles(repoRoot: string): Promise<Set<string>> {\n const [listed, deleted] = await Promise.all([\n runGit(repoRoot, ['ls-files', '--cached', '--others', '--exclude-standard', '-z']),\n runGit(repoRoot, ['ls-files', '--deleted', '-z']),\n ]);\n const files = new Set(listed.split('\\0').filter((path) => path !== ''));\n for (const path of deleted.split('\\0')) {\n files.delete(path);\n }\n return files;\n}\n\n/**\n * The line hunks turning the merge-base blob at `basePath` into the working-tree file at\n * `headPath` (equal unless renamed), as `git diff` computes them for review.\n */\nexport async function listLineHunks(\n repoRoot: string,\n mergeBase: string,\n basePath: string,\n headPath: string\n): Promise<LineHunk[]> {\n // Literal pathspecs: a name like `app/[slug]/page.tsx` must not glob-match other files.\n const paths = (basePath === headPath ? [headPath] : [basePath, headPath]).map((file) => `:(literal)${file}`);\n const output = await runGit(repoRoot, [\n // The algorithm and indent heuristic decide which lines count as added, so user config must\n // not change them; `-c` values an older git does not know are ignored rather than rejected.\n '-c',\n 'diff.algorithm=myers',\n '-c',\n 'diff.indentHeuristic=true',\n 'diff',\n '--unified=0',\n // diff.interHunkContext would otherwise merge nearby hunks, and the unchanged lines between\n // them would count as added.\n '--inter-hunk-context=0',\n // A file git would show as binary (a `-diff` attribute, a NUL byte) must still yield hunks.\n '--text',\n '--no-color',\n '--no-ext-diff',\n '--no-textconv',\n '--find-renames',\n mergeBase,\n '--',\n ...paths,\n ]);\n const hunks: LineHunk[] = [];\n for (const match of output.matchAll(/^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,(\\d+))? @@/gmu)) {\n const [, headStart, headCount] = match;\n hunks.push({\n headStart: Number(headStart),\n headCount: headCount === undefined ? 1 : Number(headCount),\n });\n }\n return hunks;\n}\n\n/** A file's `linguist-generated` / `linguist-vendored` attributes; undefined when unspecified. */\nexport interface LinguistAttributes {\n generated?: boolean;\n vendored?: boolean;\n}\n\n/**\n * The linguist attributes of repository-relative paths, from the working tree's `.gitattributes`.\n * Paths with neither attribute specified are omitted.\n */\nexport async function readLinguistAttributes(\n repoRoot: string,\n paths: Iterable<string>\n): Promise<Map<string, LinguistAttributes>> {\n const input = [...paths].join('\\0');\n if (input === '') {\n return new Map();\n }\n const output = await runGitWithInput(\n repoRoot,\n ['check-attr', '--stdin', '-z', 'linguist-generated', 'linguist-vendored'],\n input\n );\n const attributesByPath = new Map<string, LinguistAttributes>();\n const fields = output.split('\\0');\n for (let index = 0; index + 2 < fields.length; index += 3) {\n const [file, attribute, value] = fields.slice(index, index + 3) as [string, string, string];\n const enabled = toAttributeFlag(value);\n if (enabled === undefined) {\n continue;\n }\n const attributes = attributesByPath.get(file) ?? {};\n if (attribute === 'linguist-generated') {\n attributes.generated = enabled;\n } else {\n attributes.vendored = enabled;\n }\n attributesByPath.set(file, attributes);\n }\n return attributesByPath;\n}\n\n/** Linguist reads `attr`/`attr=true` as set and `-attr`/`attr=false` as explicitly unset. */\nfunction toAttributeFlag(value: string): boolean | undefined {\n if (value === 'set' || value === 'true') {\n return true;\n }\n if (value === 'unset' || value === 'false') {\n return false;\n }\n return undefined;\n}\n\nasync function runGitWithInput(cwd: string, args: string[], input: string): Promise<string> {\n return await new Promise((resolve, reject) => {\n const child = spawn('git', args, { cwd });\n // git exiting before it reads all input (e.g. on a usage error) makes the write fail with\n // EPIPE; its exit code and stderr, reported on close, already describe the failure.\n child.stdin.on('error', () => {});\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) {\n resolve(Buffer.concat(stdout).toString('utf8'));\n return;\n }\n const message = Buffer.concat(stderr).toString('utf8').trim();\n reject(new Error(`git ${describeCommand(args)} failed${message ? `: ${message}` : ''}`));\n });\n child.stdin.end(input);\n });\n}\n\nasync function runGit(cwd: string, args: string[]): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, { cwd, maxBuffer: maxOutputBytes, encoding: 'utf8' });\n return stdout;\n } catch (error) {\n const stderr = (error as { stderr?: string }).stderr?.trim();\n throw new Error(`git ${describeCommand(args)} failed${stderr ? `: ${stderr}` : ''}`);\n }\n}\n\n/** The subcommand and its first argument, skipping leading `-c key=value` overrides. */\nfunction describeCommand(args: readonly string[]): string {\n let start = 0;\n while (args[start] === '-c') {\n start += 2;\n }\n return args.slice(start, start + 2).join(' ');\n}\n"],"mappings":"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"}
package/dist/git.d.ts CHANGED
@@ -1,4 +1,8 @@
1
- import type { LineHunk } from './newDuplication.js';
1
+ /** The head side of one region of a line diff: lines [headStart, headStart + headCount); a pure deletion has headCount 0. */
2
+ export interface LineHunk {
3
+ headStart: number;
4
+ headCount: number;
5
+ }
2
6
  /** One entry of the working tree's diff against the merge-base commit. */
3
7
  export interface ChangedFile {
4
8
  status: 'added' | 'modified' | 'deleted' | 'renamed';
@@ -8,22 +12,21 @@ export interface ChangedFile {
8
12
  headPath: string;
9
13
  }
10
14
  export declare function resolveRepoRoot(directory: string): Promise<string>;
15
+ /** Whether git ignores the path or a directory above it (`git check-ignore` exits 1 otherwise). */
16
+ export declare function isIgnored(repoRoot: string, path: string): Promise<boolean>;
11
17
  export declare function resolveMergeBase(repoRoot: string, baseRef: string): Promise<string>;
12
18
  /**
13
19
  * Files whose working-tree content differs from the merge-base commit (staged or not), with
14
- * rename detection, plus untracked (non-ignored) files as additions. Copies gate like additions:
15
- * only the new path is measured against the new-code thresholds.
20
+ * rename detection, plus untracked (non-ignored) files as additions.
16
21
  */
17
22
  export declare function listChangedFiles(repoRoot: string, mergeBase: string): Promise<ChangedFile[]>;
18
23
  /**
19
24
  * Repository-relative paths git considers part of the project AND that exist in the working tree:
20
- * tracked files (minus worktree deletions) plus untracked non-ignored ones. The diff gate scans
25
+ * tracked files (minus worktree deletions) plus untracked non-ignored ones. `check` scans
21
26
  * exactly these, so local ignored artifacts (build output, generated copies) are neither measured
22
- * nor allowed to skew the duplication universes.
27
+ * nor allowed to skew duplication detection.
23
28
  */
24
29
  export declare function listRepositoryFiles(repoRoot: string): Promise<Set<string>>;
25
- /** Paths that are symbolic links (blob mode 120000) at the given revision. */
26
- export declare function listSymlinkPathsAtRevision(repoRoot: string, revision: string): Promise<Set<string>>;
27
30
  /**
28
31
  * The line hunks turning the merge-base blob at `basePath` into the working-tree file at
29
32
  * `headPath` (equal unless renamed), as `git diff` computes them for review.
@@ -35,9 +38,7 @@ export interface LinguistAttributes {
35
38
  vendored?: boolean;
36
39
  }
37
40
  /**
38
- * The linguist attributes of repository-relative paths, from the working tree's `.gitattributes`
39
- * or, with `source`, from that revision's. Paths with neither attribute specified are omitted.
41
+ * The linguist attributes of repository-relative paths, from the working tree's `.gitattributes`.
42
+ * Paths with neither attribute specified are omitted.
40
43
  */
41
- export declare function readLinguistAttributes(repoRoot: string, paths: Iterable<string>, source?: string): Promise<Map<string, LinguistAttributes>>;
42
- /** The file's content at the given commit; the path is repository-relative with forward slashes. */
43
- export declare function readFileAtRevision(repoRoot: string, revision: string, path: string): Promise<string>;
44
+ export declare function readLinguistAttributes(repoRoot: string, paths: Iterable<string>): Promise<Map<string, LinguistAttributes>>;
package/dist/git.js CHANGED
@@ -1,2 +1,2 @@
1
- import{mkdtemp as e,rm as t}from"node:fs/promises";import n from"node:path";import r from"node:os";import{execFile as i,spawn as a}from"node:child_process";import{promisify as o}from"node:util";const s=o(i);async function c(e){return(await b(e,[`rev-parse`,`--show-toplevel`])).trim()}async function l(e,t){return(await b(e,[`merge-base`,t,`HEAD`])).trim()}async function u(e,t){let[n,r]=await Promise.all([b(e,[`diff`,`--name-status`,`--find-renames`,`-z`,t]),b(e,[`ls-files`,`--others`,`--exclude-standard`,`-z`])]),i=d(n);for(let e of r.split(`\0`))e!==``&&i.push({status:`added`,headPath:e});return i}function d(e){let t=[],n=e.split(`\0`),r=0;for(;r<n.length-1;){let e=n[r].charAt(0),i=e===`R`||e===`C`?2:1,a=f(e,n.slice(r+1,r+1+i));a&&t.push(a),r+=1+i}return t}function f(e,t){let[n,r]=t;if(n!==void 0)switch(e){case`R`:return r===void 0?void 0:{status:`renamed`,basePath:n,headPath:r};case`C`:return r===void 0?void 0:{status:`added`,headPath:r};case`A`:return{status:`added`,headPath:n};case`D`:return{status:`deleted`,basePath:n,headPath:n};case`M`:case`T`:return{status:`modified`,basePath:n,headPath:n};default:return}}async function p(e){let[t,n]=await Promise.all([b(e,[`ls-files`,`--cached`,`--others`,`--exclude-standard`,`-z`]),b(e,[`ls-files`,`--deleted`,`-z`])]),r=new Set(t.split(`\0`).filter(e=>e!==``));for(let e of n.split(`\0`))r.delete(e);return r}async function m(e,t){let n=await b(e,[`ls-tree`,`-r`,`-z`,t]),r=/* @__PURE__ */ new Set;for(let e of n.split(`\0`)){let t=e.indexOf(` `);e.startsWith(`120000 `)&&t!==-1&&r.add(e.slice(t+1))}return r}async function h(e,t,n,r){let i=await b(e,[`-c`,`diff.algorithm=myers`,`-c`,`diff.indentHeuristic=true`,`diff`,`--unified=0`,`--inter-hunk-context=0`,`--text`,`--no-color`,`--no-ext-diff`,`--no-textconv`,`--find-renames`,t,`--`,...(n===r?[r]:[n,r]).map(e=>`:(literal)${e}`)]),a=[];for(let e of i.matchAll(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/gmu)){let[,t,n,r,i]=e;a.push({baseStart:Number(t),baseCount:n===void 0?1:Number(n),headStart:Number(r),headCount:i===void 0?1:Number(i)})}return a}async function g(i,a,o){let s=[...a].join(`\0`);if(s===``)return/* @__PURE__ */ new Map;let c=[`--stdin`,`-z`,`linguist-generated`,`linguist-vendored`],l;if(o===void 0)l=await y(i,[`check-attr`,...c],s);else{let a=await e(n.join(r.tmpdir(),`code-gauge-index-`));try{let e={...process.env,GIT_INDEX_FILE:n.join(a,`index`)};await y(i,[`read-tree`,o],``,e),l=await y(i,[`check-attr`,`--cached`,...c],s,e)}finally{await t(a,{recursive:!0,force:!0})}}let u=/* @__PURE__ */ new Map,d=l.split(`\0`);for(let e=0;e+2<d.length;e+=3){let[t,n,r]=d.slice(e,e+3),i=_(r);if(i===void 0)continue;let a=u.get(t)??{};n===`linguist-generated`?a.generated=i:a.vendored=i,u.set(t,a)}return u}function _(e){if(e===`set`||e===`true`)return!0;if(e===`unset`||e===`false`)return!1}async function v(e,t,n){return await b(e,[`cat-file`,`blob`,`${t}:${n}`])}async function y(e,t,n,r=process.env){return await new Promise((i,o)=>{let s=a(`git`,t,{cwd:e,env:r});s.stdin.on(`error`,()=>{});let c=[],l=[];s.stdout.on(`data`,e=>c.push(e)),s.stderr.on(`data`,e=>l.push(e)),s.on(`error`,o),s.on(`close`,e=>{if(e===0){i(Buffer.concat(c).toString(`utf8`));return}let n=Buffer.concat(l).toString(`utf8`).trim();o(/* @__PURE__ */ Error(`git ${x(t)} failed${n?`: ${n}`:``}`))}),s.stdin.end(n)})}async function b(e,t){try{let{stdout:n}=await s(`git`,t,{cwd:e,maxBuffer:536870912,encoding:`utf8`});return n}catch(e){let n=e.stderr?.trim();throw Error(`git ${x(t)} failed${n?`: ${n}`:``}`)}}function x(e){let t=0;for(;e[t]===`-c`;)t+=2;return e.slice(t,t+2).join(` `)}export{u as listChangedFiles,h as listLineHunks,p as listRepositoryFiles,m as listSymlinkPathsAtRevision,v as readFileAtRevision,g as readLinguistAttributes,l as resolveMergeBase,c as resolveRepoRoot};
1
+ import{execFile as e,spawn as t}from"node:child_process";import{promisify as n}from"node:util";const r=n(e);async function i(e){return(await h(e,[`rev-parse`,`--show-toplevel`])).trim()}async function a(e,t){try{return await r(`git`,[`check-ignore`,`--quiet`,`--`,t],{cwd:e}),!0}catch(e){if(e.code===1)return!1;throw e}}async function o(e,t){return(await h(e,[`merge-base`,t,`HEAD`])).trim()}async function s(e,t){let[n,r]=await Promise.all([h(e,[`diff`,`--name-status`,`--find-renames`,`-z`,t]),h(e,[`ls-files`,`--others`,`--exclude-standard`,`-z`])]),i=c(n);for(let e of r.split(`\0`))e!==``&&i.push({status:`added`,headPath:e});return i}function c(e){let t=[],n=e.split(`\0`),r=0;for(;r<n.length-1;){let e=n[r].charAt(0),i=e===`R`||e===`C`?2:1,a=l(e,n.slice(r+1,r+1+i));a&&t.push(a),r+=1+i}return t}function l(e,t){let[n,r]=t;if(n!==void 0)switch(e){case`R`:return r===void 0?void 0:{status:`renamed`,basePath:n,headPath:r};case`C`:return r===void 0?void 0:{status:`added`,headPath:r};case`A`:return{status:`added`,headPath:n};case`D`:return{status:`deleted`,basePath:n,headPath:n};case`M`:case`T`:return{status:`modified`,basePath:n,headPath:n};default:return}}async function u(e){let[t,n]=await Promise.all([h(e,[`ls-files`,`--cached`,`--others`,`--exclude-standard`,`-z`]),h(e,[`ls-files`,`--deleted`,`-z`])]),r=new Set(t.split(`\0`).filter(e=>e!==``));for(let e of n.split(`\0`))r.delete(e);return r}async function d(e,t,n,r){let i=await h(e,[`-c`,`diff.algorithm=myers`,`-c`,`diff.indentHeuristic=true`,`diff`,`--unified=0`,`--inter-hunk-context=0`,`--text`,`--no-color`,`--no-ext-diff`,`--no-textconv`,`--find-renames`,t,`--`,...(n===r?[r]:[n,r]).map(e=>`:(literal)${e}`)]),a=[];for(let e of i.matchAll(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/gmu)){let[,t,n]=e;a.push({headStart:Number(t),headCount:n===void 0?1:Number(n)})}return a}async function f(e,t){let n=[...t].join(`\0`);if(n===``)return/* @__PURE__ */ new Map;let r=await m(e,[`check-attr`,`--stdin`,`-z`,`linguist-generated`,`linguist-vendored`],n),i=/* @__PURE__ */ new Map,a=r.split(`\0`);for(let e=0;e+2<a.length;e+=3){let[t,n,r]=a.slice(e,e+3),o=p(r);if(o===void 0)continue;let s=i.get(t)??{};n===`linguist-generated`?s.generated=o:s.vendored=o,i.set(t,s)}return i}function p(e){if(e===`set`||e===`true`)return!0;if(e===`unset`||e===`false`)return!1}async function m(e,n,r){return await new Promise((i,a)=>{let o=t(`git`,n,{cwd:e});o.stdin.on(`error`,()=>{});let s=[],c=[];o.stdout.on(`data`,e=>s.push(e)),o.stderr.on(`data`,e=>c.push(e)),o.on(`error`,a),o.on(`close`,e=>{if(e===0){i(Buffer.concat(s).toString(`utf8`));return}let t=Buffer.concat(c).toString(`utf8`).trim();a(/* @__PURE__ */ Error(`git ${g(n)} failed${t?`: ${t}`:``}`))}),o.stdin.end(r)})}async function h(e,t){try{let{stdout:n}=await r(`git`,t,{cwd:e,maxBuffer:536870912,encoding:`utf8`});return n}catch(e){let n=e.stderr?.trim();throw Error(`git ${g(t)} failed${n?`: ${n}`:``}`)}}function g(e){let t=0;for(;e[t]===`-c`;)t+=2;return e.slice(t,t+2).join(` `)}export{a as isIgnored,s as listChangedFiles,d as listLineHunks,u as listRepositoryFiles,f as readLinguistAttributes,o as resolveMergeBase,i as resolveRepoRoot};
2
2
  //# sourceMappingURL=git.js.map
package/dist/git.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"git.js","names":[],"sources":["../src/git.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { mkdtemp, rm } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\nimport type { LineHunk } from './newDuplication.js';\n\nconst execFileAsync = promisify(execFile);\n\n/** Large enough for whole-file blobs; execFile's 1 MiB default truncates real sources. */\nconst maxOutputBytes = 512 * 1024 * 1024;\n\n/** One entry of the working tree's diff against the merge-base commit. */\nexport interface ChangedFile {\n status: 'added' | 'modified' | 'deleted' | 'renamed';\n /** Path at the merge-base, absent for added files; differs from headPath only for renames. */\n basePath?: string;\n /** Path in the working tree; for deleted files, the (now missing) base path. */\n headPath: string;\n}\n\nexport async function resolveRepoRoot(directory: string): Promise<string> {\n const output = await runGit(directory, ['rev-parse', '--show-toplevel']);\n return output.trim();\n}\n\nexport async function resolveMergeBase(repoRoot: string, baseRef: string): Promise<string> {\n const output = await runGit(repoRoot, ['merge-base', baseRef, 'HEAD']);\n return output.trim();\n}\n\n/**\n * Files whose working-tree content differs from the merge-base commit (staged or not), with\n * rename detection, plus untracked (non-ignored) files as additions. Copies gate like additions:\n * only the new path is measured against the new-code thresholds.\n */\nexport async function listChangedFiles(repoRoot: string, mergeBase: string): Promise<ChangedFile[]> {\n const [diffOutput, untrackedOutput] = await Promise.all([\n runGit(repoRoot, ['diff', '--name-status', '--find-renames', '-z', mergeBase]),\n runGit(repoRoot, ['ls-files', '--others', '--exclude-standard', '-z']),\n ]);\n\n const files = parseNameStatusEntries(diffOutput);\n for (const path of untrackedOutput.split('\\0')) {\n if (path !== '') {\n files.push({ status: 'added', headPath: path });\n }\n }\n return files;\n}\n\n/** Parses `git diff --name-status -z` output: `<status>\\0<path>\\0`, with two paths for R/C. */\nfunction parseNameStatusEntries(output: string): ChangedFile[] {\n const files: ChangedFile[] = [];\n const parts = output.split('\\0');\n let index = 0;\n while (index < parts.length - 1) {\n const kind = (parts[index] as string).charAt(0);\n const pathCount = kind === 'R' || kind === 'C' ? 2 : 1;\n const changed = toChangedFile(kind, parts.slice(index + 1, index + 1 + pathCount));\n if (changed) {\n files.push(changed);\n }\n index += 1 + pathCount;\n }\n return files;\n}\n\n/** Undefined for unmerged (U) and unknown statuses, which have nothing to gate. */\nfunction toChangedFile(kind: string, paths: (string | undefined)[]): ChangedFile | undefined {\n const [first, second] = paths;\n if (first === undefined) {\n return undefined;\n }\n switch (kind) {\n case 'R': {\n return second === undefined ? undefined : { status: 'renamed', basePath: first, headPath: second };\n }\n // A copy gates like an addition: only the new path is measured against the new-code thresholds.\n case 'C': {\n return second === undefined ? undefined : { status: 'added', headPath: second };\n }\n case 'A': {\n return { status: 'added', headPath: first };\n }\n case 'D': {\n return { status: 'deleted', basePath: first, headPath: first };\n }\n case 'M':\n case 'T': {\n return { status: 'modified', basePath: first, headPath: first };\n }\n default: {\n return undefined;\n }\n }\n}\n\n/**\n * Repository-relative paths git considers part of the project AND that exist in the working tree:\n * tracked files (minus worktree deletions) plus untracked non-ignored ones. The diff gate scans\n * exactly these, so local ignored artifacts (build output, generated copies) are neither measured\n * nor allowed to skew the duplication universes.\n */\nexport async function listRepositoryFiles(repoRoot: string): Promise<Set<string>> {\n const [listed, deleted] = await Promise.all([\n runGit(repoRoot, ['ls-files', '--cached', '--others', '--exclude-standard', '-z']),\n runGit(repoRoot, ['ls-files', '--deleted', '-z']),\n ]);\n const files = new Set(listed.split('\\0').filter((path) => path !== ''));\n for (const path of deleted.split('\\0')) {\n files.delete(path);\n }\n return files;\n}\n\n/** Paths that are symbolic links (blob mode 120000) at the given revision. */\nexport async function listSymlinkPathsAtRevision(repoRoot: string, revision: string): Promise<Set<string>> {\n const output = await runGit(repoRoot, ['ls-tree', '-r', '-z', revision]);\n const links = new Set<string>();\n for (const entry of output.split('\\0')) {\n const tabIndex = entry.indexOf('\\t');\n if (entry.startsWith('120000 ') && tabIndex !== -1) {\n links.add(entry.slice(tabIndex + 1));\n }\n }\n return links;\n}\n\n/**\n * The line hunks turning the merge-base blob at `basePath` into the working-tree file at\n * `headPath` (equal unless renamed), as `git diff` computes them for review.\n */\nexport async function listLineHunks(\n repoRoot: string,\n mergeBase: string,\n basePath: string,\n headPath: string\n): Promise<LineHunk[]> {\n // Literal pathspecs: a name like `app/[slug]/page.tsx` must not glob-match other files.\n const paths = (basePath === headPath ? [headPath] : [basePath, headPath]).map((file) => `:(literal)${file}`);\n const output = await runGit(repoRoot, [\n // The algorithm and indent heuristic decide which lines count as added, so user config must\n // not change them; `-c` values an older git does not know are ignored rather than rejected.\n '-c',\n 'diff.algorithm=myers',\n '-c',\n 'diff.indentHeuristic=true',\n 'diff',\n '--unified=0',\n // diff.interHunkContext would otherwise merge nearby hunks, and the unchanged lines between\n // them would count as added.\n '--inter-hunk-context=0',\n // A file git would show as binary (a `-diff` attribute, a NUL byte) must still yield hunks.\n '--text',\n '--no-color',\n '--no-ext-diff',\n '--no-textconv',\n '--find-renames',\n mergeBase,\n '--',\n ...paths,\n ]);\n const hunks: LineHunk[] = [];\n for (const match of output.matchAll(/^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@/gmu)) {\n const [, baseStart, baseCount, headStart, headCount] = match;\n hunks.push({\n baseStart: Number(baseStart),\n baseCount: baseCount === undefined ? 1 : Number(baseCount),\n headStart: Number(headStart),\n headCount: headCount === undefined ? 1 : Number(headCount),\n });\n }\n return hunks;\n}\n\n/** A file's `linguist-generated` / `linguist-vendored` attributes; undefined when unspecified. */\nexport interface LinguistAttributes {\n generated?: boolean;\n vendored?: boolean;\n}\n\n/**\n * The linguist attributes of repository-relative paths, from the working tree's `.gitattributes`\n * or, with `source`, from that revision's. Paths with neither attribute specified are omitted.\n */\nexport async function readLinguistAttributes(\n repoRoot: string,\n paths: Iterable<string>,\n source?: string\n): Promise<Map<string, LinguistAttributes>> {\n const input = [...paths].join('\\0');\n if (input === '') {\n return new Map();\n }\n const attributeArgs = ['--stdin', '-z', 'linguist-generated', 'linguist-vendored'];\n let output;\n if (source === undefined) {\n output = await runGitWithInput(repoRoot, ['check-attr', ...attributeArgs], input);\n } else {\n // `check-attr --source` needs git 2.40, so the revision's attributes are read from a\n // throwaway index holding its tree instead, which `--cached` supports on any git.\n const indexDirectory = await mkdtemp(path.join(os.tmpdir(), 'code-gauge-index-'));\n try {\n const env = { ...process.env, GIT_INDEX_FILE: path.join(indexDirectory, 'index') };\n await runGitWithInput(repoRoot, ['read-tree', source], '', env);\n output = await runGitWithInput(repoRoot, ['check-attr', '--cached', ...attributeArgs], input, env);\n } finally {\n await rm(indexDirectory, { recursive: true, force: true });\n }\n }\n const attributesByPath = new Map<string, LinguistAttributes>();\n const fields = output.split('\\0');\n for (let index = 0; index + 2 < fields.length; index += 3) {\n const [file, attribute, value] = fields.slice(index, index + 3) as [string, string, string];\n const enabled = toAttributeFlag(value);\n if (enabled === undefined) {\n continue;\n }\n const attributes = attributesByPath.get(file) ?? {};\n if (attribute === 'linguist-generated') {\n attributes.generated = enabled;\n } else {\n attributes.vendored = enabled;\n }\n attributesByPath.set(file, attributes);\n }\n return attributesByPath;\n}\n\n/** Linguist reads `attr`/`attr=true` as set and `-attr`/`attr=false` as explicitly unset. */\nfunction toAttributeFlag(value: string): boolean | undefined {\n if (value === 'set' || value === 'true') {\n return true;\n }\n if (value === 'unset' || value === 'false') {\n return false;\n }\n return undefined;\n}\n\n/** The file's content at the given commit; the path is repository-relative with forward slashes. */\nexport async function readFileAtRevision(repoRoot: string, revision: string, path: string): Promise<string> {\n return await runGit(repoRoot, ['cat-file', 'blob', `${revision}:${path}`]);\n}\n\nasync function runGitWithInput(\n cwd: string,\n args: string[],\n input: string,\n env: NodeJS.ProcessEnv = process.env\n): Promise<string> {\n return await new Promise((resolve, reject) => {\n const child = spawn('git', args, { cwd, env });\n // git exiting before it reads all input (e.g. on a usage error) makes the write fail with\n // EPIPE; its exit code and stderr, reported on close, already describe the failure.\n child.stdin.on('error', () => {});\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) {\n resolve(Buffer.concat(stdout).toString('utf8'));\n return;\n }\n const message = Buffer.concat(stderr).toString('utf8').trim();\n reject(new Error(`git ${describeCommand(args)} failed${message ? `: ${message}` : ''}`));\n });\n child.stdin.end(input);\n });\n}\n\nasync function runGit(cwd: string, args: string[]): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, { cwd, maxBuffer: maxOutputBytes, encoding: 'utf8' });\n return stdout;\n } catch (error) {\n const stderr = (error as { stderr?: string }).stderr?.trim();\n throw new Error(`git ${describeCommand(args)} failed${stderr ? `: ${stderr}` : ''}`);\n }\n}\n\n/** The subcommand and its first argument, skipping leading `-c key=value` overrides. */\nfunction describeCommand(args: readonly string[]): string {\n let start = 0;\n while (args[start] === '-c') {\n start += 2;\n }\n return args.slice(start, start + 2).join(' ');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"git.js","names":[],"sources":["../src/git.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\n/** Large enough for whole-file blobs; execFile's 1 MiB default truncates real sources. */\nconst maxOutputBytes = 512 * 1024 * 1024;\n\n/** The head side of one region of a line diff: lines [headStart, headStart + headCount); a pure deletion has headCount 0. */\nexport interface LineHunk {\n headStart: number;\n headCount: number;\n}\n\n/** One entry of the working tree's diff against the merge-base commit. */\nexport interface ChangedFile {\n status: 'added' | 'modified' | 'deleted' | 'renamed';\n /** Path at the merge-base, absent for added files; differs from headPath only for renames. */\n basePath?: string;\n /** Path in the working tree; for deleted files, the (now missing) base path. */\n headPath: string;\n}\n\nexport async function resolveRepoRoot(directory: string): Promise<string> {\n const output = await runGit(directory, ['rev-parse', '--show-toplevel']);\n return output.trim();\n}\n\n/** Whether git ignores the path or a directory above it (`git check-ignore` exits 1 otherwise). */\nexport async function isIgnored(repoRoot: string, path: string): Promise<boolean> {\n try {\n await execFileAsync('git', ['check-ignore', '--quiet', '--', path], { cwd: repoRoot });\n return true;\n } catch (error) {\n if ((error as { code?: unknown }).code === 1) return false;\n throw error;\n }\n}\n\nexport async function resolveMergeBase(repoRoot: string, baseRef: string): Promise<string> {\n const output = await runGit(repoRoot, ['merge-base', baseRef, 'HEAD']);\n return output.trim();\n}\n\n/**\n * Files whose working-tree content differs from the merge-base commit (staged or not), with\n * rename detection, plus untracked (non-ignored) files as additions.\n */\nexport async function listChangedFiles(repoRoot: string, mergeBase: string): Promise<ChangedFile[]> {\n const [diffOutput, untrackedOutput] = await Promise.all([\n runGit(repoRoot, ['diff', '--name-status', '--find-renames', '-z', mergeBase]),\n runGit(repoRoot, ['ls-files', '--others', '--exclude-standard', '-z']),\n ]);\n\n const files = parseNameStatusEntries(diffOutput);\n for (const path of untrackedOutput.split('\\0')) {\n if (path !== '') {\n files.push({ status: 'added', headPath: path });\n }\n }\n return files;\n}\n\n/** Parses `git diff --name-status -z` output: `<status>\\0<path>\\0`, with two paths for R/C. */\nfunction parseNameStatusEntries(output: string): ChangedFile[] {\n const files: ChangedFile[] = [];\n const parts = output.split('\\0');\n let index = 0;\n while (index < parts.length - 1) {\n const kind = (parts[index] as string).charAt(0);\n const pathCount = kind === 'R' || kind === 'C' ? 2 : 1;\n const changed = toChangedFile(kind, parts.slice(index + 1, index + 1 + pathCount));\n if (changed) {\n files.push(changed);\n }\n index += 1 + pathCount;\n }\n return files;\n}\n\n/** Undefined for unmerged (U) and unknown statuses, which have nothing to check. */\nfunction toChangedFile(kind: string, paths: (string | undefined)[]): ChangedFile | undefined {\n const [first, second] = paths;\n if (first === undefined) {\n return undefined;\n }\n switch (kind) {\n case 'R': {\n return second === undefined ? undefined : { status: 'renamed', basePath: first, headPath: second };\n }\n // A copy is an addition: every line of the new path is new.\n case 'C': {\n return second === undefined ? undefined : { status: 'added', headPath: second };\n }\n case 'A': {\n return { status: 'added', headPath: first };\n }\n case 'D': {\n return { status: 'deleted', basePath: first, headPath: first };\n }\n case 'M':\n case 'T': {\n return { status: 'modified', basePath: first, headPath: first };\n }\n default: {\n return undefined;\n }\n }\n}\n\n/**\n * Repository-relative paths git considers part of the project AND that exist in the working tree:\n * tracked files (minus worktree deletions) plus untracked non-ignored ones. `check` scans\n * exactly these, so local ignored artifacts (build output, generated copies) are neither measured\n * nor allowed to skew duplication detection.\n */\nexport async function listRepositoryFiles(repoRoot: string): Promise<Set<string>> {\n const [listed, deleted] = await Promise.all([\n runGit(repoRoot, ['ls-files', '--cached', '--others', '--exclude-standard', '-z']),\n runGit(repoRoot, ['ls-files', '--deleted', '-z']),\n ]);\n const files = new Set(listed.split('\\0').filter((path) => path !== ''));\n for (const path of deleted.split('\\0')) {\n files.delete(path);\n }\n return files;\n}\n\n/**\n * The line hunks turning the merge-base blob at `basePath` into the working-tree file at\n * `headPath` (equal unless renamed), as `git diff` computes them for review.\n */\nexport async function listLineHunks(\n repoRoot: string,\n mergeBase: string,\n basePath: string,\n headPath: string\n): Promise<LineHunk[]> {\n // Literal pathspecs: a name like `app/[slug]/page.tsx` must not glob-match other files.\n const paths = (basePath === headPath ? [headPath] : [basePath, headPath]).map((file) => `:(literal)${file}`);\n const output = await runGit(repoRoot, [\n // The algorithm and indent heuristic decide which lines count as added, so user config must\n // not change them; `-c` values an older git does not know are ignored rather than rejected.\n '-c',\n 'diff.algorithm=myers',\n '-c',\n 'diff.indentHeuristic=true',\n 'diff',\n '--unified=0',\n // diff.interHunkContext would otherwise merge nearby hunks, and the unchanged lines between\n // them would count as added.\n '--inter-hunk-context=0',\n // A file git would show as binary (a `-diff` attribute, a NUL byte) must still yield hunks.\n '--text',\n '--no-color',\n '--no-ext-diff',\n '--no-textconv',\n '--find-renames',\n mergeBase,\n '--',\n ...paths,\n ]);\n const hunks: LineHunk[] = [];\n for (const match of output.matchAll(/^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,(\\d+))? @@/gmu)) {\n const [, headStart, headCount] = match;\n hunks.push({\n headStart: Number(headStart),\n headCount: headCount === undefined ? 1 : Number(headCount),\n });\n }\n return hunks;\n}\n\n/** A file's `linguist-generated` / `linguist-vendored` attributes; undefined when unspecified. */\nexport interface LinguistAttributes {\n generated?: boolean;\n vendored?: boolean;\n}\n\n/**\n * The linguist attributes of repository-relative paths, from the working tree's `.gitattributes`.\n * Paths with neither attribute specified are omitted.\n */\nexport async function readLinguistAttributes(\n repoRoot: string,\n paths: Iterable<string>\n): Promise<Map<string, LinguistAttributes>> {\n const input = [...paths].join('\\0');\n if (input === '') {\n return new Map();\n }\n const output = await runGitWithInput(\n repoRoot,\n ['check-attr', '--stdin', '-z', 'linguist-generated', 'linguist-vendored'],\n input\n );\n const attributesByPath = new Map<string, LinguistAttributes>();\n const fields = output.split('\\0');\n for (let index = 0; index + 2 < fields.length; index += 3) {\n const [file, attribute, value] = fields.slice(index, index + 3) as [string, string, string];\n const enabled = toAttributeFlag(value);\n if (enabled === undefined) {\n continue;\n }\n const attributes = attributesByPath.get(file) ?? {};\n if (attribute === 'linguist-generated') {\n attributes.generated = enabled;\n } else {\n attributes.vendored = enabled;\n }\n attributesByPath.set(file, attributes);\n }\n return attributesByPath;\n}\n\n/** Linguist reads `attr`/`attr=true` as set and `-attr`/`attr=false` as explicitly unset. */\nfunction toAttributeFlag(value: string): boolean | undefined {\n if (value === 'set' || value === 'true') {\n return true;\n }\n if (value === 'unset' || value === 'false') {\n return false;\n }\n return undefined;\n}\n\nasync function runGitWithInput(cwd: string, args: string[], input: string): Promise<string> {\n return await new Promise((resolve, reject) => {\n const child = spawn('git', args, { cwd });\n // git exiting before it reads all input (e.g. on a usage error) makes the write fail with\n // EPIPE; its exit code and stderr, reported on close, already describe the failure.\n child.stdin.on('error', () => {});\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) {\n resolve(Buffer.concat(stdout).toString('utf8'));\n return;\n }\n const message = Buffer.concat(stderr).toString('utf8').trim();\n reject(new Error(`git ${describeCommand(args)} failed${message ? `: ${message}` : ''}`));\n });\n child.stdin.end(input);\n });\n}\n\nasync function runGit(cwd: string, args: string[]): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, { cwd, maxBuffer: maxOutputBytes, encoding: 'utf8' });\n return stdout;\n } catch (error) {\n const stderr = (error as { stderr?: string }).stderr?.trim();\n throw new Error(`git ${describeCommand(args)} failed${stderr ? `: ${stderr}` : ''}`);\n }\n}\n\n/** The subcommand and its first argument, skipping leading `-c key=value` overrides. */\nfunction describeCommand(args: readonly string[]): string {\n let start = 0;\n while (args[start] === '-c') {\n start += 2;\n }\n return args.slice(start, start + 2).join(' ');\n}\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./crossFileDuplication.cjs"),t=require("./languages.cjs"),n=require("./metrics.cjs"),r=require("./newDuplication.cjs"),i=require("./regressionGate.cjs");exports.TreeMeasurer=n.TreeMeasurer,exports.collectCrossFileDuplicationFileData=n.collectCrossFileDuplicationFileData,exports.collectDuplicationCandidates=n.collectDuplicationCandidates,exports.collectFunctionTokenSequences=n.collectFunctionTokenSequences,exports.defaultGateOptions=i.defaultGateOptions,exports.defaultLanguages=t.defaultLanguages,exports.defaultMeasurer=n.defaultMeasurer,exports.detectLanguage=t.detectLanguage,exports.evaluateRegressionGate=i.evaluateRegressionGate,exports.findNewlyDuplicatedLines=r.findNewlyDuplicatedLines,exports.measureCode=n.measureCode,exports.measureCrossFileDuplication=e.measureCrossFileDuplication,exports.supportedLanguages=t.supportedLanguages;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./crossFileDuplication.cjs"),t=require("./languages.cjs"),n=require("./metrics.cjs");exports.TreeMeasurer=n.TreeMeasurer,exports.collectCrossFileDuplicationFileData=n.collectCrossFileDuplicationFileData,exports.collectDuplicationCandidates=n.collectDuplicationCandidates,exports.collectFunctionTokenSequences=n.collectFunctionTokenSequences,exports.defaultLanguages=t.defaultLanguages,exports.defaultMeasurer=n.defaultMeasurer,exports.detectLanguage=t.detectLanguage,exports.measureCode=n.measureCode,exports.measureCrossFileDuplication=e.measureCrossFileDuplication,exports.supportedLanguages=t.supportedLanguages;
package/dist/index.d.ts CHANGED
@@ -3,8 +3,4 @@ export type { CrossFileDuplicateBlockGroup, CrossFileDuplicateOccurrence, CrossF
3
3
  export type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';
4
4
  export { defaultLanguages, detectLanguage, supportedLanguages } from './languages.js';
5
5
  export { TreeMeasurer, collectCrossFileDuplicationFileData, collectDuplicationCandidates, collectFunctionTokenSequences, defaultMeasurer, measureCode, } from './metrics.js';
6
- export { findNewlyDuplicatedLines } from './newDuplication.js';
7
- export type { CloneOccurrence, DuplicationChange, LineHunk } from './newDuplication.js';
8
- export { defaultGateOptions, evaluateRegressionGate } from './regressionGate.js';
9
- export type { GateFileInput, GateOptions, GateResult, GateTolerances, GateViolation, NewFunctionThresholds, } from './regressionGate.js';
10
6
  export type { CodeMetrics, DuplicationMetrics, DuplicationOptions, FunctionMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, LineMetrics, MeasureOptions, SupportedLanguage, } from './types.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{measureCrossFileDuplication as e}from"./crossFileDuplication.js";import{defaultLanguages as t,detectLanguage as n,supportedLanguages as r}from"./languages.js";import{TreeMeasurer as i,collectCrossFileDuplicationFileData as a,collectDuplicationCandidates as o,collectFunctionTokenSequences as s,defaultMeasurer as c,measureCode as l}from"./metrics.js";import{findNewlyDuplicatedLines as u}from"./newDuplication.js";import{defaultGateOptions as d,evaluateRegressionGate as f}from"./regressionGate.js";export{i as TreeMeasurer,a as collectCrossFileDuplicationFileData,o as collectDuplicationCandidates,s as collectFunctionTokenSequences,d as defaultGateOptions,t as defaultLanguages,c as defaultMeasurer,n as detectLanguage,f as evaluateRegressionGate,u as findNewlyDuplicatedLines,l as measureCode,e as measureCrossFileDuplication,r as supportedLanguages};
1
+ import{measureCrossFileDuplication as e}from"./crossFileDuplication.js";import{defaultLanguages as t,detectLanguage as n,supportedLanguages as r}from"./languages.js";import{TreeMeasurer as i,collectCrossFileDuplicationFileData as a,collectDuplicationCandidates as o,collectFunctionTokenSequences as s,defaultMeasurer as c,measureCode as l}from"./metrics.js";export{i as TreeMeasurer,a as collectCrossFileDuplicationFileData,o as collectDuplicationCandidates,s as collectFunctionTokenSequences,t as defaultLanguages,c as defaultMeasurer,n as detectLanguage,l as measureCode,e as measureCrossFileDuplication,r as supportedLanguages};
package/dist/metrics.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./languages.cjs"),t=require("./nativeMetrics.cjs");var n=class{registry=e.createLanguageRegistry();getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,n){let r=this.resolveLanguage(n.language),i=n.includeSyntaxTree??!1;return a(t.measureCodeNative(e,r.name,i,n.duplication),i)}measureWithCrossFileData(e,n){let i=this.resolveLanguage(n.language),a=n.includeSyntaxTree??!1;return r(t.measureCodeNative(e,i.name,a,n.duplication,!0),a)}async measureWithCrossFileDataAsync(e,n){let i=this.resolveLanguage(n.language),a=n.includeSyntaxTree??!1;return r(await t.measureCodeNativeAsync(e,i.name,a,n.duplication,!0),a)}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.resolveLanguage(n.language);return i(t.collectCrossFileDataNative(e,r.name,n.duplication?.minTokens))}collectFunctionTokenSequences(e,n){let r=this.resolveLanguage(n.language);return t.collectFunctionTokenSequencesNative(e,r.name)}resolveLanguage(e){let t=this.registry.get(e);if(!t)throw Error(`Unsupported language: ${e}`);return t}};function r(e,t){if(!e.crossFileData)throw Error(`The native addon returned no cross-file duplication data`);return{metrics:a(e,t),crossFileData:i(e.crossFileData)}}function i(e){return{candidates:e.candidates,tokens:e.tokens,containerStatements:e.containerStatements,nearMissBlocks:e.nearMissBlocks,codeLineNumbers:new Set(e.codeLineNumbers)}}function a(e,t){return{language:e.language,bytes:e.bytes,lines:e.lines,functions:e.functions.map(e=>({name:e.name,nodeType:e.nodeType,startLine:e.startLine,startColumn:e.startColumn,endLine:e.endLine,endColumn:e.endColumn,cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,nestingDepth:e.nestingDepth,ncss:e.ncss,parameterCount:e.parameterCount,halstead:o(e.halsteadCounts),depDegree:e.depDegree})),cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:o(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function o(e){let{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i}=e,a=t+n,o=r+i,s=a===0?0:o*Math.log2(a);return{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i,vocabulary:a,length:o,volume:s,effort:(n===0?0:t/2*(i/n))*s}}const s=new n;function c(e,t){return s.measure(e,t)}function l(e,t){return s.measureWithCrossFileDataAsync(e,t)}function u(e,t){return s.collectDuplicationCandidates(e,t)}function d(e,t){return s.collectFunctionTokenSequences(e,t)}function f(e,t){return s.collectCrossFileDuplicationFileData(e,t)}exports.TreeMeasurer=n,exports.collectCrossFileDuplicationFileData=f,exports.collectDuplicationCandidates=u,exports.collectFunctionTokenSequences=d,exports.defaultMeasurer=s,exports.measureCode=c,exports.measureCodeWithCrossFileDataAsync=l;
1
+ "use strict";const e=require("./languages.cjs"),t=require("./nativeMetrics.cjs");var n=class{registry=e.createLanguageRegistry();getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,n){let r=this.resolveLanguage(n.language),i=n.includeSyntaxTree??!1;return a(t.measureCodeNative(e,r.name,i,n.duplication),i)}measureWithCrossFileData(e,n){let i=this.resolveLanguage(n.language),a=n.includeSyntaxTree??!1;return r(t.measureCodeNative(e,i.name,a,n.duplication,!0),a)}async measureWithCrossFileDataAsync(e,n){let i=this.resolveLanguage(n.language),a=n.includeSyntaxTree??!1;return r(await t.measureCodeNativeAsync(e,i.name,a,n.duplication,!0),a)}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.resolveLanguage(n.language);return i(t.collectCrossFileDataNative(e,r.name,n.duplication?.minTokens))}collectFunctionTokenSequences(e,n){let r=this.resolveLanguage(n.language);return t.collectFunctionTokenSequencesNative(e,r.name)}resolveLanguage(e){let t=this.registry.get(e);if(!t)throw Error(`Unsupported language: ${e}`);return t}};function r(e,t){if(!e.crossFileData)throw Error(`The native addon returned no cross-file duplication data`);return{metrics:a(e,t),crossFileData:i(e.crossFileData)}}function i(e){return{candidates:e.candidates,tokens:e.tokens,containerStatements:e.containerStatements,nearMissBlocks:e.nearMissBlocks,codeLineNumbers:new Set(e.codeLineNumbers)}}function a(e,t){return{language:e.language,bytes:e.bytes,lines:e.lines,functions:e.functions.map(e=>({name:e.name,nodeType:e.nodeType,startLine:e.startLine,startColumn:e.startColumn,endLine:e.endLine,endColumn:e.endColumn,cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,nestingDepth:e.nestingDepth,ncss:e.ncss,parameterCount:e.parameterCount,halstead:o(e.halsteadCounts),depDegree:e.depDegree})),cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:o(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function o(e){let{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i}=e,a=t+n,o=r+i,s=a===0?0:o*Math.log2(a),c=n===0?0:t/2*(i/n);return{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i,vocabulary:a,length:o,volume:s,difficulty:c,effort:c*s}}const s=new n;function c(e,t){return s.measure(e,t)}function l(e,t){return s.measureWithCrossFileDataAsync(e,t)}function u(e,t){return s.collectDuplicationCandidates(e,t)}function d(e,t){return s.collectFunctionTokenSequences(e,t)}function f(e,t){return s.collectCrossFileDuplicationFileData(e,t)}exports.TreeMeasurer=n,exports.collectCrossFileDuplicationFileData=f,exports.collectDuplicationCandidates=u,exports.collectFunctionTokenSequences=d,exports.defaultMeasurer=s,exports.measureCode=c,exports.measureCodeWithCrossFileDataAsync=l;
2
2
  //# sourceMappingURL=metrics.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.cjs","names":["createLanguageRegistry","measureCodeNative","measureCodeNativeAsync","collectCrossFileDataNative","collectFunctionTokenSequencesNative"],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n measureCodeNativeAsync,\n type NativeCrossFileDataPayload,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Measures a file and collects its cross-file clone-detection contribution from one parse. */\n measureWithCrossFileData(\n code: string,\n options: MeasureOptions\n ): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** measureWithCrossFileData on the native addon's worker threads, for measuring files in parallel. */\n async measureWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n ): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = await measureCodeNativeAsync(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n return toCrossFileDuplicationFileData(\n collectCrossFileDataNative(code, language.name, options.duplication?.minTokens)\n );\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim, so the regression gate can re-match renamed or\n * moved functions across two revisions by token-LCS similarity.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\nfunction assembleMetricsWithCrossFileData(\n payload: NativeMetricsPayload,\n includeSyntaxTree: boolean\n): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n if (!payload.crossFileData) {\n throw new Error('The native addon returned no cross-file duplication data');\n }\n return {\n metrics: assembleNativeMetrics(payload, includeSyntaxTree),\n crossFileData: toCrossFileDuplicationFileData(payload.crossFileData),\n };\n}\n\nfunction toCrossFileDuplicationFileData(payload: NativeCrossFileDataPayload): CrossFileDuplicationFileData {\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n nearMissBlocks: payload.nearMissBlocks,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n endColumn: fn.endColumn,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cyclomaticComplexity: payload.cyclomaticComplexity,\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring TreeMeasurer.measureWithCrossFileDataAsync for the default measurer. */\nexport function measureCodeWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n return defaultMeasurer.measureWithCrossFileDataAsync(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"metrics.cjs","names":["createLanguageRegistry","measureCodeNative","measureCodeNativeAsync","collectCrossFileDataNative","collectFunctionTokenSequencesNative"],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n measureCodeNativeAsync,\n type NativeCrossFileDataPayload,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Measures a file and collects its cross-file clone-detection contribution from one parse. */\n measureWithCrossFileData(\n code: string,\n options: MeasureOptions\n ): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** measureWithCrossFileData on the native addon's worker threads, for measuring files in parallel. */\n async measureWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n ): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = await measureCodeNativeAsync(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n return toCrossFileDuplicationFileData(\n collectCrossFileDataNative(code, language.name, options.duplication?.minTokens)\n );\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\nfunction assembleMetricsWithCrossFileData(\n payload: NativeMetricsPayload,\n includeSyntaxTree: boolean\n): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n if (!payload.crossFileData) {\n throw new Error('The native addon returned no cross-file duplication data');\n }\n return {\n metrics: assembleNativeMetrics(payload, includeSyntaxTree),\n crossFileData: toCrossFileDuplicationFileData(payload.crossFileData),\n };\n}\n\nfunction toCrossFileDuplicationFileData(payload: NativeCrossFileDataPayload): CrossFileDuplicationFileData {\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n nearMissBlocks: payload.nearMissBlocks,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n endColumn: fn.endColumn,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cyclomaticComplexity: payload.cyclomaticComplexity,\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n difficulty,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring TreeMeasurer.measureWithCrossFileDataAsync for the default measurer. */\nexport function measureCodeWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n return defaultMeasurer.measureWithCrossFileDataAsync(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"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"}
package/dist/metrics.d.ts CHANGED
@@ -29,8 +29,7 @@ export declare class TreeMeasurer {
29
29
  /**
30
30
  * Normalized token hash sequences of every function, index-parallel to the functions array of
31
31
  * measure(): identifiers are anonymized by first occurrence within the function, literals by
32
- * kind, and keywords/operators kept verbatim, so the regression gate can re-match renamed or
33
- * moved functions across two revisions by token-LCS similarity.
32
+ * kind, and keywords/operators kept verbatim.
34
33
  */
35
34
  collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[];
36
35
  private resolveLanguage;
package/dist/metrics.js CHANGED
@@ -1,2 +1,2 @@
1
- import{createLanguageRegistry as e}from"./languages.js";import{collectCrossFileDataNative as t,collectFunctionTokenSequencesNative as n,measureCodeNative as r,measureCodeNativeAsync as i}from"./nativeMetrics.js";var a=class{registry=e();getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,t){let n=this.resolveLanguage(t.language),i=t.includeSyntaxTree??!1;return c(r(e,n.name,i,t.duplication),i)}measureWithCrossFileData(e,t){let n=this.resolveLanguage(t.language),i=t.includeSyntaxTree??!1;return o(r(e,n.name,i,t.duplication,!0),i)}async measureWithCrossFileDataAsync(e,t){let n=this.resolveLanguage(t.language),r=t.includeSyntaxTree??!1;return o(await i(e,n.name,r,t.duplication,!0),r)}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.resolveLanguage(n.language);return s(t(e,r.name,n.duplication?.minTokens))}collectFunctionTokenSequences(e,t){let r=this.resolveLanguage(t.language);return n(e,r.name)}resolveLanguage(e){let t=this.registry.get(e);if(!t)throw Error(`Unsupported language: ${e}`);return t}};function o(e,t){if(!e.crossFileData)throw Error(`The native addon returned no cross-file duplication data`);return{metrics:c(e,t),crossFileData:s(e.crossFileData)}}function s(e){return{candidates:e.candidates,tokens:e.tokens,containerStatements:e.containerStatements,nearMissBlocks:e.nearMissBlocks,codeLineNumbers:new Set(e.codeLineNumbers)}}function c(e,t){return{language:e.language,bytes:e.bytes,lines:e.lines,functions:e.functions.map(e=>({name:e.name,nodeType:e.nodeType,startLine:e.startLine,startColumn:e.startColumn,endLine:e.endLine,endColumn:e.endColumn,cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,nestingDepth:e.nestingDepth,ncss:e.ncss,parameterCount:e.parameterCount,halstead:l(e.halsteadCounts),depDegree:e.depDegree})),cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:l(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function l(e){let{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i}=e,a=t+n,o=r+i,s=a===0?0:o*Math.log2(a);return{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i,vocabulary:a,length:o,volume:s,effort:(n===0?0:t/2*(i/n))*s}}const u=new a;function d(e,t){return u.measure(e,t)}function f(e,t){return u.measureWithCrossFileDataAsync(e,t)}function p(e,t){return u.collectDuplicationCandidates(e,t)}function m(e,t){return u.collectFunctionTokenSequences(e,t)}function h(e,t){return u.collectCrossFileDuplicationFileData(e,t)}export{a as TreeMeasurer,h as collectCrossFileDuplicationFileData,p as collectDuplicationCandidates,m as collectFunctionTokenSequences,u as defaultMeasurer,d as measureCode,f as measureCodeWithCrossFileDataAsync};
1
+ import{createLanguageRegistry as e}from"./languages.js";import{collectCrossFileDataNative as t,collectFunctionTokenSequencesNative as n,measureCodeNative as r,measureCodeNativeAsync as i}from"./nativeMetrics.js";var a=class{registry=e();getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,t){let n=this.resolveLanguage(t.language),i=t.includeSyntaxTree??!1;return c(r(e,n.name,i,t.duplication),i)}measureWithCrossFileData(e,t){let n=this.resolveLanguage(t.language),i=t.includeSyntaxTree??!1;return o(r(e,n.name,i,t.duplication,!0),i)}async measureWithCrossFileDataAsync(e,t){let n=this.resolveLanguage(t.language),r=t.includeSyntaxTree??!1;return o(await i(e,n.name,r,t.duplication,!0),r)}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.resolveLanguage(n.language);return s(t(e,r.name,n.duplication?.minTokens))}collectFunctionTokenSequences(e,t){let r=this.resolveLanguage(t.language);return n(e,r.name)}resolveLanguage(e){let t=this.registry.get(e);if(!t)throw Error(`Unsupported language: ${e}`);return t}};function o(e,t){if(!e.crossFileData)throw Error(`The native addon returned no cross-file duplication data`);return{metrics:c(e,t),crossFileData:s(e.crossFileData)}}function s(e){return{candidates:e.candidates,tokens:e.tokens,containerStatements:e.containerStatements,nearMissBlocks:e.nearMissBlocks,codeLineNumbers:new Set(e.codeLineNumbers)}}function c(e,t){return{language:e.language,bytes:e.bytes,lines:e.lines,functions:e.functions.map(e=>({name:e.name,nodeType:e.nodeType,startLine:e.startLine,startColumn:e.startColumn,endLine:e.endLine,endColumn:e.endColumn,cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,nestingDepth:e.nestingDepth,ncss:e.ncss,parameterCount:e.parameterCount,halstead:l(e.halsteadCounts),depDegree:e.depDegree})),cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:l(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function l(e){let{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i}=e,a=t+n,o=r+i,s=a===0?0:o*Math.log2(a),c=n===0?0:t/2*(i/n);return{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i,vocabulary:a,length:o,volume:s,difficulty:c,effort:c*s}}const u=new a;function d(e,t){return u.measure(e,t)}function f(e,t){return u.measureWithCrossFileDataAsync(e,t)}function p(e,t){return u.collectDuplicationCandidates(e,t)}function m(e,t){return u.collectFunctionTokenSequences(e,t)}function h(e,t){return u.collectCrossFileDuplicationFileData(e,t)}export{a as TreeMeasurer,h as collectCrossFileDuplicationFileData,p as collectDuplicationCandidates,m as collectFunctionTokenSequences,u as defaultMeasurer,d as measureCode,f as measureCodeWithCrossFileDataAsync};
2
2
  //# sourceMappingURL=metrics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n measureCodeNativeAsync,\n type NativeCrossFileDataPayload,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Measures a file and collects its cross-file clone-detection contribution from one parse. */\n measureWithCrossFileData(\n code: string,\n options: MeasureOptions\n ): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** measureWithCrossFileData on the native addon's worker threads, for measuring files in parallel. */\n async measureWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n ): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = await measureCodeNativeAsync(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n return toCrossFileDuplicationFileData(\n collectCrossFileDataNative(code, language.name, options.duplication?.minTokens)\n );\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim, so the regression gate can re-match renamed or\n * moved functions across two revisions by token-LCS similarity.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\nfunction assembleMetricsWithCrossFileData(\n payload: NativeMetricsPayload,\n includeSyntaxTree: boolean\n): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n if (!payload.crossFileData) {\n throw new Error('The native addon returned no cross-file duplication data');\n }\n return {\n metrics: assembleNativeMetrics(payload, includeSyntaxTree),\n crossFileData: toCrossFileDuplicationFileData(payload.crossFileData),\n };\n}\n\nfunction toCrossFileDuplicationFileData(payload: NativeCrossFileDataPayload): CrossFileDuplicationFileData {\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n nearMissBlocks: payload.nearMissBlocks,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n endColumn: fn.endColumn,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cyclomaticComplexity: payload.cyclomaticComplexity,\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring TreeMeasurer.measureWithCrossFileDataAsync for the default measurer. */\nexport function measureCodeWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n return defaultMeasurer.measureWithCrossFileDataAsync(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"metrics.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n measureCodeNativeAsync,\n type NativeCrossFileDataPayload,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Measures a file and collects its cross-file clone-detection contribution from one parse. */\n measureWithCrossFileData(\n code: string,\n options: MeasureOptions\n ): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** measureWithCrossFileData on the native addon's worker threads, for measuring files in parallel. */\n async measureWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n ): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = await measureCodeNativeAsync(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n return toCrossFileDuplicationFileData(\n collectCrossFileDataNative(code, language.name, options.duplication?.minTokens)\n );\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\nfunction assembleMetricsWithCrossFileData(\n payload: NativeMetricsPayload,\n includeSyntaxTree: boolean\n): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n if (!payload.crossFileData) {\n throw new Error('The native addon returned no cross-file duplication data');\n }\n return {\n metrics: assembleNativeMetrics(payload, includeSyntaxTree),\n crossFileData: toCrossFileDuplicationFileData(payload.crossFileData),\n };\n}\n\nfunction toCrossFileDuplicationFileData(payload: NativeCrossFileDataPayload): CrossFileDuplicationFileData {\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n nearMissBlocks: payload.nearMissBlocks,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n endColumn: fn.endColumn,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cyclomaticComplexity: payload.cyclomaticComplexity,\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n difficulty,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring TreeMeasurer.measureWithCrossFileDataAsync for the default measurer. */\nexport function measureCodeWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n return defaultMeasurer.measureWithCrossFileDataAsync(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"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"}
package/dist/scan.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./crossFileDuplication.cjs"),n=require("./languages.cjs"),r=require("./nativeMetrics.cjs"),i=require("./metrics.cjs");let a=require("@willbooster/shared-lib"),o=require("node:fs/promises"),s=require("node:path");s=e.__toESM(s,1);let c=require("node:os");c=e.__toESM(c,1);const l=/* @__PURE__ */ new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`obj`,`target`,`test-fixtures`,`vendor`,`venv`]),u=/* @__PURE__ */ new Set([`__tests__`,`test`,`tests`,`spec`]),d=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,f=/Tests?\.(?:java|kt|cs)$/u;function p(e){return e===`~`?c.default.homedir():e.startsWith(`~/`)?s.default.join(c.default.homedir(),e.slice(2)):s.default.resolve(e)}async function m(e){try{return(await(0,o.stat)(e)).isDirectory()?e:s.default.dirname(e)}catch{return s.default.dirname(e)}}const h=c.default.availableParallelism()*2;async function g(e,t){let n=e;try{n=await(0,o.realpath)(e)}catch{}let r=s.default.dirname(n),i;try{i=await(0,o.stat)(n)}catch(e){let t=`${L(n,r)}: ${(0,a.getErrorMessage)(e)}`;return{displayRoot:r,files:[],errors:[t],warnings:[],generatedFiles:[],fatalError:t}}if(i.isFile()){let e=s.default.dirname(n),r=I(n,t,!0);if(!r){let t=`${L(n,e)}: unsupported file type`;return{displayRoot:e,files:[],errors:[t],warnings:[],generatedFiles:[],fatalError:t}}let i=x(t,e);return await O(n,r,`single-file`,i,n),b(i,e)}let c=x(t,n);return await T(n,c),await v(c),b(c,n)}async function _(e,t,n,r=/* @__PURE__ */ new Set){let i=x(n,e);for(let a of t){let t=s.default.join(e,a),c=F(a,n,r.has(t))?I(a,n,r.has(t)):void 0;c&&((await(0,o.lstat)(t).catch(()=>{}))?.isSymbolicLink()||i.candidates.push({file:t,language:c}))}return await v(i),b(i,e)}async function v(e){let t=await e.options.loadExclusion?.(e.candidates.flatMap(({file:e,realFile:t})=>t===void 0?[e]:[e,t]));e.exclusion=t;let n=/* @__PURE__ */ new Map;for(let t of e.candidates){let r;try{r=t.realFile??await(0,o.realpath)(t.file)}catch(n){S(e,t.file,n);continue}n.set(r,[...n.get(r)??[],t])}for(let[r,i]of n){if(e.fatalSeen)return;let n=await y(r,i,t);n!==void 0&&await O(n.file,n.language,`directory`,e,r)}}async function y(e,t,n){if(n===void 0)return t[0];if(n.isExcludedPath(e))return;let r=t.filter(({file:e})=>!n.isExcludedPath(e)),i=r.find(({file:t})=>t===e);if(i!==void 0||r.length<=1)return i??r[0];let a=await(0,o.readFile)(e,`utf8`).catch(()=>{});return r.find(({file:e})=>a!==void 0&&!n.isGeneratedCode(e,a))??r[0]}async function b(e,t){let n=[],r=[],i=[],o=[];for(let s of e.outcomes){let e=await s;if(`fatal`in e){let s=(0,a.getErrorMessage)(e.fatal);return{displayRoot:t,files:n,errors:[...r,s],warnings:i,generatedFiles:o,fatalError:s}}if(`generatedFile`in e){o.push(e.generatedFile);continue}if(`error`in e){r.push(e.error);continue}n.push(e.file),e.warning!==void 0&&i.push(e.warning)}return{displayRoot:t,files:n,errors:r,warnings:i,generatedFiles:o}}function x(e,t){return{options:e,outcomes:[],inFlight:/* @__PURE__ */ new Set,fatalSeen:!1,visitedDirectories:/* @__PURE__ */ new Set,visitedFiles:/* @__PURE__ */ new Set,rootDirectory:t,candidates:[]}}function S(e,t,n){e.outcomes.push({error:`${L(t,e.rootDirectory)}: ${(0,a.getErrorMessage)(n)}`})}async function C(e,t,n){try{return await e()}catch(e){S(n,t,e);return}}async function w(e,t){let n=await C(()=>(0,o.realpath)(e),e,t);return n!==void 0&&P(n,t.rootDirectory)?n:void 0}async function T(e,t){let n=await w(e,t);if(n===void 0||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r=await C(()=>(0,o.readdir)(e,{withFileTypes:!0}),e,t);if(r!==void 0){r.sort((e,t)=>e.name<t.name?-1:+(e.name>t.name));for(let i of r){let r=s.default.join(e,i.name);if(i.isSymbolicLink()){await E(i.name,r,t);continue}if(i.isDirectory()){if(N(i.name,t.options))continue;await T(r,t);continue}i.isFile()&&D(r,t,r,n===e?void 0:s.default.join(n,i.name))}}}async function E(e,t,n){let r=await w(t,n);if(r===void 0)return;let i=await C(()=>(0,o.stat)(t),t,n);if(i!==void 0){if(i.isDirectory()){if(N(e,n.options)||N(s.default.basename(r),n.options))return;await T(t,n);return}i.isFile()&&D(t,n,r,r)}}function D(e,t,n=e,r){let i=I(n,t.options);i&&t.candidates.push({file:e,language:i,realFile:r})}async function O(e,t,n,r,i){if(r.fatalSeen)return;let a;try{a=i??await(0,o.realpath)(e)}catch(t){S(r,e,t);return}if(r.visitedFiles.has(a))return;for(r.visitedFiles.add(a);r.inFlight.size>=h;)await Promise.race(r.inFlight);let s=k(e,a,t,n,r);r.inFlight.add(s),s.then(()=>r.inFlight.delete(s)),r.outcomes.push(s)}async function k(e,t,n,s,c){try{let r=await(0,o.readFile)(e,`utf8`),a={language:n,duplication:c.options.duplication};if(s===`single-file`)return{file:{file:e,metrics:i.measureCode(r,a)}};let l=c.exclusion;if(l&&(l.isGeneratedCode(e,r)||l.isGeneratedCode(t,r)))return{generatedFile:e};let{metrics:u,crossFileData:d,crossFileError:f}=await A(r,a);return{file:{file:e,metrics:u,duplicationCandidates:d},warning:f===void 0?void 0:`${L(e,c.rootDirectory)}: cross-file duplication candidates unavailable: ${f}`}}catch(t){return t instanceof r.NativeAddonError?(c.fatalSeen=!0,{fatal:t}):{error:`${L(e,c.rootDirectory)}: ${(0,a.getErrorMessage)(t)}`}}}async function A(e,t){try{return await i.measureCodeWithCrossFileDataAsync(e,t)}catch(n){if(n instanceof r.NativeAddonError)throw n;return{metrics:i.measureCode(e,t),crossFileError:(0,a.getErrorMessage)(n)}}}function j(e,n){if(e.fatalError||e.files.length<2)return;let r=e.files.flatMap(({file:t,duplicationCandidates:n})=>n?[{file:L(t,e.displayRoot),...n}]:[]);r.length<2||(e.crossFileDuplication=t.measureCrossFileDuplication(r,n.duplication))}function M(e,t,n){let r=new Set(e?.duplication.duplicateLineNumbers),i=t&&Object.hasOwn(t.duplicateLineNumbersByFile,n)?t.duplicateLineNumbersByFile[n]??[]:[];for(let e of i)r.add(e);return r}function N(e,t){return l.has(e)?!0:!t.includeTests&&u.has(e)}function P(e,t){let n=s.default.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${s.default.sep}`)&&!s.default.isAbsolute(n)}function F(e,t,n=!1){if(n)return I(e,t,!0)!==void 0;let r=e.split(`/`);for(let e of r.slice(0,-1))if(l.has(e)||!t.includeTests&&u.has(e))return!1;return I(e,t)!==void 0}function I(e,t,r=!1){let i=e.toLowerCase();if(!(!r&&(i.endsWith(`.d.ts`)||i.endsWith(`.d.mts`)||i.endsWith(`.d.cts`)||i.endsWith(`.min.js`)||i.endsWith(`.pnp.cjs`)))&&(r||t.includeTests||!d.test(s.default.basename(e))&&!f.test(s.default.basename(e))))return n.detectLanguage(e)}function L(e,t){return s.default.relative(t,e)||s.default.basename(e)}function R(e){process.stdout.write(e)}function z(e){process.stderr.write(e)}exports.addCrossFileDuplication=j,exports.collectDuplicatedLineNumbers=M,exports.configSearchDirectory=m,exports.formatPath=L,exports.getLanguage=I,exports.isScannedPath=F,exports.measureWithCrossFileData=A,exports.resolveTarget=p,exports.scanListedFiles=_,exports.scanTarget=g,exports.writeStderr=z,exports.writeStdout=R;
1
+ "use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./crossFileDuplication.cjs"),n=require("./languages.cjs"),r=require("./nativeMetrics.cjs"),i=require("./metrics.cjs");let a=require("@willbooster/shared-lib"),o=require("node:fs/promises"),s=require("node:path");s=e.__toESM(s,1);let c=require("node:os");c=e.__toESM(c,1);const l=/* @__PURE__ */ new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`obj`,`target`,`test-fixtures`,`vendor`,`venv`]),u=/* @__PURE__ */ new Set([`__tests__`,`test`,`tests`,`spec`]),d=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,f=/Tests?\.(?:java|kt|cs)$/u;function p(e){return e===`~`?c.default.homedir():e.startsWith(`~/`)?s.default.join(c.default.homedir(),e.slice(2)):s.default.resolve(e)}async function m(e){try{return(await(0,o.stat)(e)).isDirectory()?e:s.default.dirname(e)}catch{return s.default.dirname(e)}}const h=c.default.availableParallelism()*2;async function g(e,t){let n=e;try{n=await(0,o.realpath)(e)}catch{}let r=s.default.dirname(n),i;try{i=await(0,o.stat)(n)}catch(e){let t=`${L(n,r)}: ${(0,a.getErrorMessage)(e)}`;return{displayRoot:r,files:[],errors:[t],warnings:[],generatedFiles:[],fatalError:t}}if(i.isFile()){let e=s.default.dirname(n),r=I(n,t,!0);if(!r){let t=`${L(n,e)}: unsupported file type`;return{displayRoot:e,files:[],errors:[t],warnings:[],generatedFiles:[],fatalError:t}}let i=x(t,e);return await O(n,r,`single-file`,i,n),b(i,e)}let c=x(t,n);return await T(n,c),await v(c),b(c,n)}async function _(e,t,n,r=/* @__PURE__ */ new Set,i=``){let a=x(n,e),c=i===``?``:`${i}/`;for(let i of t){let t=s.default.join(e,i),l=F(i.startsWith(c)?i.slice(c.length):i,n,r.has(t))?I(i,n,r.has(t)):void 0;if(!l)continue;let u=await(0,o.lstat)(t).catch(()=>{});(!u||u.isFile())&&a.candidates.push({file:t,language:l})}return await v(a),b(a,e)}async function v(e){let t=await e.options.loadExclusion?.(e.candidates.flatMap(({file:e,realFile:t})=>t===void 0?[e]:[e,t]));e.exclusion=t;let n=/* @__PURE__ */ new Map;for(let t of e.candidates){let r;try{r=t.realFile??await(0,o.realpath)(t.file)}catch(n){S(e,t.file,n);continue}n.set(r,[...n.get(r)??[],t])}for(let[r,i]of n){if(e.fatalSeen)return;let n=await y(r,i,t);n!==void 0&&await O(n.file,n.language,`directory`,e,r)}}async function y(e,t,n){if(n===void 0)return t[0];if(n.isExcludedPath(e))return;let r=t.filter(({file:e})=>!n.isExcludedPath(e)),i=r.find(({file:t})=>t===e);if(i!==void 0||r.length<=1)return i??r[0];let a=await(0,o.readFile)(e,`utf8`).catch(()=>{});return r.find(({file:e})=>a!==void 0&&!n.isGeneratedCode(e,a))??r[0]}async function b(e,t){let n=[],r=[],i=[],o=[];for(let s of e.outcomes){let e=await s;if(`fatal`in e){let s=(0,a.getErrorMessage)(e.fatal);return{displayRoot:t,files:n,errors:[...r,s],warnings:i,generatedFiles:o,fatalError:s}}if(`generatedFile`in e){o.push(e.generatedFile);continue}if(`error`in e){r.push(e.error);continue}n.push(e.file),e.warning!==void 0&&i.push(e.warning)}return{displayRoot:t,files:n,errors:r,warnings:i,generatedFiles:o}}function x(e,t){return{options:e,outcomes:[],inFlight:/* @__PURE__ */ new Set,fatalSeen:!1,visitedDirectories:/* @__PURE__ */ new Set,visitedFiles:/* @__PURE__ */ new Set,rootDirectory:t,candidates:[]}}function S(e,t,n){e.outcomes.push({error:`${L(t,e.rootDirectory)}: ${(0,a.getErrorMessage)(n)}`})}async function C(e,t,n){try{return await e()}catch(e){S(n,t,e);return}}async function w(e,t){let n=await C(()=>(0,o.realpath)(e),e,t);return n!==void 0&&P(n,t.rootDirectory)?n:void 0}async function T(e,t){let n=await w(e,t);if(n===void 0||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r=await C(()=>(0,o.readdir)(e,{withFileTypes:!0}),e,t);if(r!==void 0){r.sort((e,t)=>e.name<t.name?-1:+(e.name>t.name));for(let i of r){let r=s.default.join(e,i.name);if(i.isSymbolicLink()){await E(i.name,r,t);continue}if(i.isDirectory()){if(N(i.name,t.options))continue;await T(r,t);continue}i.isFile()&&D(r,t,r,n===e?void 0:s.default.join(n,i.name))}}}async function E(e,t,n){let r=await w(t,n);if(r===void 0)return;let i=await C(()=>(0,o.stat)(t),t,n);if(i!==void 0){if(i.isDirectory()){if(N(e,n.options)||N(s.default.basename(r),n.options))return;await T(t,n);return}i.isFile()&&D(t,n,r,r)}}function D(e,t,n=e,r){let i=I(n,t.options);i&&t.candidates.push({file:e,language:i,realFile:r})}async function O(e,t,n,r,i){if(r.fatalSeen)return;let a;try{a=i??await(0,o.realpath)(e)}catch(t){S(r,e,t);return}if(r.visitedFiles.has(a))return;for(r.visitedFiles.add(a);r.inFlight.size>=h;)await Promise.race(r.inFlight);let s=k(e,a,t,n,r);r.inFlight.add(s),s.then(()=>r.inFlight.delete(s)),r.outcomes.push(s)}async function k(e,t,n,s,c){try{let r=await(0,o.readFile)(e,`utf8`),a={language:n,duplication:c.options.duplication};if(s===`single-file`)return{file:{file:e,metrics:i.measureCode(r,a)}};let l=c.exclusion;if(l&&(l.isGeneratedCode(e,r)||l.isGeneratedCode(t,r)))return{generatedFile:e};let{metrics:u,crossFileData:d,crossFileError:f}=await A(r,a);return{file:{file:e,metrics:u,duplicationCandidates:d},warning:f===void 0?void 0:`${L(e,c.rootDirectory)}: cross-file duplication candidates unavailable: ${f}`}}catch(t){return t instanceof r.NativeAddonError?(c.fatalSeen=!0,{fatal:t}):{error:`${L(e,c.rootDirectory)}: ${(0,a.getErrorMessage)(t)}`}}}async function A(e,t){try{return await i.measureCodeWithCrossFileDataAsync(e,t)}catch(n){if(n instanceof r.NativeAddonError)throw n;return{metrics:i.measureCode(e,t),crossFileError:(0,a.getErrorMessage)(n)}}}function j(e,n){if(e.fatalError||e.files.length<2)return;let r=e.files.flatMap(({file:t,duplicationCandidates:n})=>n?[{file:L(t,e.displayRoot),...n}]:[]);r.length<2||(e.crossFileDuplication=t.measureCrossFileDuplication(r,n.duplication))}function M(e,t,n){let r=new Set(e?.duplication.duplicateLineNumbers),i=t&&Object.hasOwn(t.duplicateLineNumbersByFile,n)?t.duplicateLineNumbersByFile[n]??[]:[];for(let e of i)r.add(e);return r}function N(e,t){return l.has(e)?!0:!t.includeTests&&u.has(e)}function P(e,t){let n=s.default.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${s.default.sep}`)&&!s.default.isAbsolute(n)}function F(e,t,n=!1){if(n)return I(e,t,!0)!==void 0;let r=e.split(`/`);for(let e of r.slice(0,-1))if(l.has(e)||!t.includeTests&&u.has(e))return!1;return I(e,t)!==void 0}function I(e,t,r=!1){let i=e.toLowerCase();if(!(!r&&(i.endsWith(`.d.ts`)||i.endsWith(`.d.mts`)||i.endsWith(`.d.cts`)||i.endsWith(`.min.js`)||i.endsWith(`.pnp.cjs`)))&&(r||t.includeTests||!d.test(s.default.basename(e))&&!f.test(s.default.basename(e))))return n.detectLanguage(e)}function L(e,t){return s.default.relative(t,e).replaceAll(s.default.sep,`/`)||s.default.basename(e)}function R(e){process.stdout.write(e)}function z(e){process.stderr.write(e)}exports.addCrossFileDuplication=j,exports.collectDuplicatedLineNumbers=M,exports.configSearchDirectory=m,exports.formatPath=L,exports.getLanguage=I,exports.isWithinDirectory=P,exports.resolveTarget=p,exports.scanListedFiles=_,exports.scanTarget=g,exports.writeStderr=z,exports.writeStdout=R;
2
2
  //# sourceMappingURL=scan.cjs.map