code-gauge 10.2.4 → 10.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +47 -5
  2. package/THIRD-PARTY-NOTICES.txt +36 -12
  3. package/dist/allowDirectives.cjs +2 -0
  4. package/dist/allowDirectives.cjs.map +1 -0
  5. package/dist/allowDirectives.d.ts +45 -0
  6. package/dist/allowDirectives.js +2 -0
  7. package/dist/allowDirectives.js.map +1 -0
  8. package/dist/checkCommand.cjs +3 -3
  9. package/dist/checkCommand.cjs.map +1 -1
  10. package/dist/checkCommand.d.ts +1 -1
  11. package/dist/checkCommand.js +3 -3
  12. package/dist/checkCommand.js.map +1 -1
  13. package/dist/cli.cjs.map +1 -1
  14. package/dist/cli.js.map +1 -1
  15. package/dist/cliConfig.cjs.map +1 -1
  16. package/dist/cliConfig.js.map +1 -1
  17. package/dist/crossFileDuplication.cjs.map +1 -1
  18. package/dist/crossFileDuplication.js.map +1 -1
  19. package/dist/crossFileNearMiss.cjs +1 -1
  20. package/dist/crossFileNearMiss.cjs.map +1 -1
  21. package/dist/crossFileNearMiss.js +1 -1
  22. package/dist/crossFileNearMiss.js.map +1 -1
  23. package/dist/duplicateSelection.cjs.map +1 -1
  24. package/dist/duplicateSelection.js.map +1 -1
  25. package/dist/duplication.cjs +1 -1
  26. package/dist/duplication.cjs.map +1 -1
  27. package/dist/duplication.js +1 -1
  28. package/dist/duplication.js.map +1 -1
  29. package/dist/exclusion.cjs.map +1 -1
  30. package/dist/exclusion.js.map +1 -1
  31. package/dist/git.cjs +2 -1
  32. package/dist/git.cjs.map +1 -1
  33. package/dist/git.d.ts +7 -0
  34. package/dist/git.js +2 -1
  35. package/dist/git.js.map +1 -1
  36. package/dist/languages.cjs.map +1 -1
  37. package/dist/languages.js.map +1 -1
  38. package/dist/metrics.cjs.map +1 -1
  39. package/dist/metrics.js.map +1 -1
  40. package/dist/nativeMetrics.cjs +1 -1
  41. package/dist/nativeMetrics.cjs.map +1 -1
  42. package/dist/nativeMetrics.js +1 -1
  43. package/dist/nativeMetrics.js.map +1 -1
  44. package/dist/scan.cjs +1 -1
  45. package/dist/scan.cjs.map +1 -1
  46. package/dist/scan.js +1 -1
  47. package/dist/scan.js.map +1 -1
  48. package/dist/thresholdCheck.cjs +1 -1
  49. package/dist/thresholdCheck.cjs.map +1 -1
  50. package/dist/thresholdCheck.d.ts +15 -1
  51. package/dist/thresholdCheck.js +1 -1
  52. package/dist/thresholdCheck.js.map +1 -1
  53. package/dist/thresholds.cjs.map +1 -1
  54. package/dist/thresholds.js.map +1 -1
  55. package/dist/wasmBinding.cjs.map +1 -1
  56. package/dist/wasmBinding.js.map +1 -1
  57. package/dist/worker.cjs.map +1 -1
  58. package/dist/worker.js.map +1 -1
  59. package/native/Cargo.lock +24 -24
  60. package/native/Cargo.toml +12 -12
  61. package/native/code-gauge.wasm +0 -0
  62. package/native/src/complexity.rs +28 -22
  63. package/native/src/duplication.rs +37 -3
  64. package/native/src/functions.rs +10 -12
  65. package/native/src/languages.rs +2 -3
  66. package/native/src/ncss.rs +29 -12
  67. package/native/src/util.rs +6 -0
  68. package/package.json +39 -43
@@ -1,3 +1,4 @@
1
+ import type { AllowDirectives } from './allowDirectives.js';
1
2
  import type { CrossFileDuplicationMetrics } from './crossFileDuplication.js';
2
3
  import type { LineHunk } from './git.js';
3
4
  import { type Level, type LimitsByLevel } from './thresholds.js';
@@ -8,6 +9,7 @@ export interface CheckedFile {
8
9
  metrics: CodeMetrics;
9
10
  /** The change to the file; when set, only what the change touches is checked. */
10
11
  hunks?: readonly LineHunk[];
12
+ allowDirectives?: AllowDirectives;
11
13
  }
12
14
  /** The duplication of the measured files, in which duplicated blocks and their copies are found. */
13
15
  export interface CheckedDuplication {
@@ -46,11 +48,23 @@ export interface Violation extends BlockLocation {
46
48
  /** The other copies of the block, most matched tokens first, for kind `duplication`. */
47
49
  partners?: BlockLocation[];
48
50
  }
51
+ export interface LocatedDirectiveProblem {
52
+ file: string;
53
+ line: number;
54
+ message: string;
55
+ }
49
56
  export interface CheckResult {
50
57
  violations: Violation[];
51
58
  checkedFunctionCount: number;
59
+ /** The exceeded limits a directive allowed, which `violations` leaves out. */
60
+ allowedLimitCount: number;
61
+ /** Ordered by path, then line. */
62
+ directiveProblems: LocatedDirectiveProblem[];
52
63
  }
53
- /** Every limit the files exceed, errors first, then ordered by path, then line. */
64
+ /**
65
+ * Every limit the files exceed and no directive allows, errors first, then ordered by path, then
66
+ * line.
67
+ */
54
68
  export declare function checkThresholds(files: readonly CheckedFile[], duplication: CheckedDuplication, limitsOf: (language: string) => LimitsByLevel): CheckResult;
55
69
  /** How reports name a function: with the type or module it is a member of, so that it can be found. */
56
70
  export declare function qualifyFunctionName({ name, containerName }: FunctionMetrics): string;
@@ -1,2 +1,2 @@
1
- import{duplicationThreshold as e,fileThresholds as t,functionThresholds as n,levels as r,metricNameOf as i}from"./thresholds.js";import a from"node:assert";const o=[`file`,`function`,`duplication`],s=new Set(n.filter(({includesNestedFunctions:e})=>e).map(e=>i(e)));function c(e,r,i){let a=y(r.crossFile,new Set(e.map(({file:e})=>e))),o=[],s=0;for(let c of e){let{file:e,metrics:l,hunks:f}=c,m=i(l.language),_=f?.length===0?[]:g(t,l,m);_.length>0&&o.push(h({file:e,startLine:1,endLine:l.lines.total},`file`,_));let v=u(c);s+=v.length;let y=v.map(e=>({fn:e,exceeded:g(n,e,m)})).filter(({exceeded:e})=>e.length>0);for(let{fn:t,exceeded:n}of y){let r=n.filter(e=>!d(e,t,y));r.length>0&&o.push(p(e,t,r))}o.push(...b(c,a.get(e)??[],m,r))}return{violations:o.toSorted(l),checkedFunctionCount:s}}function l(e,t){return r.indexOf(t.level)-r.indexOf(e.level)||E(e.file,t.file)||e.startLine-t.startLine||o.indexOf(e.kind)-o.indexOf(t.kind)||e.endLine-t.endLine}function u({metrics:e,hunks:t}){let n=e.language===`python`;return t?e.functions.filter(e=>t.some(t=>v(t,e.startLine,e.endLine,n))):e.functions}function d(e,t,n){return s.has(e.metric)?n.some(n=>n.fn!==t&&f(n.fn,t)&&n.exceeded.some(({metric:t})=>t===e.metric)):!1}function f(e,t){let n=e.startLine<t.startLine||e.startLine===t.startLine&&e.startColumn<=t.startColumn,r=e.endLine>t.endLine||e.endLine===t.endLine&&e.endColumn>=t.endColumn;return n&&r}function p(e,t,n){let{startLine:r,endLine:i}=t,a=n.some(({metric:e})=>e===`functionCognitiveComplexity`);return{...h({file:e,startLine:r,endLine:i},`function`,n),name:m(t),...a&&t.cognitiveBlocks.length>0&&{largestBlocks:t.cognitiveBlocks}}}function m({name:e=`<anonymous>`,containerName:t}){return t?`${t}.${e}`:e}function h(e,t,n){return{kind:t,level:n.some(e=>e.level===`error`)?`error`:`warning`,...e,exceeded:n}}function g(e,t,n){return e.flatMap(e=>_(e,t,n,(e,t)=>e>t))}function _(e,t,n,a){let o=e.measure(t),s=r.findLast(t=>a(o,n[t][e.key]));return s?[{metric:i(e),label:e.label,value:o,level:s,limit:n[s][e.key]}]:[]}function v(e,t,n,r=!1){return e.headCount===0?e.headStart>=t&&(e.headStart<n||r&&e.headStart===n):e.headStart<=n&&e.headStart+e.headCount>t}function y(e,t){let n=/* @__PURE__ */ new Map;for(let r of e?.groups??[])for(let e of r.files)if(t.has(e)){let t=n.get(e)??[];t.push(r),n.set(e,t)}return n}function b({file:t,metrics:n,hunks:r},i,a,{minSimilarityPercent:o}){let s=t=>_(e,{duplicatedLineCount:t},a,(e,t)=>e>=t),c=[...n.duplication.duplicateBlockGroups.map(e=>e.map(e=>({...e,file:t}))),...i.map(e=>e.occurrences.map(e=>x(e)))],l=e=>e.file===t&&s(e.lineNumbers.length).length>0&&(!r||r.some(t=>t.headCount>0&&v(t,e.startLine,e.endLine))),u=new Map(c.flatMap(e=>e.map(t=>[t,e]))),d=(e,t)=>u.get(e)===u.get(t)&&!w(e,t)&&!w(t,e);return C(c.flat().filter(e=>l(e)),d).map(({merged:e,sources:t})=>{let n=e=>C(t.flatMap(n=>{let r=Math.max(...t.filter(e=>w(e,n)).map(e=>e.tokenCount));return(u.get(n)??[]).filter(n=>n.tokenCount*100>=r*e&&!t.some(e=>w(e,n)))}),d).toSorted((e,t)=>S(t.sources)-S(e.sources)).map(e=>e.merged),r=n(o);return{...h(e,`duplication`,s(new Set(t.flatMap(e=>e.lineNumbers)).size)),partners:r.length>0?r:n(0)}})}function x(e){let{lineNumbers:t}=e;return a.ok(t,`No matched lines for ${e.file}, a scanned file.`),{...e,lineNumbers:t}}function S(e){return e.reduce((e,t)=>e+t.tokenCount,0)}function C(e,t){let n=[],r=e.toSorted((e,t)=>E(e.file,t.file)||e.startLine-t.startLine);for(let e of r){let r=n.at(-1);if(r&&T(r.merged,e)&&(r.sources.some(t=>w(t,e))||!r.sources.some(n=>t(n,e))))r.merged.endLine=Math.max(r.merged.endLine,e.endLine),r.sources.push(e);else{let{file:t,startLine:r,endLine:i}=e;n.push({merged:{file:t,startLine:r,endLine:i},sources:[e]})}}return n}function w(e,t){return e.file===t.file&&e.startLine<=t.startLine&&t.endLine<=e.endLine}function T(e,t){return e.file===t.file&&e.startLine<=t.endLine&&t.startLine<=e.endLine}function E(e,t){return e<t?-1:+(e>t)}export{c as checkThresholds,m as qualifyFunctionName};
1
+ import{duplicationThreshold as e,fileThresholds as t,functionThresholds as n,levels as r,metricNameOf as i,thresholds as a}from"./thresholds.js";import o from"node:assert";const s=[`file`,`function`,`duplication`],c=new Set(n.filter(({includesNestedFunctions:e})=>e).map(e=>i(e)));function l(e,t,n){let r=T(t.crossFile,new Set(e.map(({file:e})=>e))),i=[],a=[],o=0,s=0;for(let c of e){let e={...c,hunks:u(c)},l=n(e.metrics.language),m=r.get(e.file)??[],h=e.allowDirectives?.directives??[],g={used:/* @__PURE__ */ new Set,outgrown:/* @__PURE__ */ new Set},_=f(e,m,l,t,d(h,g,()=>s++));i.push(..._.violations),o+=_.checkedFunctionCount,e.hunks&&h.length>0&&f({...e,hunks:void 0},m,l,t,d(h,g,()=>{})),a.push(...p(e,g,l))}return{violations:i.toSorted(h),checkedFunctionCount:o,allowedLimitCount:s,directiveProblems:a.toSorted((e,t)=>M(e.file,t.file)||e.line-t.line)}}function u({metrics:e,hunks:t,allowDirectives:n}){return t?.some(e=>e.deletesDirective||n?.attachmentSpans.some(({startLine:t,endLine:n,endsAtFunction:r})=>w(e,t,e.headCount>0&&r?n-1:n)))?[{headStart:1,headCount:e.lines.total}]:t}function d(e,t,n){return(r,i)=>r.filter(r=>{let a=e.filter(e=>e.metric===r.metric&&i(e)),o=r.level===`warning`?a.filter(e=>r.value<=e.max):[];for(let e of a)o.includes(e)?t.used.add(e):r.value>e.max&&t.outgrown.add(e);return o.length>0&&n(),o.length===0})}function f(e,r,i,a,o){let{file:s,metrics:c,hunks:l}=e,u=[],d=l?.length===0?[]:o(S(t,c,i),({target:e})=>!e);d.length>0&&u.push(x({file:s,startLine:1,endLine:c.lines.total},`file`,d));let f=g(e),p=f.map(e=>({fn:e,exceeded:S(n,e,i)})).filter(({exceeded:e})=>e.length>0);for(let{fn:e,exceeded:t}of p){let n=o(t.filter(t=>!_(t,e,p)),({target:t})=>!t||t===e||v(t,e));n.length>0&&u.push(y(s,e,n))}return u.push(...E(e,r,i,a,o)),{violations:u,checkedFunctionCount:f.length}}function p({file:e,metrics:t,hunks:n,allowDirectives:r},i,a){let o=(e,t)=>!n||n.some(n=>w(n,e,t)),s=(r?.problems??[]).filter(({line:e})=>o(e,e));for(let e of r?.directives??[]){let{line:n,target:r}=e;(r?o(n,r.endLine):o(1,t.lines.total))&&s.push(...m(e,i,a).map(e=>({line:n,message:e})))}return s.map(t=>({file:e,...t}))}function m(t,{used:n,outgrown:r},o){let{metric:s,max:c,target:l}=t,u=`${l?`code-gauge-allow`:`code-gauge-allow-file`} ${s} ${c}`,d=[],f=a.find(e=>i(e)===s),p=f?o.error[f.key]:1/0,m=f===e?Math.max(Math.ceil(p)-1,0):p,h=c>m;return h&&d.push(`${u}: values above ${m} are errors, which a directive cannot allow`),r.has(t)?d.push(`${u}: the value grew above ${c}; reduce it or reconsider the directive`):!n.has(t)&&!h&&d.push(`${u}: allows no warning; remove the directive`),d}function h(e,t){return r.indexOf(t.level)-r.indexOf(e.level)||M(e.file,t.file)||e.startLine-t.startLine||s.indexOf(e.kind)-s.indexOf(t.kind)||e.endLine-t.endLine}function g({metrics:e,hunks:t}){let n=e.language===`python`;return t?e.functions.filter(e=>t.some(t=>w(t,e.startLine,e.endLine,n))):e.functions}function _(e,t,n){return c.has(e.metric)?n.some(n=>n.fn!==t&&v(n.fn,t)&&n.exceeded.some(({metric:t})=>t===e.metric)):!1}function v(e,t){let n=e.startLine<t.startLine||e.startLine===t.startLine&&e.startColumn<=t.startColumn,r=e.endLine>t.endLine||e.endLine===t.endLine&&e.endColumn>=t.endColumn;return n&&r}function y(e,t,n){let{startLine:r,endLine:i}=t,a=n.some(({metric:e})=>e===`functionCognitiveComplexity`);return{...x({file:e,startLine:r,endLine:i},`function`,n),name:b(t),...a&&t.cognitiveBlocks.length>0&&{largestBlocks:t.cognitiveBlocks}}}function b({name:e=`<anonymous>`,containerName:t}){return t?`${t}.${e}`:e}function x(e,t,n){return{kind:t,level:n.some(e=>e.level===`error`)?`error`:`warning`,...e,exceeded:n}}function S(e,t,n){return e.flatMap(e=>C(e,t,n,(e,t)=>e>t))}function C(e,t,n,a){let o=e.measure(t),s=r.findLast(t=>a(o,n[t][e.key]));return s?[{metric:i(e),label:e.label,value:o,level:s,limit:n[s][e.key]}]:[]}function w(e,t,n,r=!1){return e.headCount===0?e.headStart>=t&&(e.headStart<n||r&&e.headStart===n):e.headStart<=n&&e.headStart+e.headCount>t}function T(e,t){let n=/* @__PURE__ */ new Map;for(let r of e?.groups??[])for(let e of r.files)if(t.has(e)){let t=n.get(e)??[];t.push(r),n.set(e,t)}return n}function E({file:t,metrics:n,hunks:r},i,a,{minSimilarityPercent:o},s){let c=t=>C(e,{duplicatedLineCount:t},a,(e,t)=>e>=t),l=[...n.duplication.duplicateBlockGroups.map(e=>e.map(e=>({...e,file:t}))),...i.map(e=>e.occurrences.map(e=>D(e)))],u=e=>e.file===t&&c(e.lineNumbers.length).length>0&&(!r||r.some(t=>t.headCount>0&&w(t,e.startLine,e.endLine))),d=new Map(l.flatMap(e=>e.map(t=>[t,e]))),f=(e,t)=>d.get(e)===d.get(t)&&!A(e,t)&&!A(t,e);return k(l.flat().filter(e=>u(e)),f).flatMap(({merged:e,sources:t})=>{let n=s(c(new Set(t.flatMap(e=>e.lineNumbers)).size),({target:t})=>!t||t.startLine<=e.startLine&&e.startLine<=t.endLine);if(n.length===0)return[];let r=e=>k(t.flatMap(n=>{let r=Math.max(...t.filter(e=>A(e,n)).map(e=>e.tokenCount));return(d.get(n)??[]).filter(n=>n.tokenCount*100>=r*e&&!t.some(e=>A(e,n)))}),f).toSorted((e,t)=>O(t.sources)-O(e.sources)).map(e=>e.merged),i=r(o);return{...x(e,`duplication`,n),partners:i.length>0?i:r(0)}})}function D(e){let{lineNumbers:t}=e;return o.ok(t,`No matched lines for ${e.file}, a scanned file.`),{...e,lineNumbers:t}}function O(e){return e.reduce((e,t)=>e+t.tokenCount,0)}function k(e,t){let n=[],r=e.toSorted((e,t)=>M(e.file,t.file)||e.startLine-t.startLine);for(let e of r){let r=n.at(-1);if(r&&j(r.merged,e)&&(r.sources.some(t=>A(t,e))||!r.sources.some(n=>t(n,e))))r.merged.endLine=Math.max(r.merged.endLine,e.endLine),r.sources.push(e);else{let{file:t,startLine:r,endLine:i}=e;n.push({merged:{file:t,startLine:r,endLine:i},sources:[e]})}}return n}function A(e,t){return e.file===t.file&&e.startLine<=t.startLine&&t.endLine<=e.endLine}function j(e,t){return e.file===t.file&&e.startLine<=t.endLine&&t.startLine<=e.endLine}function M(e,t){return e<t?-1:+(e>t)}export{l as checkThresholds,b as qualifyFunctionName};
2
2
  //# sourceMappingURL=thresholdCheck.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"thresholdCheck.js","names":["assert","violationKinds","nestedInclusiveMetrics","functionThresholds","includesNestedFunctions","metricNameOf","threshold","checkThresholds","crossFileGroupsByFile","indexGroupsByFile","duplication","files","file","violations","checkedFunctionCount","checkedFile","limits","limitsOf","metrics","fileExceeded","hunks","collectExceeded","fileThresholds","toViolation","checkedFunctions","listCheckedFunctions","exceedingFunctions","fn","exceeded","ownExceeded","isReportedByEnclosingFunction","limit","toFunctionViolation","collectDuplicationViolations","compareViolations","levels","right","left","compareStrings","endsByIndentation","touchesSpan","hunk","outer","encloses","metric","startsBefore","inner","endsAfter","exceedsCognitiveComplexity","qualifyFunctionName","containerName","name","location","thresholds","findExceeded","subject","value","level","violates","candidate","startLine","endLine","groupsByFile","group","crossFileDuplication","groups","findExceededLines","duplicationThreshold","occurrence","crossFileGroups","toCloneOccurrence","isReported","block","groupOf","areCopies","contains","mergeOverlapping","listCopies","sources","tokenCount","source","copy","minTokenPercent","sumTokens","partner","copies","minSimilarityPercent","merged","lineNumbers","occurrences","sum","regions","ordered","locations","last","overlaps"],"sources":["../src/thresholdCheck.ts"],"sourcesContent":["import assert from 'node:assert';\nimport type {\n CrossFileDuplicateBlockGroup,\n CrossFileDuplicateOccurrence,\n CrossFileDuplicationMetrics,\n} from './crossFileDuplication.js';\nimport type { LineHunk } from './git.js';\nimport {\n duplicationThreshold,\n fileThresholds,\n functionThresholds,\n levels,\n metricNameOf,\n type Level,\n type LimitsByLevel,\n type Threshold,\n} from './thresholds.js';\nimport type { CodeMetrics, CognitiveBlock, FunctionMetrics } from './types.js';\n\n/** A measured file to check, under the path cross-file duplication knows it by. */\nexport interface CheckedFile {\n file: string;\n metrics: CodeMetrics;\n /** The change to the file; when set, only what the change touches is checked. */\n hunks?: readonly LineHunk[];\n}\n\n/** The duplication of the measured files, in which duplicated blocks and their copies are found. */\nexport interface CheckedDuplication {\n crossFile?: CrossFileDuplicationMetrics;\n /** The detection's `minSimilarityPercent`, the share of a block a copy of it holds at least. */\n minSimilarityPercent: number;\n}\n\nexport interface ExceededLimit {\n metric: string;\n /** What reports call the metric (`cognitive complexity`, `duplicated lines`). */\n label: string;\n value: number;\n /** The most severe level whose limit the value violates. */\n level: Level;\n /** That level's limit: the value is allowed up to it; a duplicated block is allowed below it. */\n limit: number;\n}\n\nexport interface BlockLocation {\n file: string;\n startLine: number;\n endLine: number;\n}\n\n/** One copy of a clone group. */\ninterface CloneOccurrence extends BlockLocation {\n /**\n * Its code lines matched in another copy, whose number is the length of a duplicated block. Its\n * line span would also count the lines the copies do not share.\n */\n lineNumbers: readonly number[];\n tokenCount: number;\n}\n\nexport interface Violation extends BlockLocation {\n kind: 'function' | 'file' | 'duplication';\n /** The most severe level among `exceeded`. */\n level: Level;\n /** The function's name, qualified with the class it is a member of, for kind `function`. */\n name?: string;\n exceeded: ExceededLimit[];\n /**\n * The parts adding the most to the cognitive complexity, largest first, for a function that\n * exceeds a limit of it and has such parts: operator sequences and jumps outside any branching\n * construct add to the complexity without being one.\n */\n largestBlocks?: CognitiveBlock[];\n /** The other copies of the block, most matched tokens first, for kind `duplication`. */\n partners?: BlockLocation[];\n}\n\nexport interface CheckResult {\n violations: Violation[];\n checkedFunctionCount: number;\n}\n\nconst violationKinds: readonly Violation['kind'][] = ['file', 'function', 'duplication'];\n\nconst cognitiveComplexityMetric = 'functionCognitiveComplexity';\n\nconst nestedInclusiveMetrics = new Set(\n functionThresholds\n .filter(({ includesNestedFunctions }) => includesNestedFunctions)\n .map((threshold) => metricNameOf(threshold))\n);\n\n/** Every limit the files exceed, errors first, then ordered by path, then line. */\nexport function checkThresholds(\n files: readonly CheckedFile[],\n duplication: CheckedDuplication,\n limitsOf: (language: string) => LimitsByLevel\n): CheckResult {\n const crossFileGroupsByFile = indexGroupsByFile(duplication.crossFile, new Set(files.map(({ file }) => file)));\n const violations: Violation[] = [];\n let checkedFunctionCount = 0;\n for (const checkedFile of files) {\n const { file, metrics, hunks } = checkedFile;\n const limits = limitsOf(metrics.language);\n // A rename or a mode change lists the file as changed without touching any of its lines.\n const fileExceeded = hunks?.length === 0 ? [] : collectExceeded(fileThresholds, metrics, limits);\n if (fileExceeded.length > 0) {\n violations.push(toViolation({ file, startLine: 1, endLine: metrics.lines.total }, 'file', fileExceeded));\n }\n const checkedFunctions = listCheckedFunctions(checkedFile);\n checkedFunctionCount += checkedFunctions.length;\n const exceedingFunctions = checkedFunctions\n .map((fn) => ({ fn, exceeded: collectExceeded(functionThresholds, fn, limits) }))\n .filter(({ exceeded }) => exceeded.length > 0);\n for (const { fn, exceeded } of exceedingFunctions) {\n const ownExceeded = exceeded.filter((limit) => !isReportedByEnclosingFunction(limit, fn, exceedingFunctions));\n if (ownExceeded.length > 0) {\n violations.push(toFunctionViolation(file, fn, ownExceeded));\n }\n }\n violations.push(\n ...collectDuplicationViolations(checkedFile, crossFileGroupsByFile.get(file) ?? [], limits, duplication)\n );\n }\n return { violations: violations.toSorted(compareViolations), checkedFunctionCount };\n}\n\n// The kind is compared before the end line because a file violation's end line is corrected after\n// this ordering (to the last line that exists), and the order must not depend on that correction.\nfunction compareViolations(left: Violation, right: Violation): number {\n return (\n levels.indexOf(right.level) - levels.indexOf(left.level) ||\n compareStrings(left.file, right.file) ||\n left.startLine - right.startLine ||\n violationKinds.indexOf(left.kind) - violationKinds.indexOf(right.kind) ||\n left.endLine - right.endLine\n );\n}\n\n/** The file's functions or, for a changed file, those the change touches. */\nfunction listCheckedFunctions({ metrics, hunks }: CheckedFile): FunctionMetrics[] {\n const endsByIndentation = metrics.language === 'python';\n return hunks\n ? metrics.functions.filter((fn) =>\n hunks.some((hunk) => touchesSpan(hunk, fn.startLine, fn.endLine, endsByIndentation))\n )\n : metrics.functions;\n}\n\n/**\n * Whether a function enclosing `fn` exceeds the same limit with a value that covers `fn`, so\n * reporting it for `fn` too would count the same code twice. The enclosing value is at least that\n * of `fn` and both share the file's limits, so its level is never milder.\n */\nfunction isReportedByEnclosingFunction(\n limit: ExceededLimit,\n fn: FunctionMetrics,\n exceedingFunctions: readonly { fn: FunctionMetrics; exceeded: ExceededLimit[] }[]\n): boolean {\n if (!nestedInclusiveMetrics.has(limit.metric)) {\n return false;\n }\n return exceedingFunctions.some(\n (outer) => outer.fn !== fn && encloses(outer.fn, fn) && outer.exceeded.some(({ metric }) => metric === limit.metric)\n );\n}\n\nfunction encloses(outer: FunctionMetrics, inner: FunctionMetrics): boolean {\n const startsBefore =\n outer.startLine < inner.startLine ||\n (outer.startLine === inner.startLine && outer.startColumn <= inner.startColumn);\n const endsAfter =\n outer.endLine > inner.endLine || (outer.endLine === inner.endLine && outer.endColumn >= inner.endColumn);\n return startsBefore && endsAfter;\n}\n\nfunction toFunctionViolation(file: string, fn: FunctionMetrics, exceeded: ExceededLimit[]): Violation {\n const { startLine, endLine } = fn;\n const exceedsCognitiveComplexity = exceeded.some(({ metric }) => metric === cognitiveComplexityMetric);\n return {\n ...toViolation({ file, startLine, endLine }, 'function', exceeded),\n name: qualifyFunctionName(fn),\n ...(exceedsCognitiveComplexity && fn.cognitiveBlocks.length > 0 && { largestBlocks: fn.cognitiveBlocks }),\n };\n}\n\n/** How reports name a function: with the type or module it is a member of, so that it can be found. */\nexport function qualifyFunctionName({ name = '<anonymous>', containerName }: FunctionMetrics): string {\n return containerName ? `${containerName}.${name}` : name;\n}\n\nfunction toViolation(location: BlockLocation, kind: Violation['kind'], exceeded: ExceededLimit[]): Violation {\n const level = exceeded.some((limit) => limit.level === 'error') ? 'error' : 'warning';\n return { kind, level, ...location, exceeded };\n}\n\nfunction collectExceeded<Subject>(\n thresholds: readonly Threshold<Subject>[],\n subject: Subject,\n limits: LimitsByLevel\n): ExceededLimit[] {\n return thresholds.flatMap((threshold) => findExceeded(threshold, subject, limits, (value, limit) => value > limit));\n}\n\n/** The most severe violated limit of the threshold, if any. */\nfunction findExceeded<Subject>(\n threshold: Threshold<Subject>,\n subject: Subject,\n limits: LimitsByLevel,\n violates: (value: number, limit: number) => boolean\n): ExceededLimit[] {\n const value = threshold.measure(subject);\n const level = levels.findLast((candidate) => violates(value, limits[candidate][threshold.key] as number));\n return level\n ? [\n {\n metric: metricNameOf(threshold),\n label: threshold.label,\n value,\n level,\n limit: limits[level][threshold.key] as number,\n },\n ]\n : [];\n}\n\n/**\n * Whether the hunk changes the head lines [startLine, endLine]. A hunk without head lines (a pure\n * deletion) sits between its headStart line and the next one. Where indentation ends a span, its\n * last line is a statement rather than a closing delimiter, so a deletion right after that line may\n * have removed the span's tail; `endsByIndentation` counts it as touching, since the head alone\n * cannot tell it from a deletion after the span.\n */\nfunction touchesSpan(hunk: LineHunk, startLine: number, endLine: number, endsByIndentation = false): boolean {\n return hunk.headCount === 0\n ? hunk.headStart >= startLine && (hunk.headStart < endLine || (endsByIndentation && hunk.headStart === endLine))\n : hunk.headStart <= endLine && hunk.headStart + hunk.headCount > startLine;\n}\n\n/** The cross-file groups with an occurrence in each of the given files. */\nfunction indexGroupsByFile(\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n files: ReadonlySet<string>\n): Map<string, CrossFileDuplicateBlockGroup[]> {\n const groupsByFile = new Map<string, CrossFileDuplicateBlockGroup[]>();\n for (const group of crossFileDuplication?.groups ?? []) {\n for (const file of group.files) {\n if (files.has(file)) {\n const groups = groupsByFile.get(file) ?? [];\n groups.push(group);\n groupsByFile.set(file, groups);\n }\n }\n }\n return groupsByFile;\n}\n\n/**\n * One violation per duplicated region of the file: its clone occurrences, within-file and\n * cross-file, that hold at least the duplicated lines of a level (with `hunks`, only those\n * overlapping added lines), with overlapping occurrences merged into one region unless they are\n * copies of each other.\n */\nfunction collectDuplicationViolations(\n { file, metrics, hunks }: CheckedFile,\n crossFileGroups: readonly CrossFileDuplicateBlockGroup[],\n limits: LimitsByLevel,\n { minSimilarityPercent }: CheckedDuplication\n): Violation[] {\n const findExceededLines = (duplicatedLineCount: number): ExceededLimit[] =>\n findExceeded(duplicationThreshold, { duplicatedLineCount }, limits, (value, limit) => value >= limit);\n const groups: CloneOccurrence[][] = [\n ...metrics.duplication.duplicateBlockGroups.map((group) => group.map((occurrence) => ({ ...occurrence, file }))),\n ...crossFileGroups.map((group) => group.occurrences.map((occurrence) => toCloneOccurrence(occurrence))),\n ];\n const isReported = (block: CloneOccurrence): boolean =>\n block.file === file &&\n findExceededLines(block.lineNumbers.length).length > 0 &&\n (!hunks || hunks.some((hunk) => hunk.headCount > 0 && touchesSpan(hunk, block.startLine, block.endLine)));\n const groupOf = new Map(groups.flatMap((group) => group.map((occurrence) => [occurrence, group] as const)));\n // Copies of one group that share a line (the branches of an `else if` chain, the entries of a\n // list) stay apart: merged, they would be one block without a copy. One lying within another is\n // a repetition inside that block, not a block beside it.\n const areCopies = (left: CloneOccurrence, right: CloneOccurrence): boolean =>\n groupOf.get(left) === groupOf.get(right) && !contains(left, right) && !contains(right, left);\n const reportedBlocks = groups.flat().filter((occurrence) => isReported(occurrence));\n return mergeOverlapping(reportedBlocks, areCopies).map(({ merged, sources }) => {\n const listCopies = (minTokenPercent: number): BlockLocation[] =>\n mergeOverlapping(\n sources.flatMap((source) => {\n // The region merges every clone that overlaps it, so a copy of an occurrence lying\n // within a larger one is measured against the larger one.\n const tokenCount = Math.max(\n ...sources.filter((outer) => contains(outer, source)).map((outer) => outer.tokenCount)\n );\n return (groupOf.get(source) ?? []).filter(\n (copy) =>\n copy.tokenCount * 100 >= tokenCount * minTokenPercent &&\n // The region's own occurrences are among the copies of the groups it belongs to, and\n // so may be an occurrence within one of them that is not reported itself: a\n // repetition inside the block, not another place.\n !sources.some((outer) => contains(outer, copy))\n );\n }),\n areCopies\n )\n // The largest say the most about what to share.\n .toSorted((left, right) => sumTokens(right.sources) - sumTokens(left.sources))\n .map((partner) => partner.merged);\n // A group joins a block with every place repeating a part of it: a function another file\n // copies whole is grouped with each file holding a few of its lines. Such a fragment is not a\n // copy of the block: sharing it would not remove the block, and a report listing it reads as\n // one more copy. Sizes are compared in tokens, since the same code may be laid out over any\n // number of lines.\n const copies = listCopies(minSimilarityPercent);\n return {\n ...toViolation(\n merged,\n 'duplication',\n // Occurrences that overlap share lines, which count once.\n findExceededLines(new Set(sources.flatMap((source) => source.lineNumbers)).size)\n ),\n // A block whose parts were matched with different places has no copy of its size. The\n // places then are all that locates what it repeats.\n partners: copies.length > 0 ? copies : listCopies(0),\n };\n });\n}\n\nfunction toCloneOccurrence(occurrence: CrossFileDuplicateOccurrence): CloneOccurrence {\n const { lineNumbers } = occurrence;\n assert.ok(lineNumbers, `No matched lines for ${occurrence.file}, a scanned file.`);\n return { ...occurrence, lineNumbers };\n}\n\nfunction sumTokens(occurrences: readonly CloneOccurrence[]): number {\n return occurrences.reduce((sum, occurrence) => sum + occurrence.tokenCount, 0);\n}\n\n/**\n * The locations with those overlapping in the same file merged, except those `areCopies` holds\n * for, ordered by file, then line.\n */\nfunction mergeOverlapping(\n locations: readonly CloneOccurrence[],\n areCopies: (left: CloneOccurrence, right: CloneOccurrence) => boolean\n): { merged: BlockLocation; sources: CloneOccurrence[] }[] {\n const regions: { merged: BlockLocation; sources: CloneOccurrence[] }[] = [];\n const ordered = locations.toSorted(\n (left, right) => compareStrings(left.file, right.file) || left.startLine - right.startLine\n );\n for (const location of ordered) {\n const last = regions.at(-1);\n // A block containing the location holds it whatever it is a copy of.\n const joins =\n last &&\n overlaps(last.merged, location) &&\n (last.sources.some((source) => contains(source, location)) ||\n !last.sources.some((source) => areCopies(source, location)));\n if (joins) {\n last.merged.endLine = Math.max(last.merged.endLine, location.endLine);\n last.sources.push(location);\n } else {\n const { file, startLine, endLine } = location;\n regions.push({ merged: { file, startLine, endLine }, sources: [location] });\n }\n }\n return regions;\n}\n\n/** Whether `outer` spans every line of `inner`; a block contains itself. */\nfunction contains(outer: BlockLocation, inner: BlockLocation): boolean {\n return outer.file === inner.file && outer.startLine <= inner.startLine && inner.endLine <= outer.endLine;\n}\n\nfunction overlaps(left: BlockLocation, right: BlockLocation): boolean {\n return left.file === right.file && left.startLine <= right.endLine && right.startLine <= left.endLine;\n}\n\n/** Code-unit order, so reports do not depend on the locale. */\nfunction compareStrings(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n"],"mappings":"4JAmFA,MAAMC,EAA+C,CAAC,OAAQ,WAAY,aAAa,EAIjFC,EAAyB,IAAI,IACjCC,EACG,QAAQ,CAAE,6BAA8BC,CAAuB,CAAC,CAChE,IAAK,GAAcC,EAAaC,CAAS,CAAC,CAC/C,EAGA,SAAgBC,EACd,EACA,EACA,EACa,CACb,IAAMC,EAAwBC,EAAkBC,EAAY,UAAW,IAAI,IAAIC,EAAM,KAAK,CAAE,UAAWC,CAAI,CAAC,CAAC,EACvGC,EAA0B,CAAC,EAC7BC,EAAuB,EAC3B,IAAK,IAAMC,KAAeJ,EAAO,CAC/B,GAAM,CAAE,OAAM,UAAS,SAAUI,EAC3BC,EAASC,EAASC,EAAQ,QAAQ,EAElCC,EAAeC,GAAO,SAAW,EAAI,CAAC,EAAIC,EAAgBC,EAAgBJ,EAASF,CAAM,EAC3FG,EAAa,OAAS,GACxBN,EAAW,KAAKU,EAAY,CAAE,OAAM,UAAW,EAAG,QAASL,EAAQ,MAAM,KAAM,EAAG,OAAQC,CAAY,CAAC,EAEzG,IAAMK,EAAmBC,EAAqBV,CAAW,EACzD,GAAwBS,EAAiB,OACzC,IAAME,EAAqBF,EACxB,IAAK,IAAQ,CAAE,KAAI,SAAUH,EAAgBlB,EAAoBwB,EAAIX,CAAM,CAAE,EAAE,CAAC,CAChF,QAAQ,CAAE,cAAeY,EAAS,OAAS,CAAC,EAC/C,IAAK,GAAM,CAAE,KAAI,cAAcF,EAAoB,CACjD,IAAMG,EAAcD,EAAS,OAAQ,GAAU,CAACE,EAA8BC,EAAOJ,EAAID,CAAkB,CAAC,EACxGG,EAAY,OAAS,GACvBhB,EAAW,KAAKmB,EAAoBpB,EAAMe,EAAIE,CAAW,CAAC,CAE9D,CACAhB,EAAW,KACT,GAAGoB,EAA6BlB,EAAaP,EAAsB,IAAII,CAAI,GAAK,CAAC,EAAGI,EAAQN,CAAW,CACzG,CACF,CACA,MAAO,CAAE,WAAYG,EAAW,SAASqB,CAAiB,EAAG,sBAAqB,CACpF,CAIA,SAASA,EAAkB,EAAiB,EAA0B,CACpE,OACEC,EAAO,QAAQC,EAAM,KAAK,EAAID,EAAO,QAAQE,EAAK,KAAK,GACvDC,EAAeD,EAAK,KAAMD,EAAM,IAAI,GACpCC,EAAK,UAAYD,EAAM,WACvBnC,EAAe,QAAQoC,EAAK,IAAI,EAAIpC,EAAe,QAAQmC,EAAM,IAAI,GACrEC,EAAK,QAAUD,EAAM,OAEzB,CAGA,SAASX,EAAqB,CAAE,UAAS,SAAyC,CAChF,IAAMc,EAAoBrB,EAAQ,WAAa,SAC/C,OAAOE,EACHF,EAAQ,UAAU,OAAQ,GACxBE,EAAM,KAAM,GAASoB,EAAYC,EAAMd,EAAG,UAAWA,EAAG,QAASY,CAAiB,CAAC,CACrF,EACArB,EAAQ,SACd,CAOA,SAASY,EACP,EACA,EACA,EACS,CAIT,OAHK5B,EAAuB,IAAI6B,EAAM,MAAM,EAGrCL,EAAmB,KACvB,GAAUgB,EAAM,KAAOf,GAAMgB,EAASD,EAAM,GAAIf,CAAE,GAAKe,EAAM,SAAS,MAAM,CAAE,YAAaE,IAAWb,EAAM,MAAM,CACrH,EAJS,EAKX,CAEA,SAASY,EAAS,EAAwB,EAAiC,CACzE,IAAME,EACJH,EAAM,UAAYI,EAAM,WACvBJ,EAAM,YAAcI,EAAM,WAAaJ,EAAM,aAAeI,EAAM,YAC/DC,EACJL,EAAM,QAAUI,EAAM,SAAYJ,EAAM,UAAYI,EAAM,SAAWJ,EAAM,WAAaI,EAAM,UAChG,OAAOD,GAAgBE,CACzB,CAEA,SAASf,EAAoB,EAAc,EAAqB,EAAsC,CACpG,GAAM,CAAE,YAAW,WAAYL,EACzBqB,EAA6BpB,EAAS,MAAM,CAAE,YAAagB,IAAW,6BAAyB,EACrG,MAAO,CACL,GAAGrB,EAAY,CAAE,OAAM,YAAW,SAAQ,EAAG,WAAYK,CAAQ,EACjE,KAAMqB,EAAoBtB,CAAE,EAC5B,GAAIqB,GAA8BrB,EAAG,gBAAgB,OAAS,GAAK,CAAE,cAAeA,EAAG,eAAgB,CACzG,CACF,CAGA,SAAgBsB,EAAoB,CAAE,OAAO,cAAe,iBAA0C,CACpG,OAAOC,EAAgB,GAAGA,EAAc,GAAGC,IAASA,CACtD,CAEA,SAAS5B,EAAY,EAAyB,EAAyB,EAAsC,CAE3G,MAAO,CAAE,OAAM,MADDK,EAAS,KAAM,GAAUG,EAAM,QAAU,OAAO,EAAI,QAAU,UACtD,GAAGqB,EAAU,UAAS,CAC9C,CAEA,SAAS/B,EACP,EACA,EACA,EACiB,CACjB,OAAOgC,EAAW,QAAS,GAAcC,EAAahD,EAAWiD,EAASvC,GAAS,EAAO,IAAUwC,EAAQzB,CAAK,CAAC,CACpH,CAGA,SAASuB,EACP,EACA,EACA,EACA,EACiB,CACjB,IAAME,EAAQlD,EAAU,QAAQiD,CAAO,EACjCE,EAAQtB,EAAO,SAAU,GAAcuB,EAASF,EAAOxC,EAAO2C,EAAU,CAACrD,EAAU,IAAc,CAAC,EACxG,OAAOmD,EACH,CACE,CACE,OAAQpD,EAAaC,CAAS,EAC9B,MAAOA,EAAU,MACjB,QACA,QACA,MAAOU,EAAOyC,EAAM,CAACnD,EAAU,IACjC,CACF,EACA,CAAC,CACP,CASA,SAASkC,EAAY,EAAgB,EAAmB,EAAiB,EAAoB,GAAgB,CAC3G,OAAOC,EAAK,YAAc,EACtBA,EAAK,WAAamB,IAAcnB,EAAK,UAAYoB,GAAYtB,GAAqBE,EAAK,YAAcoB,GACrGpB,EAAK,WAAaoB,GAAWpB,EAAK,UAAYA,EAAK,UAAYmB,CACrE,CAGA,SAASnD,EACP,EACA,EAC6C,CAC7C,IAAMqD,kBAAe,IAAI,IACzB,IAAK,IAAMC,KAASC,GAAsB,QAAU,CAAC,EACnD,IAAK,IAAMpD,KAAQmD,EAAM,MACvB,GAAIpD,EAAM,IAAIC,CAAI,EAAG,CACnB,IAAMqD,EAASH,EAAa,IAAIlD,CAAI,GAAK,CAAC,EAC1CqD,EAAO,KAAKF,CAAK,EACjBD,EAAa,IAAIlD,EAAMqD,CAAM,CAC/B,CAGJ,OAAOH,CACT,CAQA,SAAS7B,EACP,CAAE,OAAM,UAAS,SACjB,EACA,EACA,CAAE,wBACW,CACb,IAAMiC,EAAqB,GACzBZ,EAAaa,EAAsB,CAAE,qBAAoB,EAAGnD,GAAS,EAAO,IAAUwC,GAASzB,CAAK,EAChGkC,EAA8B,CAClC,GAAG/C,EAAQ,YAAY,qBAAqB,IAAK,GAAU6C,EAAM,IAAK,IAAgB,CAAE,GAAGK,EAAY,MAAK,EAAE,CAAC,EAC/G,GAAGC,EAAgB,IAAK,GAAUN,EAAM,YAAY,IAAK,GAAeO,EAAkBF,CAAU,CAAC,CAAC,CACxG,EACMG,EAAc,GAClBC,EAAM,OAAS5D,GACfsD,EAAkBM,EAAM,YAAY,MAAM,CAAC,CAAC,OAAS,IACpD,CAACpD,GAASA,EAAM,KAAM,GAASqB,EAAK,UAAY,GAAKD,EAAYC,EAAM+B,EAAM,UAAWA,EAAM,OAAO,CAAC,GACnGC,EAAU,IAAI,IAAIR,EAAO,QAAS,GAAUF,EAAM,IAAK,GAAe,CAACK,EAAYL,CAAK,CAAU,CAAC,CAAC,EAIpGW,GAAa,EAAuB,IACxCD,EAAQ,IAAIpC,CAAI,IAAMoC,EAAQ,IAAIrC,CAAK,GAAK,CAACuC,EAAStC,EAAMD,CAAK,GAAK,CAACuC,EAASvC,EAAOC,CAAI,EAE7F,OAAOuC,EADgBX,EAAO,KAAK,CAAC,CAAC,OAAQ,GAAeM,EAAWH,CAAU,CAC5C,EAAGM,CAAS,CAAC,CAAC,KAAK,CAAE,SAAQ,aAAc,CAC9E,IAAMG,EAAc,GAClBD,EACEE,EAAQ,QAAS,GAAW,CAG1B,IAAMC,EAAa,KAAK,IACtB,GAAGD,EAAQ,OAAQ,GAAUH,EAASjC,EAAOsC,CAAM,CAAC,CAAC,CAAC,IAAK,GAAUtC,EAAM,UAAU,CACvF,EACA,OAAQ+B,EAAQ,IAAIO,CAAM,GAAK,CAAC,EAAA,CAAG,OAChC,GACCC,EAAK,WAAa,KAAOF,EAAaG,GAItC,CAACJ,EAAQ,KAAM,GAAUH,EAASjC,EAAOuC,CAAI,CAAC,CAClD,CACF,CAAC,EACDP,CACF,CAAC,CAEE,UAAU,EAAM,IAAUS,EAAU/C,EAAM,OAAO,EAAI+C,EAAU9C,EAAK,OAAO,CAAC,CAAC,CAC7E,IAAK,GAAY+C,EAAQ,MAAM,EAM9BC,EAASR,EAAWS,CAAoB,EAC9C,MAAO,CACL,GAAG/D,EACDgE,EACA,cAEArB,EAAkB,IAAI,IAAIY,EAAQ,QAAS,GAAWE,EAAO,WAAW,CAAC,CAAC,CAAC,IAAI,CACjF,EAGA,SAAUK,EAAO,OAAS,EAAIA,EAASR,EAAW,CAAC,CACrD,CACF,CAAC,CACH,CAEA,SAASP,EAAkB,EAA2D,CACpF,GAAM,CAAE,eAAgBF,EAExB,OADApE,EAAO,GAAGwF,EAAa,wBAAwBpB,EAAW,KAAK,kBAAkB,EAC1E,CAAE,GAAGA,EAAY,aAAY,CACtC,CAEA,SAASe,EAAU,EAAiD,CAClE,OAAOM,EAAY,QAAQ,EAAK,IAAeC,EAAMtB,EAAW,WAAY,CAAC,CAC/E,CAMA,SAASQ,EACP,EACA,EACyD,CACzD,IAAMe,EAAmE,CAAC,EACpEC,EAAUC,EAAU,UACvB,EAAM,IAAUvD,EAAeD,EAAK,KAAMD,EAAM,IAAI,GAAKC,EAAK,UAAYD,EAAM,SACnF,EACA,IAAK,IAAMgB,KAAYwC,EAAS,CAC9B,IAAME,EAAOH,EAAQ,GAAG,EAAE,EAO1B,GAJEG,GACAC,EAASD,EAAK,OAAQ1C,CAAQ,IAC7B0C,EAAK,QAAQ,KAAM,GAAWnB,EAASK,EAAQ5B,CAAQ,CAAC,GACvD,CAAC0C,EAAK,QAAQ,KAAM,GAAWpB,EAAUM,EAAQ5B,CAAQ,CAAC,GAE5D,EAAK,OAAO,QAAU,KAAK,IAAI0C,EAAK,OAAO,QAAS1C,EAAS,OAAO,EACpE0C,EAAK,QAAQ,KAAK1C,CAAQ,MACrB,CACL,GAAM,CAAE,OAAM,YAAW,WAAYA,EACrCuC,EAAQ,KAAK,CAAE,OAAQ,CAAE,OAAM,YAAW,SAAQ,EAAG,QAAS,CAACvC,CAAQ,CAAE,CAAC,CAC5E,CACF,CACA,OAAOuC,CACT,CAGA,SAAShB,EAAS,EAAsB,EAA+B,CACrE,OAAOjC,EAAM,OAASI,EAAM,MAAQJ,EAAM,WAAaI,EAAM,WAAaA,EAAM,SAAWJ,EAAM,OACnG,CAEA,SAASqD,EAAS,EAAqB,EAA+B,CACpE,OAAO1D,EAAK,OAASD,EAAM,MAAQC,EAAK,WAAaD,EAAM,SAAWA,EAAM,WAAaC,EAAK,OAChG,CAGA,SAASC,EAAe,EAAc,EAAuB,CAC3D,OAAOD,EAAOD,EAAQ,GAAK,IAAOA,EACpC"}
1
+ {"version":3,"file":"thresholdCheck.js","names":["assert","violationKinds","nestedInclusiveMetrics","functionThresholds","includesNestedFunctions","metricNameOf","threshold","checkThresholds","crossFileGroupsByFile","indexGroupsByFile","duplication","files","file","violations","directiveProblems","checkedFunctionCount","allowedLimitCount","changedFile","checkedFile","expandHunks","limits","limitsOf","crossFileGroups","directives","usage","checked","checkFile","createAllow","collectDirectiveProblems","compareViolations","compareStrings","left","right","hunks","hunk","allowDirectives","touchesSpan","startLine","endsAtFunction","endLine","metrics","exceeded","applying","directive","limit","covers","allowing","onAllowed","fileExceeded","allow","collectExceeded","fileThresholds","target","toViolation","checkedFunctions","listCheckedFunctions","exceedingFunctions","fn","ownExceeded","isReportedByEnclosingFunction","encloses","toFunctionViolation","collectDuplicationViolations","isTouched","problems","line","describeDirectiveProblems","problem","prefix","metric","max","messages","thresholds","candidate","errorLimit","errorMax","duplicationThreshold","allowsErrors","outgrown","used","levels","endsByIndentation","outer","startsBefore","inner","endsAfter","exceedsCognitiveComplexity","qualifyFunctionName","containerName","name","location","findExceeded","subject","value","level","violates","groupsByFile","group","crossFileDuplication","groups","findExceededLines","occurrence","toCloneOccurrence","isReported","block","groupOf","areCopies","contains","mergeOverlapping","sources","source","merged","listCopies","tokenCount","copy","minTokenPercent","sumTokens","partner","copies","minSimilarityPercent","lineNumbers","occurrences","sum","regions","ordered","locations","last","overlaps"],"sources":["../src/thresholdCheck.ts"],"sourcesContent":["import assert from 'node:assert';\n\nimport type { AllowDirective, AllowDirectives } from './allowDirectives.js';\nimport type {\n CrossFileDuplicateBlockGroup,\n CrossFileDuplicateOccurrence,\n CrossFileDuplicationMetrics,\n} from './crossFileDuplication.js';\nimport type { LineHunk } from './git.js';\nimport {\n duplicationThreshold,\n fileThresholds,\n functionThresholds,\n type Level,\n levels,\n type LimitsByLevel,\n metricNameOf,\n type Threshold,\n thresholds,\n} from './thresholds.js';\nimport type { CodeMetrics, CognitiveBlock, FunctionMetrics } from './types.js';\n\n/** A measured file to check, under the path cross-file duplication knows it by. */\nexport interface CheckedFile {\n file: string;\n metrics: CodeMetrics;\n /** The change to the file; when set, only what the change touches is checked. */\n hunks?: readonly LineHunk[];\n allowDirectives?: AllowDirectives;\n}\n\n/** The duplication of the measured files, in which duplicated blocks and their copies are found. */\nexport interface CheckedDuplication {\n crossFile?: CrossFileDuplicationMetrics;\n /** The detection's `minSimilarityPercent`, the share of a block a copy of it holds at least. */\n minSimilarityPercent: number;\n}\n\nexport interface ExceededLimit {\n metric: string;\n /** What reports call the metric (`cognitive complexity`, `duplicated lines`). */\n label: string;\n value: number;\n /** The most severe level whose limit the value violates. */\n level: Level;\n /** That level's limit: the value is allowed up to it; a duplicated block is allowed below it. */\n limit: number;\n}\n\nexport interface BlockLocation {\n file: string;\n startLine: number;\n endLine: number;\n}\n\n/** One copy of a clone group. */\ninterface CloneOccurrence extends BlockLocation {\n /**\n * Its code lines matched in another copy, whose number is the length of a duplicated block. Its\n * line span would also count the lines the copies do not share.\n */\n lineNumbers: readonly number[];\n tokenCount: number;\n}\n\nexport interface Violation extends BlockLocation {\n kind: 'function' | 'file' | 'duplication';\n /** The most severe level among `exceeded`. */\n level: Level;\n /** The function's name, qualified with the class it is a member of, for kind `function`. */\n name?: string;\n exceeded: ExceededLimit[];\n /**\n * The parts adding the most to the cognitive complexity, largest first, for a function that\n * exceeds a limit of it and has such parts: operator sequences and jumps outside any branching\n * construct add to the complexity without being one.\n */\n largestBlocks?: CognitiveBlock[];\n /** The other copies of the block, most matched tokens first, for kind `duplication`. */\n partners?: BlockLocation[];\n}\n\nexport interface LocatedDirectiveProblem {\n file: string;\n line: number;\n message: string;\n}\n\nexport interface CheckResult {\n violations: Violation[];\n checkedFunctionCount: number;\n /** The exceeded limits a directive allowed, which `violations` leaves out. */\n allowedLimitCount: number;\n /** Ordered by path, then line. */\n directiveProblems: LocatedDirectiveProblem[];\n}\n\n/** The directives that allowed a warning, and those covering a value above their max. */\ninterface DirectiveUsage {\n used: Set<AllowDirective>;\n outgrown: Set<AllowDirective>;\n}\n\n/** The limits `exceeded` keeps: those no directive `covers` allows. */\ntype Allow = (exceeded: ExceededLimit[], covers: (directive: AllowDirective) => boolean) => ExceededLimit[];\n\nconst violationKinds: readonly Violation['kind'][] = ['file', 'function', 'duplication'];\n\nconst cognitiveComplexityMetric = 'functionCognitiveComplexity';\n\nconst nestedInclusiveMetrics = new Set(\n functionThresholds\n .filter(({ includesNestedFunctions }) => includesNestedFunctions)\n .map((threshold) => metricNameOf(threshold))\n);\n\n/**\n * Every limit the files exceed and no directive allows, errors first, then ordered by path, then\n * line.\n */\nexport function checkThresholds(\n files: readonly CheckedFile[],\n duplication: CheckedDuplication,\n limitsOf: (language: string) => LimitsByLevel\n): CheckResult {\n const crossFileGroupsByFile = indexGroupsByFile(duplication.crossFile, new Set(files.map(({ file }) => file)));\n const violations: Violation[] = [];\n const directiveProblems: LocatedDirectiveProblem[] = [];\n let checkedFunctionCount = 0;\n let allowedLimitCount = 0;\n for (const changedFile of files) {\n const checkedFile = { ...changedFile, hunks: expandHunks(changedFile) };\n const limits = limitsOf(checkedFile.metrics.language);\n const crossFileGroups = crossFileGroupsByFile.get(checkedFile.file) ?? [];\n const directives = checkedFile.allowDirectives?.directives ?? [];\n const usage: DirectiveUsage = { used: new Set(), outgrown: new Set() };\n const checked = checkFile(\n checkedFile,\n crossFileGroups,\n limits,\n duplication,\n createAllow(directives, usage, () => allowedLimitCount++)\n );\n violations.push(...checked.violations);\n checkedFunctionCount += checked.checkedFunctionCount;\n // What the change does not touch may use a directive too, so whether one is used is decided on\n // the whole file.\n if (checkedFile.hunks && directives.length > 0) {\n checkFile(\n { ...checkedFile, hunks: undefined },\n crossFileGroups,\n limits,\n duplication,\n createAllow(directives, usage, () => {})\n );\n }\n directiveProblems.push(...collectDirectiveProblems(checkedFile, usage, limits));\n }\n return {\n violations: violations.toSorted(compareViolations),\n checkedFunctionCount,\n allowedLimitCount,\n directiveProblems: directiveProblems.toSorted(\n (left, right) => compareStrings(left.file, right.file) || left.line - right.line\n ),\n };\n}\n\n/**\n * The change's hunks or, when the change touches a directive (it changes or deletes lines of a\n * directive's attachment span, or deletes a directive), the whole\n * file: such a change may alter what any directive of the file allows.\n */\nfunction expandHunks({ metrics, hunks, allowDirectives }: CheckedFile): readonly LineHunk[] | undefined {\n const touchesDirective = hunks?.some(\n (hunk) =>\n hunk.deletesDirective ||\n allowDirectives?.attachmentSpans.some(({ startLine, endLine, endsAtFunction }) =>\n // An edit of the function's first line touches the function itself, but a deletion right\n // above it lies between the function and its directive.\n touchesSpan(hunk, startLine, hunk.headCount > 0 && endsAtFunction ? endLine - 1 : endLine)\n )\n );\n return touchesDirective ? [{ headStart: 1, headCount: metrics.lines.total }] : hunks;\n}\n\n/** Allows the warnings of the directives covering them, recording which directives applied. */\nfunction createAllow(directives: readonly AllowDirective[], usage: DirectiveUsage, onAllowed: () => void): Allow {\n return (exceeded, covers) =>\n exceeded.filter((limit) => {\n const applying = directives.filter((directive) => directive.metric === limit.metric && covers(directive));\n const allowing = limit.level === 'warning' ? applying.filter((directive) => limit.value <= directive.max) : [];\n for (const directive of applying) {\n if (allowing.includes(directive)) {\n usage.used.add(directive);\n } else if (limit.value > directive.max) {\n usage.outgrown.add(directive);\n }\n }\n if (allowing.length > 0) onAllowed();\n return allowing.length === 0;\n });\n}\n\nfunction checkFile(\n checkedFile: CheckedFile,\n crossFileGroups: readonly CrossFileDuplicateBlockGroup[],\n limits: LimitsByLevel,\n duplication: CheckedDuplication,\n allow: Allow\n): { violations: Violation[]; checkedFunctionCount: number } {\n const { file, metrics, hunks } = checkedFile;\n const violations: Violation[] = [];\n // A rename or a mode change lists the file as changed without touching any of its lines.\n const fileExceeded =\n hunks?.length === 0 ? [] : allow(collectExceeded(fileThresholds, metrics, limits), ({ target }) => !target);\n if (fileExceeded.length > 0) {\n violations.push(toViolation({ file, startLine: 1, endLine: metrics.lines.total }, 'file', fileExceeded));\n }\n const checkedFunctions = listCheckedFunctions(checkedFile);\n const exceedingFunctions = checkedFunctions\n .map((fn) => ({ fn, exceeded: collectExceeded(functionThresholds, fn, limits) }))\n .filter(({ exceeded }) => exceeded.length > 0);\n for (const { fn, exceeded } of exceedingFunctions) {\n const ownExceeded = allow(\n exceeded.filter((limit) => !isReportedByEnclosingFunction(limit, fn, exceedingFunctions)),\n ({ target }) => !target || target === fn || encloses(target, fn)\n );\n if (ownExceeded.length > 0) {\n violations.push(toFunctionViolation(file, fn, ownExceeded));\n }\n }\n violations.push(...collectDuplicationViolations(checkedFile, crossFileGroups, limits, duplication, allow));\n return { violations, checkedFunctionCount: checkedFunctions.length };\n}\n\n/**\n * The parse problems of the file's directives and the problems of each directive; with `hunks`,\n * those whose line or covered code the change touches.\n */\nfunction collectDirectiveProblems(\n { file, metrics, hunks, allowDirectives }: CheckedFile,\n usage: DirectiveUsage,\n limits: LimitsByLevel\n): LocatedDirectiveProblem[] {\n const isTouched = (startLine: number, endLine: number): boolean =>\n !hunks || hunks.some((hunk) => touchesSpan(hunk, startLine, endLine));\n const problems = (allowDirectives?.problems ?? []).filter(({ line }) => isTouched(line, line));\n for (const directive of allowDirectives?.directives ?? []) {\n const { line, target } = directive;\n if (target ? isTouched(line, target.endLine) : isTouched(1, metrics.lines.total)) {\n problems.push(...describeDirectiveProblems(directive, usage, limits).map((message) => ({ line, message })));\n }\n }\n return problems.map((problem) => ({ file, ...problem }));\n}\n\n/** Whether the directive's max reaches the error level, which it cannot allow, and whether a value outgrew it or it allows nothing. */\nfunction describeDirectiveProblems(\n directive: AllowDirective,\n { used, outgrown }: DirectiveUsage,\n limits: LimitsByLevel\n): string[] {\n const { metric, max, target } = directive;\n const prefix = `${target ? 'code-gauge-allow' : 'code-gauge-allow-file'} ${metric} ${max}`;\n const messages: string[] = [];\n const threshold = thresholds.find((candidate) => metricNameOf(candidate) === metric);\n const errorLimit = threshold ? (limits.error[threshold.key] as number) : Infinity;\n // A duplicated block violates from its limit on, so the largest value it allows is below it.\n const errorMax = threshold === duplicationThreshold ? Math.max(Math.ceil(errorLimit) - 1, 0) : errorLimit;\n const allowsErrors = max > errorMax;\n if (allowsErrors) {\n messages.push(`${prefix}: values above ${errorMax} are errors, which a directive cannot allow`);\n }\n if (outgrown.has(directive)) {\n messages.push(`${prefix}: the value grew above ${max}; reduce it or reconsider the directive`);\n } else if (!used.has(directive) && !allowsErrors) {\n messages.push(`${prefix}: allows no warning; remove the directive`);\n }\n return messages;\n}\n\n// The kind is compared before the end line because a file violation's end line is corrected after\n// this ordering (to the last line that exists), and the order must not depend on that correction.\nfunction compareViolations(left: Violation, right: Violation): number {\n return (\n levels.indexOf(right.level) - levels.indexOf(left.level) ||\n compareStrings(left.file, right.file) ||\n left.startLine - right.startLine ||\n violationKinds.indexOf(left.kind) - violationKinds.indexOf(right.kind) ||\n left.endLine - right.endLine\n );\n}\n\n/** The file's functions or, for a changed file, those the change touches. */\nfunction listCheckedFunctions({ metrics, hunks }: CheckedFile): FunctionMetrics[] {\n const endsByIndentation = metrics.language === 'python';\n return hunks\n ? metrics.functions.filter((fn) =>\n hunks.some((hunk) => touchesSpan(hunk, fn.startLine, fn.endLine, endsByIndentation))\n )\n : metrics.functions;\n}\n\n/**\n * Whether a function enclosing `fn` exceeds the same limit with a value that covers `fn`, so\n * reporting it for `fn` too would count the same code twice. The enclosing value is at least that\n * of `fn` and both share the file's limits, so its level is never milder.\n */\nfunction isReportedByEnclosingFunction(\n limit: ExceededLimit,\n fn: FunctionMetrics,\n exceedingFunctions: readonly { fn: FunctionMetrics; exceeded: ExceededLimit[] }[]\n): boolean {\n if (!nestedInclusiveMetrics.has(limit.metric)) {\n return false;\n }\n return exceedingFunctions.some(\n (outer) => outer.fn !== fn && encloses(outer.fn, fn) && outer.exceeded.some(({ metric }) => metric === limit.metric)\n );\n}\n\nfunction encloses(outer: FunctionMetrics, inner: FunctionMetrics): boolean {\n const startsBefore =\n outer.startLine < inner.startLine ||\n (outer.startLine === inner.startLine && outer.startColumn <= inner.startColumn);\n const endsAfter =\n outer.endLine > inner.endLine || (outer.endLine === inner.endLine && outer.endColumn >= inner.endColumn);\n return startsBefore && endsAfter;\n}\n\nfunction toFunctionViolation(file: string, fn: FunctionMetrics, exceeded: ExceededLimit[]): Violation {\n const { startLine, endLine } = fn;\n const exceedsCognitiveComplexity = exceeded.some(({ metric }) => metric === cognitiveComplexityMetric);\n return {\n ...toViolation({ file, startLine, endLine }, 'function', exceeded),\n name: qualifyFunctionName(fn),\n ...(exceedsCognitiveComplexity && fn.cognitiveBlocks.length > 0 && { largestBlocks: fn.cognitiveBlocks }),\n };\n}\n\n/** How reports name a function: with the type or module it is a member of, so that it can be found. */\nexport function qualifyFunctionName({ name = '<anonymous>', containerName }: FunctionMetrics): string {\n return containerName ? `${containerName}.${name}` : name;\n}\n\nfunction toViolation(location: BlockLocation, kind: Violation['kind'], exceeded: ExceededLimit[]): Violation {\n const level = exceeded.some((limit) => limit.level === 'error') ? 'error' : 'warning';\n return { kind, level, ...location, exceeded };\n}\n\nfunction collectExceeded<Subject>(\n thresholds: readonly Threshold<Subject>[],\n subject: Subject,\n limits: LimitsByLevel\n): ExceededLimit[] {\n return thresholds.flatMap((threshold) => findExceeded(threshold, subject, limits, (value, limit) => value > limit));\n}\n\n/** The most severe violated limit of the threshold, if any. */\nfunction findExceeded<Subject>(\n threshold: Threshold<Subject>,\n subject: Subject,\n limits: LimitsByLevel,\n violates: (value: number, limit: number) => boolean\n): ExceededLimit[] {\n const value = threshold.measure(subject);\n const level = levels.findLast((candidate) => violates(value, limits[candidate][threshold.key] as number));\n return level\n ? [\n {\n metric: metricNameOf(threshold),\n label: threshold.label,\n value,\n level,\n limit: limits[level][threshold.key] as number,\n },\n ]\n : [];\n}\n\n/**\n * Whether the hunk changes the head lines [startLine, endLine]. A hunk without head lines (a pure\n * deletion) sits between its headStart line and the next one. Where indentation ends a span, its\n * last line is a statement rather than a closing delimiter, so a deletion right after that line may\n * have removed the span's tail; `endsByIndentation` counts it as touching, since the head alone\n * cannot tell it from a deletion after the span.\n */\nfunction touchesSpan(hunk: LineHunk, startLine: number, endLine: number, endsByIndentation = false): boolean {\n return hunk.headCount === 0\n ? hunk.headStart >= startLine && (hunk.headStart < endLine || (endsByIndentation && hunk.headStart === endLine))\n : hunk.headStart <= endLine && hunk.headStart + hunk.headCount > startLine;\n}\n\n/** The cross-file groups with an occurrence in each of the given files. */\nfunction indexGroupsByFile(\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n files: ReadonlySet<string>\n): Map<string, CrossFileDuplicateBlockGroup[]> {\n const groupsByFile = new Map<string, CrossFileDuplicateBlockGroup[]>();\n for (const group of crossFileDuplication?.groups ?? []) {\n for (const file of group.files) {\n if (files.has(file)) {\n const groups = groupsByFile.get(file) ?? [];\n groups.push(group);\n groupsByFile.set(file, groups);\n }\n }\n }\n return groupsByFile;\n}\n\n/**\n * One violation per duplicated region of the file: its clone occurrences, within-file and\n * cross-file, that hold at least the duplicated lines of a level (with `hunks`, only those\n * overlapping added lines), with overlapping occurrences merged into one region unless they are\n * copies of each other.\n */\nfunction collectDuplicationViolations(\n { file, metrics, hunks }: CheckedFile,\n crossFileGroups: readonly CrossFileDuplicateBlockGroup[],\n limits: LimitsByLevel,\n { minSimilarityPercent }: CheckedDuplication,\n allow: Allow\n): Violation[] {\n const findExceededLines = (duplicatedLineCount: number): ExceededLimit[] =>\n findExceeded(duplicationThreshold, { duplicatedLineCount }, limits, (value, limit) => value >= limit);\n const groups: CloneOccurrence[][] = [\n ...metrics.duplication.duplicateBlockGroups.map((group) => group.map((occurrence) => ({ ...occurrence, file }))),\n ...crossFileGroups.map((group) => group.occurrences.map((occurrence) => toCloneOccurrence(occurrence))),\n ];\n const isReported = (block: CloneOccurrence): boolean =>\n block.file === file &&\n findExceededLines(block.lineNumbers.length).length > 0 &&\n (!hunks || hunks.some((hunk) => hunk.headCount > 0 && touchesSpan(hunk, block.startLine, block.endLine)));\n const groupOf = new Map(groups.flatMap((group) => group.map((occurrence) => [occurrence, group] as const)));\n // Copies of one group that share a line (the branches of an `else if` chain, the entries of a\n // list) stay apart: merged, they would be one block without a copy. One lying within another is\n // a repetition inside that block, not a block beside it.\n const areCopies = (left: CloneOccurrence, right: CloneOccurrence): boolean =>\n groupOf.get(left) === groupOf.get(right) && !contains(left, right) && !contains(right, left);\n const reportedBlocks = groups.flat().filter((occurrence) => isReported(occurrence));\n return mergeOverlapping(reportedBlocks, areCopies).flatMap(({ merged, sources }) => {\n // Occurrences that overlap share lines, which count once.\n const exceeded = allow(\n findExceededLines(new Set(sources.flatMap((source) => source.lineNumbers)).size),\n ({ target }) => !target || (target.startLine <= merged.startLine && merged.startLine <= target.endLine)\n );\n if (exceeded.length === 0) {\n return [];\n }\n const listCopies = (minTokenPercent: number): BlockLocation[] =>\n mergeOverlapping(\n sources.flatMap((source) => {\n // The region merges every clone that overlaps it, so a copy of an occurrence lying\n // within a larger one is measured against the larger one.\n const tokenCount = Math.max(\n ...sources.filter((outer) => contains(outer, source)).map((outer) => outer.tokenCount)\n );\n return (groupOf.get(source) ?? []).filter(\n (copy) =>\n copy.tokenCount * 100 >= tokenCount * minTokenPercent &&\n // The region's own occurrences are among the copies of the groups it belongs to, and\n // so may be an occurrence within one of them that is not reported itself: a\n // repetition inside the block, not another place.\n !sources.some((outer) => contains(outer, copy))\n );\n }),\n areCopies\n )\n // The largest say the most about what to share.\n .toSorted((left, right) => sumTokens(right.sources) - sumTokens(left.sources))\n .map((partner) => partner.merged);\n // A group joins a block with every place repeating a part of it: a function another file\n // copies whole is grouped with each file holding a few of its lines. Such a fragment is not a\n // copy of the block: sharing it would not remove the block, and a report listing it reads as\n // one more copy. Sizes are compared in tokens, since the same code may be laid out over any\n // number of lines.\n const copies = listCopies(minSimilarityPercent);\n return {\n ...toViolation(merged, 'duplication', exceeded),\n // A block whose parts were matched with different places has no copy of its size. The\n // places then are all that locates what it repeats.\n partners: copies.length > 0 ? copies : listCopies(0),\n };\n });\n}\n\nfunction toCloneOccurrence(occurrence: CrossFileDuplicateOccurrence): CloneOccurrence {\n const { lineNumbers } = occurrence;\n assert.ok(lineNumbers, `No matched lines for ${occurrence.file}, a scanned file.`);\n return { ...occurrence, lineNumbers };\n}\n\nfunction sumTokens(occurrences: readonly CloneOccurrence[]): number {\n return occurrences.reduce((sum, occurrence) => sum + occurrence.tokenCount, 0);\n}\n\n/**\n * The locations with those overlapping in the same file merged, except those `areCopies` holds\n * for, ordered by file, then line.\n */\nfunction mergeOverlapping(\n locations: readonly CloneOccurrence[],\n areCopies: (left: CloneOccurrence, right: CloneOccurrence) => boolean\n): { merged: BlockLocation; sources: CloneOccurrence[] }[] {\n const regions: { merged: BlockLocation; sources: CloneOccurrence[] }[] = [];\n const ordered = locations.toSorted(\n (left, right) => compareStrings(left.file, right.file) || left.startLine - right.startLine\n );\n for (const location of ordered) {\n const last = regions.at(-1);\n // A block containing the location holds it whatever it is a copy of.\n const joins =\n last &&\n overlaps(last.merged, location) &&\n (last.sources.some((source) => contains(source, location)) ||\n !last.sources.some((source) => areCopies(source, location)));\n if (joins) {\n last.merged.endLine = Math.max(last.merged.endLine, location.endLine);\n last.sources.push(location);\n } else {\n const { file, startLine, endLine } = location;\n regions.push({ merged: { file, startLine, endLine }, sources: [location] });\n }\n }\n return regions;\n}\n\n/** Whether `outer` spans every line of `inner`; a block contains itself. */\nfunction contains(outer: BlockLocation, inner: BlockLocation): boolean {\n return outer.file === inner.file && outer.startLine <= inner.startLine && inner.endLine <= outer.endLine;\n}\n\nfunction overlaps(left: BlockLocation, right: BlockLocation): boolean {\n return left.file === right.file && left.startLine <= right.endLine && right.startLine <= left.endLine;\n}\n\n/** Code-unit order, so reports do not depend on the locale. */\nfunction compareStrings(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"thresholds.cjs","names":["levels","functionThresholds","fn","fileThresholds","file","duplicationThreshold","block","thresholds","metricNameOf","name","threshold","resolveLimits","resolveLevel","levelConfig","config","level","key","defaultLimits","language","cliLimits"],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** The severities of a violation, mildest first. */\nexport const levels = ['warning', 'error'] as const;\nexport type Level = (typeof levels)[number];\n\n/**\n * One threshold of `code-gauge check`. Its `key` is the config setting, the per-language override\n * setting, and (kebab-cased after a level prefix) the command-line options, so a new entry here is\n * all a new thresholded metric needs.\n */\nexport interface Threshold<Subject = never> {\n key: string;\n /** How the report names the measured value. */\n label: string;\n /** `Infinity` leaves the level unchecked unless the config or the command line sets a limit. */\n defaultLimits: Readonly<Record<Level, number>>;\n /** What to change in the code when the limit is exceeded. */\n hint: string;\n measure: (subject: Subject) => number;\n /**\n * The value of a function is at least that of each function nested in it, whose code it counts\n * too. Not so for Halstead difficulty and effort: a ratio can be higher for the nested part alone.\n */\n includesNestedFunctions?: true;\n}\n\n/** Limits by threshold key; `Infinity` disables a limit. */\nexport type Limits = Readonly<Record<string, number>>;\nexport type LimitsByLevel = Readonly<Record<Level, Limits>>;\n\nexport const functionThresholds: readonly Threshold<FunctionMetrics>[] = [\n {\n key: 'maxFunctionCognitiveComplexity',\n label: 'cognitive complexity',\n defaultLimits: { warning: 15, error: 30 },\n hint: 'flatten nested branching with early returns and extract nested blocks into named functions.',\n measure: (fn) => fn.cognitiveComplexity,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionCyclomaticComplexity',\n label: 'cyclomatic complexity',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function has too many independent paths; split it by decision or replace condition chains with a lookup table.',\n measure: (fn) => fn.cyclomaticComplexity,\n },\n {\n key: 'maxFunctionNcss',\n label: 'NCSS',\n defaultLimits: { warning: 30, error: 60 },\n hint: 'move each group of statements that forms one step into a named function.',\n measure: (fn) => fn.ncss,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionNestingDepth',\n label: 'nesting depth',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'replace nested conditions with guard clauses or move inner blocks into functions.',\n measure: (fn) => fn.nestingDepth,\n },\n {\n key: 'maxFunctionParameterCount',\n label: 'parameters',\n defaultLimits: { warning: 6, error: Infinity },\n hint: 'group related parameters into one object or split the function.',\n measure: (fn) => fn.parameterCount,\n },\n {\n key: 'maxFunctionHalsteadVolume',\n label: 'Halstead volume',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function holds too many operators and operands; move cohesive parts into helper functions.',\n measure: (fn) => fn.halstead.volume,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionHalsteadDifficulty',\n label: 'Halstead difficulty',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function reuses the same operands many times; split it so each part handles fewer values.',\n measure: (fn) => fn.halstead.difficulty,\n },\n {\n key: 'maxFunctionHalsteadEffort',\n label: 'Halstead effort',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function is both large and dense; split it into smaller functions with fewer values each.',\n measure: (fn) => fn.halstead.effort,\n },\n {\n key: 'maxFunctionDepDegree',\n label: 'DepDegree',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'too many values flow between the variables of the function; split it so each part works on fewer variables.',\n measure: (fn) => fn.depDegree,\n includesNestedFunctions: true,\n },\n];\n\nexport const fileThresholds: readonly Threshold<CodeMetrics>[] = [\n {\n key: 'maxFileNcss',\n label: 'file NCSS',\n defaultLimits: { warning: 400, error: 1000 },\n hint: 'split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.',\n measure: (file) => file.ncssCount,\n },\n];\n\n/**\n * A duplicated block violates when it holds at least this many duplicated lines, its code lines\n * matched in another copy: comment-only and blank lines do not count, so a copy reads the same\n * length whether or not it is commented, and neither do the lines the copies do not share, those\n * between the matched parts of an edited copy and those a near-miss copy adds or rewrites.\n */\nexport const duplicationThreshold: Threshold<{ duplicatedLineCount: number }> = {\n key: 'minDuplicateLines',\n label: 'duplicated lines',\n defaultLimits: { warning: 15, error: Infinity },\n hint: 'extract the repeated code into one shared function or module and call it from every location.',\n measure: (block) => block.duplicatedLineCount,\n};\n\nexport const thresholds: readonly Threshold[] = [...functionThresholds, ...fileThresholds, duplicationThreshold];\n\n/** The name a report gives a threshold's measured value: its key without the `max`/`min` prefix. */\nexport function metricNameOf(threshold: Threshold): string {\n const name = threshold.key.slice(3);\n return name.charAt(0).toLowerCase() + name.slice(1);\n}\n\n/** One level of the `thresholds` config section; a missing key falls back to the less specific layer. */\nexport interface LevelThresholdsConfig {\n limits: Limits;\n /** Overrides for the files of one language, keyed by language name. */\n languages: Readonly<Record<string, Limits>>;\n}\n\nexport type ThresholdsConfig = Readonly<Partial<Record<Level, LevelThresholdsConfig>>>;\n\n/**\n * The limits of each level applying to files of each language, with precedence command-line\n * options > the config's per-language overrides > the config's limits > built-in defaults.\n */\nexport function resolveLimits(\n cliLimits: LimitsByLevel,\n config: ThresholdsConfig | undefined\n): (language: string) => LimitsByLevel {\n const resolveLevel = (level: Level, language: string): Limits => {\n const levelConfig = config?.[level];\n return {\n ...Object.fromEntries(thresholds.map(({ key, defaultLimits }) => [key, defaultLimits[level]])),\n ...levelConfig?.limits,\n // Object.hasOwn: a language named like an Object.prototype member must not read an inherited value.\n ...(levelConfig && Object.hasOwn(levelConfig.languages, language) ? levelConfig.languages[language] : undefined),\n ...cliLimits[level],\n };\n };\n return (language) => ({ warning: resolveLevel('warning', language), error: resolveLevel('error', language) });\n}\n"],"mappings":"aAGA,MAAaA,EAAS,CAAC,UAAW,OAAO,EA4B5BC,EAA4D,CACvE,CACE,IAAK,iCACL,MAAO,uBACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,8FACN,QAAU,GAAOC,EAAG,oBACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,kCACL,MAAO,wBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,qHACN,QAAU,GAAOA,EAAG,oBACtB,EACA,CACE,IAAK,kBACL,MAAO,OACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,2EACN,QAAU,GAAOA,EAAG,KACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,0BACL,MAAO,gBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,oFACN,QAAU,GAAOA,EAAG,YACtB,EACA,CACE,IAAK,4BACL,MAAO,aACP,cAAe,CAAE,QAAS,EAAG,MAAO,GAAS,EAC7C,KAAM,kEACN,QAAU,GAAOA,EAAG,cACtB,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,iGACN,QAAU,GAAOA,EAAG,SAAS,OAC7B,wBAAyB,EAC3B,EACA,CACE,IAAK,gCACL,MAAO,sBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAOA,EAAG,SAAS,UAC/B,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAOA,EAAG,SAAS,MAC/B,EACA,CACE,IAAK,uBACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,8GACN,QAAU,GAAOA,EAAG,UACpB,wBAAyB,EAC3B,CACF,EAEaC,EAAoD,CAC/D,CACE,IAAK,cACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAK,MAAO,GAAK,EAC3C,KAAM,gKACN,QAAU,GAASC,EAAK,SAC1B,CACF,EAQaC,EAAmE,CAC9E,IAAK,oBACL,MAAO,mBACP,cAAe,CAAE,QAAS,GAAI,MAAO,GAAS,EAC9C,KAAM,gGACN,QAAU,GAAUC,EAAM,mBAC5B,EAEaC,EAAmC,CAAC,GAAGN,EAAoB,GAAGE,EAAgBE,CAAoB,EAG/G,SAAgBG,EAAa,EAA8B,CACzD,IAAMC,EAAOC,EAAU,IAAI,MAAM,CAAC,EAClC,OAAOD,EAAK,OAAO,CAAC,CAAC,CAAC,YAAY,EAAIA,EAAK,MAAM,CAAC,CACpD,CAeA,SAAgBE,EACd,EACA,EACqC,CACrC,IAAMC,GAAgB,EAAc,IAA6B,CAC/D,IAAMC,EAAcC,IAASC,GAC7B,MAAO,CACL,GAAG,OAAO,YAAYR,EAAW,KAAK,CAAE,MAAK,mBAAoB,CAACS,EAAKC,EAAcF,EAAM,CAAC,CAAC,EAC7F,GAAGF,GAAa,OAEhB,GAAIA,GAAe,OAAO,OAAOA,EAAY,UAAWK,CAAQ,EAAIL,EAAY,UAAUK,GAAY,IAAA,GACtG,GAAGC,EAAUJ,EACf,CACF,EACA,MAAQ,KAAc,CAAE,QAASH,EAAa,UAAWM,CAAQ,EAAG,MAAON,EAAa,QAASM,CAAQ,CAAE,EAC7G"}
1
+ {"version":3,"file":"thresholds.cjs","names":["levels","functionThresholds","fn","fileThresholds","file","duplicationThreshold","block","thresholds","metricNameOf","name","threshold","resolveLimits","resolveLevel","levelConfig","config","level","key","defaultLimits","language","cliLimits"],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** The severities of a violation, mildest first. */\nexport const levels = ['warning', 'error'] as const;\nexport type Level = (typeof levels)[number];\n\n/**\n * One threshold of `code-gauge check`. Its `key` is the config setting, the per-language override\n * setting, and (kebab-cased after a level prefix) the command-line options, so a new entry here is\n * all a new thresholded metric needs.\n */\nexport interface Threshold<Subject = never> {\n key: string;\n /** How the report names the measured value. */\n label: string;\n /** `Infinity` leaves the level unchecked unless the config or the command line sets a limit. */\n defaultLimits: Readonly<Record<Level, number>>;\n /** What to change in the code when the limit is exceeded. */\n hint: string;\n measure: (subject: Subject) => number;\n /**\n * The value of a function is at least that of each function nested in it, whose code it counts\n * too. Not so for Halstead difficulty and effort: a ratio can be higher for the nested part alone.\n */\n includesNestedFunctions?: true;\n}\n\n/** Limits by threshold key; `Infinity` disables a limit. */\nexport type Limits = Readonly<Record<string, number>>;\nexport type LimitsByLevel = Readonly<Record<Level, Limits>>;\n\nexport const functionThresholds: readonly Threshold<FunctionMetrics>[] = [\n {\n key: 'maxFunctionCognitiveComplexity',\n label: 'cognitive complexity',\n defaultLimits: { warning: 15, error: 30 },\n hint: 'flatten nested branching with early returns and extract nested blocks into named functions.',\n measure: (fn) => fn.cognitiveComplexity,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionCyclomaticComplexity',\n label: 'cyclomatic complexity',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function has too many independent paths; split it by decision or replace condition chains with a lookup table.',\n measure: (fn) => fn.cyclomaticComplexity,\n },\n {\n key: 'maxFunctionNcss',\n label: 'NCSS',\n defaultLimits: { warning: 30, error: 60 },\n hint: 'move each group of statements that forms one step into a named function.',\n measure: (fn) => fn.ncss,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionNestingDepth',\n label: 'nesting depth',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'replace nested conditions with guard clauses or move inner blocks into functions.',\n measure: (fn) => fn.nestingDepth,\n },\n {\n key: 'maxFunctionParameterCount',\n label: 'parameters',\n defaultLimits: { warning: 6, error: Infinity },\n hint: 'group related parameters into one object or split the function.',\n measure: (fn) => fn.parameterCount,\n },\n {\n key: 'maxFunctionHalsteadVolume',\n label: 'Halstead volume',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function holds too many operators and operands; move cohesive parts into helper functions.',\n measure: (fn) => fn.halstead.volume,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionHalsteadDifficulty',\n label: 'Halstead difficulty',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function reuses the same operands many times; split it so each part handles fewer values.',\n measure: (fn) => fn.halstead.difficulty,\n },\n {\n key: 'maxFunctionHalsteadEffort',\n label: 'Halstead effort',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function is both large and dense; split it into smaller functions with fewer values each.',\n measure: (fn) => fn.halstead.effort,\n },\n {\n key: 'maxFunctionDepDegree',\n label: 'DepDegree',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'too many values flow between the variables of the function; split it so each part works on fewer variables.',\n measure: (fn) => fn.depDegree,\n includesNestedFunctions: true,\n },\n];\n\nexport const fileThresholds: readonly Threshold<CodeMetrics>[] = [\n {\n key: 'maxFileNcss',\n label: 'file NCSS',\n defaultLimits: { warning: 400, error: 1000 },\n hint: 'split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.',\n measure: (file) => file.ncssCount,\n },\n];\n\n/**\n * A duplicated block violates when it holds at least this many duplicated lines, its code lines\n * matched in another copy: comment-only and blank lines do not count, so a copy reads the same\n * length whether or not it is commented, and neither do the lines the copies do not share, those\n * between the matched parts of an edited copy and those a near-miss copy adds or rewrites.\n */\nexport const duplicationThreshold: Threshold<{ duplicatedLineCount: number }> = {\n key: 'minDuplicateLines',\n label: 'duplicated lines',\n defaultLimits: { warning: 15, error: Infinity },\n hint: 'extract the repeated code into one shared function or module and call it from every location.',\n measure: (block) => block.duplicatedLineCount,\n};\n\nexport const thresholds: readonly Threshold[] = [...functionThresholds, ...fileThresholds, duplicationThreshold];\n\n/** The name a report gives a threshold's measured value: its key without the `max`/`min` prefix. */\nexport function metricNameOf(threshold: Threshold): string {\n const name = threshold.key.slice(3);\n return name.charAt(0).toLowerCase() + name.slice(1);\n}\n\n/** One level of the `thresholds` config section; a missing key falls back to the less specific layer. */\nexport interface LevelThresholdsConfig {\n limits: Limits;\n /** Overrides for the files of one language, keyed by language name. */\n languages: Readonly<Record<string, Limits>>;\n}\n\nexport type ThresholdsConfig = Readonly<Partial<Record<Level, LevelThresholdsConfig>>>;\n\n/**\n * The limits of each level applying to files of each language, with precedence command-line\n * options > the config's per-language overrides > the config's limits > built-in defaults.\n */\nexport function resolveLimits(\n cliLimits: LimitsByLevel,\n config: ThresholdsConfig | undefined\n): (language: string) => LimitsByLevel {\n const resolveLevel = (level: Level, language: string): Limits => {\n const levelConfig = config?.[level];\n return {\n ...Object.fromEntries(thresholds.map(({ key, defaultLimits }) => [key, defaultLimits[level]])),\n ...levelConfig?.limits,\n // Object.hasOwn: a language named like an Object.prototype member must not read an inherited value.\n ...(levelConfig && Object.hasOwn(levelConfig.languages, language) ? levelConfig.languages[language] : undefined),\n ...cliLimits[level],\n };\n };\n return (language) => ({ warning: resolveLevel('warning', language), error: resolveLevel('error', language) });\n}\n"],"mappings":"A,aAGA,MAAaA,EAAS,CAAC,UAAW,OAAO,EA4B5BC,EAA4D,CACvE,CACE,IAAK,iCACL,MAAO,uBACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,8FACN,QAAU,GAAOC,EAAG,oBACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,kCACL,MAAO,wBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,qHACN,QAAU,GAAOA,EAAG,oBACtB,EACA,CACE,IAAK,kBACL,MAAO,OACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,2EACN,QAAU,GAAOA,EAAG,KACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,0BACL,MAAO,gBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,oFACN,QAAU,GAAOA,EAAG,YACtB,EACA,CACE,IAAK,4BACL,MAAO,aACP,cAAe,CAAE,QAAS,EAAG,MAAO,GAAS,EAC7C,KAAM,kEACN,QAAU,GAAOA,EAAG,cACtB,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,iGACN,QAAU,GAAOA,EAAG,SAAS,OAC7B,wBAAyB,EAC3B,EACA,CACE,IAAK,gCACL,MAAO,sBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAOA,EAAG,SAAS,UAC/B,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAOA,EAAG,SAAS,MAC/B,EACA,CACE,IAAK,uBACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,8GACN,QAAU,GAAOA,EAAG,UACpB,wBAAyB,EAC3B,CACF,EAEaC,EAAoD,CAC/D,CACE,IAAK,cACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAK,MAAO,GAAK,EAC3C,KAAM,gKACN,QAAU,GAASC,EAAK,SAC1B,CACF,EAQaC,EAAmE,CAC9E,IAAK,oBACL,MAAO,mBACP,cAAe,CAAE,QAAS,GAAI,MAAO,GAAS,EAC9C,KAAM,gGACN,QAAU,GAAUC,EAAM,mBAC5B,EAEaC,EAAmC,CAAC,GAAGN,EAAoB,GAAGE,EAAgBE,CAAoB,EAG/G,SAAgBG,EAAa,EAA8B,CACzD,IAAMC,EAAOC,EAAU,IAAI,MAAM,CAAC,EAClC,OAAOD,EAAK,OAAO,CAAC,CAAC,CAAC,YAAY,EAAIA,EAAK,MAAM,CAAC,CACpD,CAeA,SAAgBE,EACd,EACA,EACqC,CACrC,IAAMC,GAAgB,EAAc,IAA6B,CAC/D,IAAMC,EAAcC,IAASC,GAC7B,MAAO,CACL,GAAG,OAAO,YAAYR,EAAW,KAAK,CAAE,MAAK,mBAAoB,CAACS,EAAKC,EAAcF,EAAM,CAAC,CAAC,EAC7F,GAAGF,GAAa,OAEhB,GAAIA,GAAe,OAAO,OAAOA,EAAY,UAAWK,CAAQ,EAAIL,EAAY,UAAUK,GAAY,IAAA,GACtG,GAAGC,EAAUJ,EACf,CACF,EACA,MAAQ,KAAc,CAAE,QAASH,EAAa,UAAWM,CAAQ,EAAG,MAAON,EAAa,QAASM,CAAQ,CAAE,EAC7G,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"thresholds.js","names":["levels","functionThresholds","fn","fileThresholds","file","duplicationThreshold","block","thresholds","metricNameOf","name","threshold","resolveLimits","resolveLevel","levelConfig","config","level","key","defaultLimits","language","cliLimits"],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** The severities of a violation, mildest first. */\nexport const levels = ['warning', 'error'] as const;\nexport type Level = (typeof levels)[number];\n\n/**\n * One threshold of `code-gauge check`. Its `key` is the config setting, the per-language override\n * setting, and (kebab-cased after a level prefix) the command-line options, so a new entry here is\n * all a new thresholded metric needs.\n */\nexport interface Threshold<Subject = never> {\n key: string;\n /** How the report names the measured value. */\n label: string;\n /** `Infinity` leaves the level unchecked unless the config or the command line sets a limit. */\n defaultLimits: Readonly<Record<Level, number>>;\n /** What to change in the code when the limit is exceeded. */\n hint: string;\n measure: (subject: Subject) => number;\n /**\n * The value of a function is at least that of each function nested in it, whose code it counts\n * too. Not so for Halstead difficulty and effort: a ratio can be higher for the nested part alone.\n */\n includesNestedFunctions?: true;\n}\n\n/** Limits by threshold key; `Infinity` disables a limit. */\nexport type Limits = Readonly<Record<string, number>>;\nexport type LimitsByLevel = Readonly<Record<Level, Limits>>;\n\nexport const functionThresholds: readonly Threshold<FunctionMetrics>[] = [\n {\n key: 'maxFunctionCognitiveComplexity',\n label: 'cognitive complexity',\n defaultLimits: { warning: 15, error: 30 },\n hint: 'flatten nested branching with early returns and extract nested blocks into named functions.',\n measure: (fn) => fn.cognitiveComplexity,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionCyclomaticComplexity',\n label: 'cyclomatic complexity',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function has too many independent paths; split it by decision or replace condition chains with a lookup table.',\n measure: (fn) => fn.cyclomaticComplexity,\n },\n {\n key: 'maxFunctionNcss',\n label: 'NCSS',\n defaultLimits: { warning: 30, error: 60 },\n hint: 'move each group of statements that forms one step into a named function.',\n measure: (fn) => fn.ncss,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionNestingDepth',\n label: 'nesting depth',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'replace nested conditions with guard clauses or move inner blocks into functions.',\n measure: (fn) => fn.nestingDepth,\n },\n {\n key: 'maxFunctionParameterCount',\n label: 'parameters',\n defaultLimits: { warning: 6, error: Infinity },\n hint: 'group related parameters into one object or split the function.',\n measure: (fn) => fn.parameterCount,\n },\n {\n key: 'maxFunctionHalsteadVolume',\n label: 'Halstead volume',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function holds too many operators and operands; move cohesive parts into helper functions.',\n measure: (fn) => fn.halstead.volume,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionHalsteadDifficulty',\n label: 'Halstead difficulty',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function reuses the same operands many times; split it so each part handles fewer values.',\n measure: (fn) => fn.halstead.difficulty,\n },\n {\n key: 'maxFunctionHalsteadEffort',\n label: 'Halstead effort',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function is both large and dense; split it into smaller functions with fewer values each.',\n measure: (fn) => fn.halstead.effort,\n },\n {\n key: 'maxFunctionDepDegree',\n label: 'DepDegree',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'too many values flow between the variables of the function; split it so each part works on fewer variables.',\n measure: (fn) => fn.depDegree,\n includesNestedFunctions: true,\n },\n];\n\nexport const fileThresholds: readonly Threshold<CodeMetrics>[] = [\n {\n key: 'maxFileNcss',\n label: 'file NCSS',\n defaultLimits: { warning: 400, error: 1000 },\n hint: 'split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.',\n measure: (file) => file.ncssCount,\n },\n];\n\n/**\n * A duplicated block violates when it holds at least this many duplicated lines, its code lines\n * matched in another copy: comment-only and blank lines do not count, so a copy reads the same\n * length whether or not it is commented, and neither do the lines the copies do not share, those\n * between the matched parts of an edited copy and those a near-miss copy adds or rewrites.\n */\nexport const duplicationThreshold: Threshold<{ duplicatedLineCount: number }> = {\n key: 'minDuplicateLines',\n label: 'duplicated lines',\n defaultLimits: { warning: 15, error: Infinity },\n hint: 'extract the repeated code into one shared function or module and call it from every location.',\n measure: (block) => block.duplicatedLineCount,\n};\n\nexport const thresholds: readonly Threshold[] = [...functionThresholds, ...fileThresholds, duplicationThreshold];\n\n/** The name a report gives a threshold's measured value: its key without the `max`/`min` prefix. */\nexport function metricNameOf(threshold: Threshold): string {\n const name = threshold.key.slice(3);\n return name.charAt(0).toLowerCase() + name.slice(1);\n}\n\n/** One level of the `thresholds` config section; a missing key falls back to the less specific layer. */\nexport interface LevelThresholdsConfig {\n limits: Limits;\n /** Overrides for the files of one language, keyed by language name. */\n languages: Readonly<Record<string, Limits>>;\n}\n\nexport type ThresholdsConfig = Readonly<Partial<Record<Level, LevelThresholdsConfig>>>;\n\n/**\n * The limits of each level applying to files of each language, with precedence command-line\n * options > the config's per-language overrides > the config's limits > built-in defaults.\n */\nexport function resolveLimits(\n cliLimits: LimitsByLevel,\n config: ThresholdsConfig | undefined\n): (language: string) => LimitsByLevel {\n const resolveLevel = (level: Level, language: string): Limits => {\n const levelConfig = config?.[level];\n return {\n ...Object.fromEntries(thresholds.map(({ key, defaultLimits }) => [key, defaultLimits[level]])),\n ...levelConfig?.limits,\n // Object.hasOwn: a language named like an Object.prototype member must not read an inherited value.\n ...(levelConfig && Object.hasOwn(levelConfig.languages, language) ? levelConfig.languages[language] : undefined),\n ...cliLimits[level],\n };\n };\n return (language) => ({ warning: resolveLevel('warning', language), error: resolveLevel('error', language) });\n}\n"],"mappings":"AAGA,MAAaA,EAAS,CAAC,UAAW,OAAO,EA4B5BC,EAA4D,CACvE,CACE,IAAK,iCACL,MAAO,uBACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,8FACN,QAAU,GAAOC,EAAG,oBACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,kCACL,MAAO,wBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,qHACN,QAAU,GAAOA,EAAG,oBACtB,EACA,CACE,IAAK,kBACL,MAAO,OACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,2EACN,QAAU,GAAOA,EAAG,KACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,0BACL,MAAO,gBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,oFACN,QAAU,GAAOA,EAAG,YACtB,EACA,CACE,IAAK,4BACL,MAAO,aACP,cAAe,CAAE,QAAS,EAAG,MAAO,GAAS,EAC7C,KAAM,kEACN,QAAU,GAAOA,EAAG,cACtB,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,iGACN,QAAU,GAAOA,EAAG,SAAS,OAC7B,wBAAyB,EAC3B,EACA,CACE,IAAK,gCACL,MAAO,sBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAOA,EAAG,SAAS,UAC/B,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAOA,EAAG,SAAS,MAC/B,EACA,CACE,IAAK,uBACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,8GACN,QAAU,GAAOA,EAAG,UACpB,wBAAyB,EAC3B,CACF,EAEaC,EAAoD,CAC/D,CACE,IAAK,cACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAK,MAAO,GAAK,EAC3C,KAAM,gKACN,QAAU,GAASC,EAAK,SAC1B,CACF,EAQaC,EAAmE,CAC9E,IAAK,oBACL,MAAO,mBACP,cAAe,CAAE,QAAS,GAAI,MAAO,GAAS,EAC9C,KAAM,gGACN,QAAU,GAAUC,EAAM,mBAC5B,EAEaC,EAAmC,CAAC,GAAGN,EAAoB,GAAGE,EAAgBE,CAAoB,EAG/G,SAAgBG,EAAa,EAA8B,CACzD,IAAMC,EAAOC,EAAU,IAAI,MAAM,CAAC,EAClC,OAAOD,EAAK,OAAO,CAAC,CAAC,CAAC,YAAY,EAAIA,EAAK,MAAM,CAAC,CACpD,CAeA,SAAgBE,EACd,EACA,EACqC,CACrC,IAAMC,GAAgB,EAAc,IAA6B,CAC/D,IAAMC,EAAcC,IAASC,GAC7B,MAAO,CACL,GAAG,OAAO,YAAYR,EAAW,KAAK,CAAE,MAAK,mBAAoB,CAACS,EAAKC,EAAcF,EAAM,CAAC,CAAC,EAC7F,GAAGF,GAAa,OAEhB,GAAIA,GAAe,OAAO,OAAOA,EAAY,UAAWK,CAAQ,EAAIL,EAAY,UAAUK,GAAY,IAAA,GACtG,GAAGC,EAAUJ,EACf,CACF,EACA,MAAQ,KAAc,CAAE,QAASH,EAAa,UAAWM,CAAQ,EAAG,MAAON,EAAa,QAASM,CAAQ,CAAE,EAC7G"}
1
+ {"version":3,"file":"thresholds.js","names":["levels","functionThresholds","fn","fileThresholds","file","duplicationThreshold","block","thresholds","metricNameOf","name","threshold","resolveLimits","resolveLevel","levelConfig","config","level","key","defaultLimits","language","cliLimits"],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** The severities of a violation, mildest first. */\nexport const levels = ['warning', 'error'] as const;\nexport type Level = (typeof levels)[number];\n\n/**\n * One threshold of `code-gauge check`. Its `key` is the config setting, the per-language override\n * setting, and (kebab-cased after a level prefix) the command-line options, so a new entry here is\n * all a new thresholded metric needs.\n */\nexport interface Threshold<Subject = never> {\n key: string;\n /** How the report names the measured value. */\n label: string;\n /** `Infinity` leaves the level unchecked unless the config or the command line sets a limit. */\n defaultLimits: Readonly<Record<Level, number>>;\n /** What to change in the code when the limit is exceeded. */\n hint: string;\n measure: (subject: Subject) => number;\n /**\n * The value of a function is at least that of each function nested in it, whose code it counts\n * too. Not so for Halstead difficulty and effort: a ratio can be higher for the nested part alone.\n */\n includesNestedFunctions?: true;\n}\n\n/** Limits by threshold key; `Infinity` disables a limit. */\nexport type Limits = Readonly<Record<string, number>>;\nexport type LimitsByLevel = Readonly<Record<Level, Limits>>;\n\nexport const functionThresholds: readonly Threshold<FunctionMetrics>[] = [\n {\n key: 'maxFunctionCognitiveComplexity',\n label: 'cognitive complexity',\n defaultLimits: { warning: 15, error: 30 },\n hint: 'flatten nested branching with early returns and extract nested blocks into named functions.',\n measure: (fn) => fn.cognitiveComplexity,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionCyclomaticComplexity',\n label: 'cyclomatic complexity',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function has too many independent paths; split it by decision or replace condition chains with a lookup table.',\n measure: (fn) => fn.cyclomaticComplexity,\n },\n {\n key: 'maxFunctionNcss',\n label: 'NCSS',\n defaultLimits: { warning: 30, error: 60 },\n hint: 'move each group of statements that forms one step into a named function.',\n measure: (fn) => fn.ncss,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionNestingDepth',\n label: 'nesting depth',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'replace nested conditions with guard clauses or move inner blocks into functions.',\n measure: (fn) => fn.nestingDepth,\n },\n {\n key: 'maxFunctionParameterCount',\n label: 'parameters',\n defaultLimits: { warning: 6, error: Infinity },\n hint: 'group related parameters into one object or split the function.',\n measure: (fn) => fn.parameterCount,\n },\n {\n key: 'maxFunctionHalsteadVolume',\n label: 'Halstead volume',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function holds too many operators and operands; move cohesive parts into helper functions.',\n measure: (fn) => fn.halstead.volume,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionHalsteadDifficulty',\n label: 'Halstead difficulty',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function reuses the same operands many times; split it so each part handles fewer values.',\n measure: (fn) => fn.halstead.difficulty,\n },\n {\n key: 'maxFunctionHalsteadEffort',\n label: 'Halstead effort',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function is both large and dense; split it into smaller functions with fewer values each.',\n measure: (fn) => fn.halstead.effort,\n },\n {\n key: 'maxFunctionDepDegree',\n label: 'DepDegree',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'too many values flow between the variables of the function; split it so each part works on fewer variables.',\n measure: (fn) => fn.depDegree,\n includesNestedFunctions: true,\n },\n];\n\nexport const fileThresholds: readonly Threshold<CodeMetrics>[] = [\n {\n key: 'maxFileNcss',\n label: 'file NCSS',\n defaultLimits: { warning: 400, error: 1000 },\n hint: 'split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.',\n measure: (file) => file.ncssCount,\n },\n];\n\n/**\n * A duplicated block violates when it holds at least this many duplicated lines, its code lines\n * matched in another copy: comment-only and blank lines do not count, so a copy reads the same\n * length whether or not it is commented, and neither do the lines the copies do not share, those\n * between the matched parts of an edited copy and those a near-miss copy adds or rewrites.\n */\nexport const duplicationThreshold: Threshold<{ duplicatedLineCount: number }> = {\n key: 'minDuplicateLines',\n label: 'duplicated lines',\n defaultLimits: { warning: 15, error: Infinity },\n hint: 'extract the repeated code into one shared function or module and call it from every location.',\n measure: (block) => block.duplicatedLineCount,\n};\n\nexport const thresholds: readonly Threshold[] = [...functionThresholds, ...fileThresholds, duplicationThreshold];\n\n/** The name a report gives a threshold's measured value: its key without the `max`/`min` prefix. */\nexport function metricNameOf(threshold: Threshold): string {\n const name = threshold.key.slice(3);\n return name.charAt(0).toLowerCase() + name.slice(1);\n}\n\n/** One level of the `thresholds` config section; a missing key falls back to the less specific layer. */\nexport interface LevelThresholdsConfig {\n limits: Limits;\n /** Overrides for the files of one language, keyed by language name. */\n languages: Readonly<Record<string, Limits>>;\n}\n\nexport type ThresholdsConfig = Readonly<Partial<Record<Level, LevelThresholdsConfig>>>;\n\n/**\n * The limits of each level applying to files of each language, with precedence command-line\n * options > the config's per-language overrides > the config's limits > built-in defaults.\n */\nexport function resolveLimits(\n cliLimits: LimitsByLevel,\n config: ThresholdsConfig | undefined\n): (language: string) => LimitsByLevel {\n const resolveLevel = (level: Level, language: string): Limits => {\n const levelConfig = config?.[level];\n return {\n ...Object.fromEntries(thresholds.map(({ key, defaultLimits }) => [key, defaultLimits[level]])),\n ...levelConfig?.limits,\n // Object.hasOwn: a language named like an Object.prototype member must not read an inherited value.\n ...(levelConfig && Object.hasOwn(levelConfig.languages, language) ? levelConfig.languages[language] : undefined),\n ...cliLimits[level],\n };\n };\n return (language) => ({ warning: resolveLevel('warning', language), error: resolveLevel('error', language) });\n}\n"],"mappings":"AAGA,MAAaA,EAAS,CAAC,UAAW,OAAO,EA4B5BC,EAA4D,CACvE,CACE,IAAK,iCACL,MAAO,uBACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,8FACN,QAAU,GAAOC,EAAG,oBACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,kCACL,MAAO,wBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,qHACN,QAAU,GAAOA,EAAG,oBACtB,EACA,CACE,IAAK,kBACL,MAAO,OACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,2EACN,QAAU,GAAOA,EAAG,KACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,0BACL,MAAO,gBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,oFACN,QAAU,GAAOA,EAAG,YACtB,EACA,CACE,IAAK,4BACL,MAAO,aACP,cAAe,CAAE,QAAS,EAAG,MAAO,GAAS,EAC7C,KAAM,kEACN,QAAU,GAAOA,EAAG,cACtB,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,iGACN,QAAU,GAAOA,EAAG,SAAS,OAC7B,wBAAyB,EAC3B,EACA,CACE,IAAK,gCACL,MAAO,sBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAOA,EAAG,SAAS,UAC/B,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAOA,EAAG,SAAS,MAC/B,EACA,CACE,IAAK,uBACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,8GACN,QAAU,GAAOA,EAAG,UACpB,wBAAyB,EAC3B,CACF,EAEaC,EAAoD,CAC/D,CACE,IAAK,cACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAK,MAAO,GAAK,EAC3C,KAAM,gKACN,QAAU,GAASC,EAAK,SAC1B,CACF,EAQaC,EAAmE,CAC9E,IAAK,oBACL,MAAO,mBACP,cAAe,CAAE,QAAS,GAAI,MAAO,GAAS,EAC9C,KAAM,gGACN,QAAU,GAAUC,EAAM,mBAC5B,EAEaC,EAAmC,CAAC,GAAGN,EAAoB,GAAGE,EAAgBE,CAAoB,EAG/G,SAAgBG,EAAa,EAA8B,CACzD,IAAMC,EAAOC,EAAU,IAAI,MAAM,CAAC,EAClC,OAAOD,EAAK,OAAO,CAAC,CAAC,CAAC,YAAY,EAAIA,EAAK,MAAM,CAAC,CACpD,CAeA,SAAgBE,EACd,EACA,EACqC,CACrC,IAAMC,GAAgB,EAAc,IAA6B,CAC/D,IAAMC,EAAcC,IAASC,GAC7B,MAAO,CACL,GAAG,OAAO,YAAYR,EAAW,KAAK,CAAE,MAAK,mBAAoB,CAACS,EAAKC,EAAcF,EAAM,CAAC,CAAC,EAC7F,GAAGF,GAAa,OAEhB,GAAIA,GAAe,OAAO,OAAOA,EAAY,UAAWK,CAAQ,EAAIL,EAAY,UAAUK,GAAY,IAAA,GACtG,GAAGC,EAAUJ,EACf,CACF,EACA,MAAQ,KAAc,CAAE,QAASH,EAAa,UAAWM,CAAQ,EAAG,MAAON,EAAa,QAASM,CAAQ,CAAE,EAC7G,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"wasmBinding.cjs","names":["NativeAddonError","require_nativeMetrics","encoder","decoder","createWasmBinding","instance","instantiationFailure","call","instantiate","module","error","status","invoke","exports","stderr","result","measureCodeNative","passString","code","language","includeSyntaxTree","toOptionalU32","minTokens","maxGapTokens","minSimilarityPercent","includeCrossFileData","args","memory","createWasiImports","version","bytes","text","pointer","value","MAX_RANDOM_BYTES","view","getMemory","countPointer","sizePointer","timePointer","offset","length","memoryView","written","index","iovsLength","iovsPointer","fd","writtenPointer","unavailable"],"sources":["../src/wasmBinding.ts"],"sourcesContent":["import { expectedPayloadVersion, NativeAddonError, type NativeBinding } from './nativeMetrics.js';\n\n/** The C ABI exported by native/src/wasm.rs. */\ninterface WasmExports {\n memory: WebAssembly.Memory;\n payload_version(): number;\n alloc(length: number): number;\n result_ptr(): number;\n result_len(): number;\n measure_code(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number,\n includeSyntaxTree: number,\n minTokens: number,\n maxGapTokens: number,\n minSimilarityPercent: number,\n includeCrossFileData: number\n ): number;\n collect_cross_file_data(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number,\n minTokens: number\n ): number;\n collect_function_token_sequences(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number\n ): number;\n}\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/**\n * Wraps the WebAssembly build of the native addon (native/src/wasm.rs) as a NativeBinding. The\n * module is instantiated synchronously on first use and again after a trap (a panic or stack\n * overflow), because a trap leaves the instance's memory and stack pointer in an undefined state.\n * An instantiation failure (e.g., a payload version mismatch) is memoized like the N-API loader's,\n * since instantiating the same module again would fail again.\n */\nexport function createWasmBinding(module: WebAssembly.Module): NativeBinding {\n let instance: { exports: WasmExports; stderr: string[] } | undefined;\n let instantiationFailure: unknown;\n\n const call = (invoke: (exports: WasmExports) => number): string => {\n if (!instance) {\n if (instantiationFailure) {\n throw instantiationFailure;\n }\n try {\n instance = instantiate(module);\n } catch (error) {\n instantiationFailure = error;\n throw error;\n }\n }\n const { exports, stderr } = instance;\n stderr.length = 0;\n let status: number;\n try {\n status = invoke(exports);\n } catch (error) {\n instance = undefined;\n throw new Error(`The code-gauge WebAssembly module crashed: ${stderr.join('').trim() || String(error)}`, {\n cause: error,\n });\n }\n const result = decoder.decode(new Uint8Array(exports.memory.buffer, exports.result_ptr(), exports.result_len()));\n if (status !== 0) {\n throw new Error(result);\n }\n return result;\n };\n\n const measureCodeNative: NativeBinding['measureCodeNative'] = (\n code,\n language,\n includeSyntaxTree,\n minTokens,\n maxGapTokens,\n minSimilarityPercent,\n includeCrossFileData\n ) =>\n call((exports) =>\n exports.measure_code(\n ...passString(exports, code),\n ...passString(exports, language),\n Number(includeSyntaxTree),\n toOptionalU32(minTokens),\n toOptionalU32(maxGapTokens),\n toOptionalU32(minSimilarityPercent),\n Number(includeCrossFileData)\n )\n );\n\n return {\n measureCodeNative,\n // The WebAssembly build has no threads, so the asynchronous form measures in place.\n measureCodeNativeAsync: async (...args) => measureCodeNative(...args),\n collectCrossFileDataNative: (code, language, minTokens) =>\n call((exports) =>\n exports.collect_cross_file_data(\n ...passString(exports, code),\n ...passString(exports, language),\n toOptionalU32(minTokens)\n )\n ),\n collectFunctionTokenSequencesNative: (code, language) =>\n call((exports) =>\n exports.collect_function_token_sequences(...passString(exports, code), ...passString(exports, language))\n ),\n };\n}\n\nfunction instantiate(module: WebAssembly.Module): { exports: WasmExports; stderr: string[] } {\n const stderr: string[] = [];\n let memory: WebAssembly.Memory | undefined;\n const instance = new WebAssembly.Instance(module, {\n wasi_snapshot_preview1: createWasiImports(() => memory as WebAssembly.Memory, stderr),\n });\n const exports = instance.exports as unknown as WasmExports;\n memory = exports.memory;\n const version = exports.payload_version();\n if (version !== expectedPayloadVersion) {\n throw new NativeAddonError(\n `The code-gauge WebAssembly module has payload version ${version}, but ${expectedPayloadVersion} is ` +\n 'expected; reinstall code-gauge, or in a code-gauge repository checkout, rebuild it with `bun run build-wasm`'\n );\n }\n return { exports, stderr };\n}\n\n/** Copies a string into a buffer whose ownership passes to the called export. */\nfunction passString(exports: WasmExports, text: string): [number, number] {\n // TextEncoder replaces lone surrogates with U+FFFD, like toWellFormed() does for the N-API addon.\n const bytes = encoder.encode(text);\n const pointer = exports.alloc(bytes.length);\n new Uint8Array(exports.memory.buffer, pointer, bytes.length).set(bytes);\n return [pointer, bytes.length];\n}\n\n/** native/src/wasm.rs reads a negative value as an absent setting. */\nfunction toOptionalU32(value: number | undefined): number {\n return value ?? -1;\n}\n\nconst WASI_ERRNO_SUCCESS = 0;\nconst WASI_ERRNO_BADF = 8;\n// crypto.getRandomValues() rejects requests larger than this.\nconst MAX_RANDOM_BYTES = 65_536;\n\n/**\n * The WASI preview 1 functions the module imports. The metrics code performs no I/O, so file\n * descriptors are unavailable except for writes, whose stderr output (e.g., a panic message) is\n * kept for the error raised when the module traps.\n */\nfunction createWasiImports(\n getMemory: () => WebAssembly.Memory,\n stderr: string[]\n): Record<string, (...args: never[]) => number> {\n const view = (): DataView => new DataView(getMemory().buffer);\n return {\n environ_get: () => WASI_ERRNO_SUCCESS,\n environ_sizes_get: (countPointer: number, sizePointer: number) => {\n view().setUint32(countPointer, 0, true);\n view().setUint32(sizePointer, 0, true);\n return WASI_ERRNO_SUCCESS;\n },\n clock_time_get: (_clockId: number, _precision: bigint, timePointer: number) => {\n view().setBigUint64(timePointer, BigInt(Date.now()) * 1_000_000n, true);\n return WASI_ERRNO_SUCCESS;\n },\n random_get: (pointer: number, length: number) => {\n for (let offset = 0; offset < length; offset += MAX_RANDOM_BYTES) {\n crypto.getRandomValues(\n new Uint8Array(getMemory().buffer, pointer + offset, Math.min(MAX_RANDOM_BYTES, length - offset))\n );\n }\n return WASI_ERRNO_SUCCESS;\n },\n fd_write: (fd: number, iovsPointer: number, iovsLength: number, writtenPointer: number) => {\n const memoryView = view();\n let written = 0;\n for (let index = 0; index < iovsLength; index++) {\n const pointer = memoryView.getUint32(iovsPointer + index * 8, true);\n const length = memoryView.getUint32(iovsPointer + index * 8 + 4, true);\n if (fd === 2) {\n stderr.push(decoder.decode(new Uint8Array(getMemory().buffer, pointer, length)));\n }\n written += length;\n }\n memoryView.setUint32(writtenPointer, written, true);\n return WASI_ERRNO_SUCCESS;\n },\n fd_close: unavailable,\n fd_fdstat_get: unavailable,\n fd_fdstat_set_flags: unavailable,\n fd_read: unavailable,\n fd_seek: unavailable,\n proc_exit: (code: number) => {\n throw new Error(`The code-gauge WebAssembly module exited with code ${code}`);\n },\n };\n}\n\nfunction unavailable(): number {\n return WASI_ERRNO_BADF;\n}\n"],"mappings":"oDAmCME,EAAU,IAAI,YACdC,EAAU,IAAI,YASpB,SAAgBC,EAAkB,EAA2C,CAC3E,IAAIC,EACAC,EAEEC,EAAQ,GAAqD,CACjE,GAAI,CAACF,EAAU,CACb,GAAIC,EACF,MAAMA,EAER,GAAI,CACF,EAAWE,EAAYC,CAAM,CAC/B,OAASC,EAAO,CAEd,KADA,GAAuBA,EACjBA,CACR,CACF,CACA,GAAM,CAAE,UAAS,UAAWL,EAC5B,EAAO,OAAS,EAChB,IAAIM,EACJ,GAAI,CACF,EAASC,EAAOC,CAAO,CACzB,OAASH,EAAO,CAEd,KADA,GAAW,IAAA,GACD,MAAM,8CAA8CI,EAAO,KAAK,EAAE,CAAC,CAAC,KAAK,GAAK,OAAOJ,CAAK,IAAK,CACvG,MAAOA,CACT,CAAC,CACH,CACA,IAAMK,EAASZ,EAAQ,OAAO,IAAI,WAAWU,EAAQ,OAAO,OAAQA,EAAQ,WAAW,EAAGA,EAAQ,WAAW,CAAC,CAAC,EAC/G,GAAIF,IAAW,EACb,MAAU,MAAMI,CAAM,EAExB,OAAOA,CACT,EAEMC,GACJ,EACA,EACA,EACA,EACA,EACA,EACA,IAEAT,EAAM,GACJM,EAAQ,aACN,GAAGI,EAAWJ,EAASK,CAAI,EAC3B,GAAGD,EAAWJ,EAASM,CAAQ,EAC/B,OAAOC,CAAiB,EACxBC,EAAcC,CAAS,EACvBD,EAAcE,CAAY,EAC1BF,EAAcG,CAAoB,EAClC,OAAOC,CAAoB,CAC7B,CACF,EAEF,MAAO,CACL,oBAEA,uBAAwB,MAAO,GAAGC,IAASV,EAAkB,GAAGU,CAAI,EACpE,4BAA6B,EAAM,EAAU,IAC3CnB,EAAM,GACJM,EAAQ,wBACN,GAAGI,EAAWJ,EAASK,CAAI,EAC3B,GAAGD,EAAWJ,EAASM,CAAQ,EAC/BE,EAAcC,CAAS,CACzB,CACF,EACF,qCAAsC,EAAM,IAC1Cf,EAAM,GACJM,EAAQ,iCAAiC,GAAGI,EAAWJ,EAASK,CAAI,EAAG,GAAGD,EAAWJ,EAASM,CAAQ,CAAC,CACzG,CACJ,CACF,CAEA,SAASX,EAAY,EAAwE,CAC3F,IAAMM,EAAmB,CAAC,EACtBa,EAIEd,EAAU,IAHK,YAAY,SAASJ,EAAQ,CAChD,uBAAwBmB,MAAwBD,EAA8Bb,CAAM,CACtF,CACuB,CAAC,CAAC,QACzB,EAASD,EAAQ,OACjB,IAAMgB,EAAUhB,EAAQ,gBAAgB,EACxC,GAAIgB,IAAA,GACF,MAAM,IAAI7B,EAAAA,iBACR,yDAAyD6B,EAAQ,2HAEnE,EAEF,MAAO,CAAE,UAAS,QAAO,CAC3B,CAGA,SAASZ,EAAW,EAAsB,EAAgC,CAExE,IAAMa,EAAQ5B,EAAQ,OAAO6B,CAAI,EAC3BC,EAAUnB,EAAQ,MAAMiB,EAAM,MAAM,EAE1C,OADA,IAAI,WAAWjB,EAAQ,OAAO,OAAQmB,EAASF,EAAM,MAAM,CAAC,CAAC,IAAIA,CAAK,EAC/D,CAACE,EAASF,EAAM,MAAM,CAC/B,CAGA,SAAST,EAAc,EAAmC,CACxD,OAAOY,GAAS,EAClB,CAEA,MAGMC,EAAmB,MAOzB,SAASN,EACP,EACA,EAC8C,CAC9C,IAAMO,MAAuB,IAAI,SAASC,EAAU,CAAC,CAAC,MAAM,EAC5D,MAAO,CACL,gBAAmB,EACnB,mBAAoB,EAAsB,KACxCD,EAAK,CAAC,CAAC,UAAUE,EAAc,EAAG,EAAI,EACtCF,EAAK,CAAC,CAAC,UAAUG,EAAa,EAAG,EAAI,EAC9B,GAET,gBAAiB,EAAkB,EAAoB,KACrDH,EAAK,CAAC,CAAC,aAAaI,EAAa,OAAO,KAAK,IAAI,CAAC,EAAI,SAAY,EAAI,EAC/D,GAET,YAAa,EAAiB,IAAmB,CAC/C,IAAK,IAAIC,EAAS,EAAGA,EAASC,EAAQ,GAAUP,EAC9C,OAAO,gBACL,IAAI,WAAWE,EAAU,CAAC,CAAC,OAAQJ,EAAUQ,EAAQ,KAAK,IAAIN,EAAkBO,EAASD,CAAM,CAAC,CAClG,EAEF,MAAO,EACT,EACA,UAAW,EAAY,EAAqB,EAAoB,IAA2B,CACzF,IAAME,EAAaP,EAAK,EACpBQ,EAAU,EACd,IAAK,IAAIC,EAAQ,EAAGA,EAAQC,EAAY,IAAS,CAC/C,IAAMb,EAAUU,EAAW,UAAUI,EAAcF,EAAQ,EAAG,EAAI,EAC5DH,EAASC,EAAW,UAAUI,EAAcF,EAAQ,EAAI,EAAG,EAAI,EACjEG,IAAO,GACTjC,EAAO,KAAKX,EAAQ,OAAO,IAAI,WAAWiC,EAAU,CAAC,CAAC,OAAQJ,EAASS,CAAM,CAAC,CAAC,EAEjF,GAAWA,CACb,CAEA,OADAC,EAAW,UAAUM,EAAgBL,EAAS,EAAI,EAC3C,CACT,EACA,SAAUM,EACV,cAAeA,EACf,oBAAqBA,EACrB,QAASA,EACT,QAASA,EACT,UAAY,GAAiB,CAC3B,MAAU,MAAM,sDAAsD/B,GAAM,CAC9E,CACF,CACF,CAEA,SAAS+B,GAAsB,CAC7B,MAAO,EACT"}
1
+ {"version":3,"file":"wasmBinding.cjs","names":["NativeAddonError","require_nativeMetrics","encoder","decoder","createWasmBinding","instance","instantiationFailure","call","instantiate","module","error","status","invoke","exports","stderr","result","measureCodeNative","passString","code","language","includeSyntaxTree","toOptionalU32","minTokens","maxGapTokens","minSimilarityPercent","includeCrossFileData","args","memory","createWasiImports","version","bytes","text","pointer","value","MAX_RANDOM_BYTES","view","getMemory","countPointer","sizePointer","timePointer","offset","length","memoryView","written","index","iovsLength","iovsPointer","fd","writtenPointer","unavailable"],"sources":["../src/wasmBinding.ts"],"sourcesContent":["import { expectedPayloadVersion, NativeAddonError, type NativeBinding } from './nativeMetrics.js';\n\n/** The C ABI exported by native/src/wasm.rs. */\ninterface WasmExports {\n memory: WebAssembly.Memory;\n payload_version(): number;\n alloc(length: number): number;\n result_ptr(): number;\n result_len(): number;\n measure_code(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number,\n includeSyntaxTree: number,\n minTokens: number,\n maxGapTokens: number,\n minSimilarityPercent: number,\n includeCrossFileData: number\n ): number;\n collect_cross_file_data(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number,\n minTokens: number\n ): number;\n collect_function_token_sequences(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number\n ): number;\n}\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/**\n * Wraps the WebAssembly build of the native addon (native/src/wasm.rs) as a NativeBinding. The\n * module is instantiated synchronously on first use and again after a trap (a panic or stack\n * overflow), because a trap leaves the instance's memory and stack pointer in an undefined state.\n * An instantiation failure (e.g., a payload version mismatch) is memoized like the N-API loader's,\n * since instantiating the same module again would fail again.\n */\nexport function createWasmBinding(module: WebAssembly.Module): NativeBinding {\n let instance: { exports: WasmExports; stderr: string[] } | undefined;\n let instantiationFailure: unknown;\n\n const call = (invoke: (exports: WasmExports) => number): string => {\n if (!instance) {\n if (instantiationFailure) {\n throw instantiationFailure;\n }\n try {\n instance = instantiate(module);\n } catch (error) {\n instantiationFailure = error;\n throw error;\n }\n }\n const { exports, stderr } = instance;\n stderr.length = 0;\n let status: number;\n try {\n status = invoke(exports);\n } catch (error) {\n instance = undefined;\n throw new Error(`The code-gauge WebAssembly module crashed: ${stderr.join('').trim() || String(error)}`, {\n cause: error,\n });\n }\n const result = decoder.decode(new Uint8Array(exports.memory.buffer, exports.result_ptr(), exports.result_len()));\n if (status !== 0) {\n throw new Error(result);\n }\n return result;\n };\n\n const measureCodeNative: NativeBinding['measureCodeNative'] = (\n code,\n language,\n includeSyntaxTree,\n minTokens,\n maxGapTokens,\n minSimilarityPercent,\n includeCrossFileData\n ) =>\n call((exports) =>\n exports.measure_code(\n ...passString(exports, code),\n ...passString(exports, language),\n Number(includeSyntaxTree),\n toOptionalU32(minTokens),\n toOptionalU32(maxGapTokens),\n toOptionalU32(minSimilarityPercent),\n Number(includeCrossFileData)\n )\n );\n\n return {\n measureCodeNative,\n // The WebAssembly build has no threads, so the asynchronous form measures in place.\n measureCodeNativeAsync: async (...args) => measureCodeNative(...args),\n collectCrossFileDataNative: (code, language, minTokens) =>\n call((exports) =>\n exports.collect_cross_file_data(\n ...passString(exports, code),\n ...passString(exports, language),\n toOptionalU32(minTokens)\n )\n ),\n collectFunctionTokenSequencesNative: (code, language) =>\n call((exports) =>\n exports.collect_function_token_sequences(...passString(exports, code), ...passString(exports, language))\n ),\n };\n}\n\nfunction instantiate(module: WebAssembly.Module): { exports: WasmExports; stderr: string[] } {\n const stderr: string[] = [];\n let memory: WebAssembly.Memory | undefined;\n const instance = new WebAssembly.Instance(module, {\n wasi_snapshot_preview1: createWasiImports(() => memory as WebAssembly.Memory, stderr),\n });\n const exports = instance.exports as unknown as WasmExports;\n memory = exports.memory;\n const version = exports.payload_version();\n if (version !== expectedPayloadVersion) {\n throw new NativeAddonError(\n `The code-gauge WebAssembly module has payload version ${version}, but ${expectedPayloadVersion} is ` +\n 'expected; reinstall code-gauge, or in a code-gauge repository checkout, rebuild it with `bun run build-wasm`'\n );\n }\n return { exports, stderr };\n}\n\n/** Copies a string into a buffer whose ownership passes to the called export. */\nfunction passString(exports: WasmExports, text: string): [number, number] {\n // TextEncoder replaces lone surrogates with U+FFFD, like toWellFormed() does for the N-API addon.\n const bytes = encoder.encode(text);\n const pointer = exports.alloc(bytes.length);\n new Uint8Array(exports.memory.buffer, pointer, bytes.length).set(bytes);\n return [pointer, bytes.length];\n}\n\n/** native/src/wasm.rs reads a negative value as an absent setting. */\nfunction toOptionalU32(value: number | undefined): number {\n return value ?? -1;\n}\n\nconst WASI_ERRNO_SUCCESS = 0;\nconst WASI_ERRNO_BADF = 8;\n// crypto.getRandomValues() rejects requests larger than this.\nconst MAX_RANDOM_BYTES = 65_536;\n\n/**\n * The WASI preview 1 functions the module imports. The metrics code performs no I/O, so file\n * descriptors are unavailable except for writes, whose stderr output (e.g., a panic message) is\n * kept for the error raised when the module traps.\n */\nfunction createWasiImports(\n getMemory: () => WebAssembly.Memory,\n stderr: string[]\n): Record<string, (...args: never[]) => number> {\n const view = (): DataView => new DataView(getMemory().buffer);\n return {\n environ_get: () => WASI_ERRNO_SUCCESS,\n environ_sizes_get: (countPointer: number, sizePointer: number) => {\n view().setUint32(countPointer, 0, true);\n view().setUint32(sizePointer, 0, true);\n return WASI_ERRNO_SUCCESS;\n },\n clock_time_get: (_clockId: number, _precision: bigint, timePointer: number) => {\n view().setBigUint64(timePointer, BigInt(Date.now()) * 1_000_000n, true);\n return WASI_ERRNO_SUCCESS;\n },\n random_get: (pointer: number, length: number) => {\n for (let offset = 0; offset < length; offset += MAX_RANDOM_BYTES) {\n crypto.getRandomValues(\n new Uint8Array(getMemory().buffer, pointer + offset, Math.min(MAX_RANDOM_BYTES, length - offset))\n );\n }\n return WASI_ERRNO_SUCCESS;\n },\n fd_write: (fd: number, iovsPointer: number, iovsLength: number, writtenPointer: number) => {\n const memoryView = view();\n let written = 0;\n for (let index = 0; index < iovsLength; index++) {\n const pointer = memoryView.getUint32(iovsPointer + index * 8, true);\n const length = memoryView.getUint32(iovsPointer + index * 8 + 4, true);\n if (fd === 2) {\n stderr.push(decoder.decode(new Uint8Array(getMemory().buffer, pointer, length)));\n }\n written += length;\n }\n memoryView.setUint32(writtenPointer, written, true);\n return WASI_ERRNO_SUCCESS;\n },\n fd_close: unavailable,\n fd_fdstat_get: unavailable,\n fd_fdstat_set_flags: unavailable,\n fd_read: unavailable,\n fd_seek: unavailable,\n proc_exit: (code: number) => {\n throw new Error(`The code-gauge WebAssembly module exited with code ${code}`);\n },\n };\n}\n\nfunction unavailable(): number {\n return WASI_ERRNO_BADF;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"wasmBinding.js","names":["encoder","decoder","createWasmBinding","instance","instantiationFailure","call","instantiate","module","error","status","invoke","exports","stderr","result","measureCodeNative","passString","code","language","includeSyntaxTree","toOptionalU32","minTokens","maxGapTokens","minSimilarityPercent","includeCrossFileData","args","memory","createWasiImports","version","NativeAddonError","bytes","text","pointer","value","MAX_RANDOM_BYTES","view","getMemory","countPointer","sizePointer","timePointer","offset","length","memoryView","written","index","iovsLength","iovsPointer","fd","writtenPointer","unavailable"],"sources":["../src/wasmBinding.ts"],"sourcesContent":["import { expectedPayloadVersion, NativeAddonError, type NativeBinding } from './nativeMetrics.js';\n\n/** The C ABI exported by native/src/wasm.rs. */\ninterface WasmExports {\n memory: WebAssembly.Memory;\n payload_version(): number;\n alloc(length: number): number;\n result_ptr(): number;\n result_len(): number;\n measure_code(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number,\n includeSyntaxTree: number,\n minTokens: number,\n maxGapTokens: number,\n minSimilarityPercent: number,\n includeCrossFileData: number\n ): number;\n collect_cross_file_data(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number,\n minTokens: number\n ): number;\n collect_function_token_sequences(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number\n ): number;\n}\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/**\n * Wraps the WebAssembly build of the native addon (native/src/wasm.rs) as a NativeBinding. The\n * module is instantiated synchronously on first use and again after a trap (a panic or stack\n * overflow), because a trap leaves the instance's memory and stack pointer in an undefined state.\n * An instantiation failure (e.g., a payload version mismatch) is memoized like the N-API loader's,\n * since instantiating the same module again would fail again.\n */\nexport function createWasmBinding(module: WebAssembly.Module): NativeBinding {\n let instance: { exports: WasmExports; stderr: string[] } | undefined;\n let instantiationFailure: unknown;\n\n const call = (invoke: (exports: WasmExports) => number): string => {\n if (!instance) {\n if (instantiationFailure) {\n throw instantiationFailure;\n }\n try {\n instance = instantiate(module);\n } catch (error) {\n instantiationFailure = error;\n throw error;\n }\n }\n const { exports, stderr } = instance;\n stderr.length = 0;\n let status: number;\n try {\n status = invoke(exports);\n } catch (error) {\n instance = undefined;\n throw new Error(`The code-gauge WebAssembly module crashed: ${stderr.join('').trim() || String(error)}`, {\n cause: error,\n });\n }\n const result = decoder.decode(new Uint8Array(exports.memory.buffer, exports.result_ptr(), exports.result_len()));\n if (status !== 0) {\n throw new Error(result);\n }\n return result;\n };\n\n const measureCodeNative: NativeBinding['measureCodeNative'] = (\n code,\n language,\n includeSyntaxTree,\n minTokens,\n maxGapTokens,\n minSimilarityPercent,\n includeCrossFileData\n ) =>\n call((exports) =>\n exports.measure_code(\n ...passString(exports, code),\n ...passString(exports, language),\n Number(includeSyntaxTree),\n toOptionalU32(minTokens),\n toOptionalU32(maxGapTokens),\n toOptionalU32(minSimilarityPercent),\n Number(includeCrossFileData)\n )\n );\n\n return {\n measureCodeNative,\n // The WebAssembly build has no threads, so the asynchronous form measures in place.\n measureCodeNativeAsync: async (...args) => measureCodeNative(...args),\n collectCrossFileDataNative: (code, language, minTokens) =>\n call((exports) =>\n exports.collect_cross_file_data(\n ...passString(exports, code),\n ...passString(exports, language),\n toOptionalU32(minTokens)\n )\n ),\n collectFunctionTokenSequencesNative: (code, language) =>\n call((exports) =>\n exports.collect_function_token_sequences(...passString(exports, code), ...passString(exports, language))\n ),\n };\n}\n\nfunction instantiate(module: WebAssembly.Module): { exports: WasmExports; stderr: string[] } {\n const stderr: string[] = [];\n let memory: WebAssembly.Memory | undefined;\n const instance = new WebAssembly.Instance(module, {\n wasi_snapshot_preview1: createWasiImports(() => memory as WebAssembly.Memory, stderr),\n });\n const exports = instance.exports as unknown as WasmExports;\n memory = exports.memory;\n const version = exports.payload_version();\n if (version !== expectedPayloadVersion) {\n throw new NativeAddonError(\n `The code-gauge WebAssembly module has payload version ${version}, but ${expectedPayloadVersion} is ` +\n 'expected; reinstall code-gauge, or in a code-gauge repository checkout, rebuild it with `bun run build-wasm`'\n );\n }\n return { exports, stderr };\n}\n\n/** Copies a string into a buffer whose ownership passes to the called export. */\nfunction passString(exports: WasmExports, text: string): [number, number] {\n // TextEncoder replaces lone surrogates with U+FFFD, like toWellFormed() does for the N-API addon.\n const bytes = encoder.encode(text);\n const pointer = exports.alloc(bytes.length);\n new Uint8Array(exports.memory.buffer, pointer, bytes.length).set(bytes);\n return [pointer, bytes.length];\n}\n\n/** native/src/wasm.rs reads a negative value as an absent setting. */\nfunction toOptionalU32(value: number | undefined): number {\n return value ?? -1;\n}\n\nconst WASI_ERRNO_SUCCESS = 0;\nconst WASI_ERRNO_BADF = 8;\n// crypto.getRandomValues() rejects requests larger than this.\nconst MAX_RANDOM_BYTES = 65_536;\n\n/**\n * The WASI preview 1 functions the module imports. The metrics code performs no I/O, so file\n * descriptors are unavailable except for writes, whose stderr output (e.g., a panic message) is\n * kept for the error raised when the module traps.\n */\nfunction createWasiImports(\n getMemory: () => WebAssembly.Memory,\n stderr: string[]\n): Record<string, (...args: never[]) => number> {\n const view = (): DataView => new DataView(getMemory().buffer);\n return {\n environ_get: () => WASI_ERRNO_SUCCESS,\n environ_sizes_get: (countPointer: number, sizePointer: number) => {\n view().setUint32(countPointer, 0, true);\n view().setUint32(sizePointer, 0, true);\n return WASI_ERRNO_SUCCESS;\n },\n clock_time_get: (_clockId: number, _precision: bigint, timePointer: number) => {\n view().setBigUint64(timePointer, BigInt(Date.now()) * 1_000_000n, true);\n return WASI_ERRNO_SUCCESS;\n },\n random_get: (pointer: number, length: number) => {\n for (let offset = 0; offset < length; offset += MAX_RANDOM_BYTES) {\n crypto.getRandomValues(\n new Uint8Array(getMemory().buffer, pointer + offset, Math.min(MAX_RANDOM_BYTES, length - offset))\n );\n }\n return WASI_ERRNO_SUCCESS;\n },\n fd_write: (fd: number, iovsPointer: number, iovsLength: number, writtenPointer: number) => {\n const memoryView = view();\n let written = 0;\n for (let index = 0; index < iovsLength; index++) {\n const pointer = memoryView.getUint32(iovsPointer + index * 8, true);\n const length = memoryView.getUint32(iovsPointer + index * 8 + 4, true);\n if (fd === 2) {\n stderr.push(decoder.decode(new Uint8Array(getMemory().buffer, pointer, length)));\n }\n written += length;\n }\n memoryView.setUint32(writtenPointer, written, true);\n return WASI_ERRNO_SUCCESS;\n },\n fd_close: unavailable,\n fd_fdstat_get: unavailable,\n fd_fdstat_set_flags: unavailable,\n fd_read: unavailable,\n fd_seek: unavailable,\n proc_exit: (code: number) => {\n throw new Error(`The code-gauge WebAssembly module exited with code ${code}`);\n },\n };\n}\n\nfunction unavailable(): number {\n return WASI_ERRNO_BADF;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"wasmBinding.js","names":["encoder","decoder","createWasmBinding","instance","instantiationFailure","call","instantiate","module","error","status","invoke","exports","stderr","result","measureCodeNative","passString","code","language","includeSyntaxTree","toOptionalU32","minTokens","maxGapTokens","minSimilarityPercent","includeCrossFileData","args","memory","createWasiImports","version","NativeAddonError","bytes","text","pointer","value","MAX_RANDOM_BYTES","view","getMemory","countPointer","sizePointer","timePointer","offset","length","memoryView","written","index","iovsLength","iovsPointer","fd","writtenPointer","unavailable"],"sources":["../src/wasmBinding.ts"],"sourcesContent":["import { expectedPayloadVersion, NativeAddonError, type NativeBinding } from './nativeMetrics.js';\n\n/** The C ABI exported by native/src/wasm.rs. */\ninterface WasmExports {\n memory: WebAssembly.Memory;\n payload_version(): number;\n alloc(length: number): number;\n result_ptr(): number;\n result_len(): number;\n measure_code(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number,\n includeSyntaxTree: number,\n minTokens: number,\n maxGapTokens: number,\n minSimilarityPercent: number,\n includeCrossFileData: number\n ): number;\n collect_cross_file_data(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number,\n minTokens: number\n ): number;\n collect_function_token_sequences(\n codePtr: number,\n codeLength: number,\n languagePtr: number,\n languageLength: number\n ): number;\n}\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/**\n * Wraps the WebAssembly build of the native addon (native/src/wasm.rs) as a NativeBinding. The\n * module is instantiated synchronously on first use and again after a trap (a panic or stack\n * overflow), because a trap leaves the instance's memory and stack pointer in an undefined state.\n * An instantiation failure (e.g., a payload version mismatch) is memoized like the N-API loader's,\n * since instantiating the same module again would fail again.\n */\nexport function createWasmBinding(module: WebAssembly.Module): NativeBinding {\n let instance: { exports: WasmExports; stderr: string[] } | undefined;\n let instantiationFailure: unknown;\n\n const call = (invoke: (exports: WasmExports) => number): string => {\n if (!instance) {\n if (instantiationFailure) {\n throw instantiationFailure;\n }\n try {\n instance = instantiate(module);\n } catch (error) {\n instantiationFailure = error;\n throw error;\n }\n }\n const { exports, stderr } = instance;\n stderr.length = 0;\n let status: number;\n try {\n status = invoke(exports);\n } catch (error) {\n instance = undefined;\n throw new Error(`The code-gauge WebAssembly module crashed: ${stderr.join('').trim() || String(error)}`, {\n cause: error,\n });\n }\n const result = decoder.decode(new Uint8Array(exports.memory.buffer, exports.result_ptr(), exports.result_len()));\n if (status !== 0) {\n throw new Error(result);\n }\n return result;\n };\n\n const measureCodeNative: NativeBinding['measureCodeNative'] = (\n code,\n language,\n includeSyntaxTree,\n minTokens,\n maxGapTokens,\n minSimilarityPercent,\n includeCrossFileData\n ) =>\n call((exports) =>\n exports.measure_code(\n ...passString(exports, code),\n ...passString(exports, language),\n Number(includeSyntaxTree),\n toOptionalU32(minTokens),\n toOptionalU32(maxGapTokens),\n toOptionalU32(minSimilarityPercent),\n Number(includeCrossFileData)\n )\n );\n\n return {\n measureCodeNative,\n // The WebAssembly build has no threads, so the asynchronous form measures in place.\n measureCodeNativeAsync: async (...args) => measureCodeNative(...args),\n collectCrossFileDataNative: (code, language, minTokens) =>\n call((exports) =>\n exports.collect_cross_file_data(\n ...passString(exports, code),\n ...passString(exports, language),\n toOptionalU32(minTokens)\n )\n ),\n collectFunctionTokenSequencesNative: (code, language) =>\n call((exports) =>\n exports.collect_function_token_sequences(...passString(exports, code), ...passString(exports, language))\n ),\n };\n}\n\nfunction instantiate(module: WebAssembly.Module): { exports: WasmExports; stderr: string[] } {\n const stderr: string[] = [];\n let memory: WebAssembly.Memory | undefined;\n const instance = new WebAssembly.Instance(module, {\n wasi_snapshot_preview1: createWasiImports(() => memory as WebAssembly.Memory, stderr),\n });\n const exports = instance.exports as unknown as WasmExports;\n memory = exports.memory;\n const version = exports.payload_version();\n if (version !== expectedPayloadVersion) {\n throw new NativeAddonError(\n `The code-gauge WebAssembly module has payload version ${version}, but ${expectedPayloadVersion} is ` +\n 'expected; reinstall code-gauge, or in a code-gauge repository checkout, rebuild it with `bun run build-wasm`'\n );\n }\n return { exports, stderr };\n}\n\n/** Copies a string into a buffer whose ownership passes to the called export. */\nfunction passString(exports: WasmExports, text: string): [number, number] {\n // TextEncoder replaces lone surrogates with U+FFFD, like toWellFormed() does for the N-API addon.\n const bytes = encoder.encode(text);\n const pointer = exports.alloc(bytes.length);\n new Uint8Array(exports.memory.buffer, pointer, bytes.length).set(bytes);\n return [pointer, bytes.length];\n}\n\n/** native/src/wasm.rs reads a negative value as an absent setting. */\nfunction toOptionalU32(value: number | undefined): number {\n return value ?? -1;\n}\n\nconst WASI_ERRNO_SUCCESS = 0;\nconst WASI_ERRNO_BADF = 8;\n// crypto.getRandomValues() rejects requests larger than this.\nconst MAX_RANDOM_BYTES = 65_536;\n\n/**\n * The WASI preview 1 functions the module imports. The metrics code performs no I/O, so file\n * descriptors are unavailable except for writes, whose stderr output (e.g., a panic message) is\n * kept for the error raised when the module traps.\n */\nfunction createWasiImports(\n getMemory: () => WebAssembly.Memory,\n stderr: string[]\n): Record<string, (...args: never[]) => number> {\n const view = (): DataView => new DataView(getMemory().buffer);\n return {\n environ_get: () => WASI_ERRNO_SUCCESS,\n environ_sizes_get: (countPointer: number, sizePointer: number) => {\n view().setUint32(countPointer, 0, true);\n view().setUint32(sizePointer, 0, true);\n return WASI_ERRNO_SUCCESS;\n },\n clock_time_get: (_clockId: number, _precision: bigint, timePointer: number) => {\n view().setBigUint64(timePointer, BigInt(Date.now()) * 1_000_000n, true);\n return WASI_ERRNO_SUCCESS;\n },\n random_get: (pointer: number, length: number) => {\n for (let offset = 0; offset < length; offset += MAX_RANDOM_BYTES) {\n crypto.getRandomValues(\n new Uint8Array(getMemory().buffer, pointer + offset, Math.min(MAX_RANDOM_BYTES, length - offset))\n );\n }\n return WASI_ERRNO_SUCCESS;\n },\n fd_write: (fd: number, iovsPointer: number, iovsLength: number, writtenPointer: number) => {\n const memoryView = view();\n let written = 0;\n for (let index = 0; index < iovsLength; index++) {\n const pointer = memoryView.getUint32(iovsPointer + index * 8, true);\n const length = memoryView.getUint32(iovsPointer + index * 8 + 4, true);\n if (fd === 2) {\n stderr.push(decoder.decode(new Uint8Array(getMemory().buffer, pointer, length)));\n }\n written += length;\n }\n memoryView.setUint32(writtenPointer, written, true);\n return WASI_ERRNO_SUCCESS;\n },\n fd_close: unavailable,\n fd_fdstat_get: unavailable,\n fd_fdstat_set_flags: unavailable,\n fd_read: unavailable,\n fd_seek: unavailable,\n proc_exit: (code: number) => {\n throw new Error(`The code-gauge WebAssembly module exited with code ${code}`);\n },\n };\n}\n\nfunction unavailable(): number {\n return WASI_ERRNO_BADF;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"worker.cjs","names":["setNativeBinding","createWasmBinding","wasmModule","require_runtime","require_crossFileDuplication","require_languages","require_nativeMetrics","require_metrics","require_wasmBinding","native_code_gauge_wasm"],"sources":["../src/worker.ts"],"sourcesContent":["// The entry point for Cloudflare Workers (the `workerd` export condition), which cannot load N-API\n// addons: the same API runs on the WebAssembly build of the native addon.\nimport wasmModule from '../native/code-gauge.wasm';\nimport { setNativeBinding } from './nativeMetrics.js';\nimport { createWasmBinding } from './wasmBinding.js';\n\nsetNativeBinding(createWasmBinding(wasmModule));\n\nexport * from './index.js';\n"],"mappings":"6XAMAA,EAAAA,iBAAiBC,EAAAA,kBAAkBC,EAAAA,OAAU,CAAC"}
1
+ {"version":3,"file":"worker.cjs","names":["setNativeBinding","createWasmBinding","wasmModule","require_runtime","require_crossFileDuplication","require_languages","require_nativeMetrics","require_metrics","require_wasmBinding","native_code_gauge_wasm"],"sources":["../src/worker.ts"],"sourcesContent":["// The entry point for Cloudflare Workers (the `workerd` export condition), which cannot load N-API\n// addons: the same API runs on the WebAssembly build of the native addon.\nimport wasmModule from '../native/code-gauge.wasm';\nimport { setNativeBinding } from './nativeMetrics.js';\nimport { createWasmBinding } from './wasmBinding.js';\n\nsetNativeBinding(createWasmBinding(wasmModule));\n\nexport * from './index.js';\n"],"mappings":"A,6XAMAA,EAAAA,iBAAiBC,EAAAA,kBAAkBC,EAAAA,OAAU,CAAC,E"}
@@ -1 +1 @@
1
- {"version":3,"file":"worker.js","names":["wasmModule","setNativeBinding","createWasmBinding","TreeMeasurer","collectCrossFileDuplicationFileData","collectDuplicationCandidates","collectFunctionTokenSequences","defaultLanguages","defaultMeasurer","detectLanguage","measureCode","measureCrossFileDuplication","supportedLanguages"],"sources":["../src/worker.ts"],"sourcesContent":["// The entry point for Cloudflare Workers (the `workerd` export condition), which cannot load N-API\n// addons: the same API runs on the WebAssembly build of the native addon.\nimport wasmModule from '../native/code-gauge.wasm';\nimport { setNativeBinding } from './nativeMetrics.js';\nimport { createWasmBinding } from './wasmBinding.js';\n\nsetNativeBinding(createWasmBinding(wasmModule));\n\nexport * from './index.js';\n"],"mappings":"6gBAMAC,EAAiBC,EAAkBF,CAAU,CAAC"}
1
+ {"version":3,"file":"worker.js","names":["wasmModule","setNativeBinding","createWasmBinding","TreeMeasurer","collectCrossFileDuplicationFileData","collectDuplicationCandidates","collectFunctionTokenSequences","defaultLanguages","defaultMeasurer","detectLanguage","measureCode","measureCrossFileDuplication","supportedLanguages"],"sources":["../src/worker.ts"],"sourcesContent":["// The entry point for Cloudflare Workers (the `workerd` export condition), which cannot load N-API\n// addons: the same API runs on the WebAssembly build of the native addon.\nimport wasmModule from '../native/code-gauge.wasm';\nimport { setNativeBinding } from './nativeMetrics.js';\nimport { createWasmBinding } from './wasmBinding.js';\n\nsetNativeBinding(createWasmBinding(wasmModule));\n\nexport * from './index.js';\n"],"mappings":"A,6gBAMAC,EAAiBC,EAAkBF,CAAU,CAAC,E"}
package/native/Cargo.lock CHANGED
@@ -444,9 +444,9 @@ checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
444
444
 
445
445
  [[package]]
446
446
  name = "willbooster-tree-sitter"
447
- version = "2.0.1"
447
+ version = "17.1.4"
448
448
  source = "registry+https://github.com/rust-lang/crates.io-index"
449
- checksum = "f0e4c1b0eb97a06595a2ef7923b2b10a28a34bad807c4f2f303d21eb4b54ed96"
449
+ checksum = "0fb6eea725ea4b25a3fe494f8fcad5de289a744d5fb855d51c8a4ec5f8e15f64"
450
450
  dependencies = [
451
451
  "cc",
452
452
  "regex",
@@ -457,9 +457,9 @@ dependencies = [
457
457
 
458
458
  [[package]]
459
459
  name = "willbooster-tree-sitter-c"
460
- version = "5.0.0"
460
+ version = "34.0.0"
461
461
  source = "registry+https://github.com/rust-lang/crates.io-index"
462
- checksum = "0a6c8c48ea72f8d1783e4cdac7d50a500bd14b46ae578ef96fff99d2653e0152"
462
+ checksum = "d932710638a50195e963519c34d79474829a9082d2a7ac0b9ae27f35dd8ad0e3"
463
463
  dependencies = [
464
464
  "cc",
465
465
  "tree-sitter-language",
@@ -467,9 +467,9 @@ dependencies = [
467
467
 
468
468
  [[package]]
469
469
  name = "willbooster-tree-sitter-c-sharp"
470
- version = "4.0.0"
470
+ version = "19.0.0"
471
471
  source = "registry+https://github.com/rust-lang/crates.io-index"
472
- checksum = "b9539ec95844d3d65db6d77680a67b95e0d4bdea7531c670cd94d1d1fc9905c3"
472
+ checksum = "31a320100f72ea159eefb1d25e3145d1efc0e514916e6d35e92d7121ab9c4a22"
473
473
  dependencies = [
474
474
  "cc",
475
475
  "tree-sitter-language",
@@ -477,9 +477,9 @@ dependencies = [
477
477
 
478
478
  [[package]]
479
479
  name = "willbooster-tree-sitter-cpp"
480
- version = "3.0.0"
480
+ version = "28.0.0"
481
481
  source = "registry+https://github.com/rust-lang/crates.io-index"
482
- checksum = "d74786e7d0c88c6d0b410629a89265653d00baf34aa9c1df617fdc630911ca7b"
482
+ checksum = "1ee6133692a500d4229356dfb80cbde46e9409a227f82c1aeeb0e51519f846e0"
483
483
  dependencies = [
484
484
  "cc",
485
485
  "tree-sitter-language",
@@ -487,9 +487,9 @@ dependencies = [
487
487
 
488
488
  [[package]]
489
489
  name = "willbooster-tree-sitter-go"
490
- version = "2.0.0"
490
+ version = "9.0.0"
491
491
  source = "registry+https://github.com/rust-lang/crates.io-index"
492
- checksum = "41c5ac97ff2ddd2309013298b2d54c064a7201607cd7d34d052a6dbfd3fb7d89"
492
+ checksum = "ea017d5f05a6ddf92ac3081f8e5a0c84ed2806b0937921b78ce4f67aba4cc7a0"
493
493
  dependencies = [
494
494
  "cc",
495
495
  "tree-sitter-language",
@@ -497,9 +497,9 @@ dependencies = [
497
497
 
498
498
  [[package]]
499
499
  name = "willbooster-tree-sitter-java"
500
- version = "2.0.0"
500
+ version = "32.0.0"
501
501
  source = "registry+https://github.com/rust-lang/crates.io-index"
502
- checksum = "36f6d73008df52a2f2062d6f636b900a35bffcff4df4f9ce8e79ee2670a50e60"
502
+ checksum = "206cc0712d457c8f58df153b026aacaa3b99e10fa7dd427d8469e1964160dfa3"
503
503
  dependencies = [
504
504
  "cc",
505
505
  "tree-sitter-language",
@@ -507,9 +507,9 @@ dependencies = [
507
507
 
508
508
  [[package]]
509
509
  name = "willbooster-tree-sitter-javascript"
510
- version = "5.0.0"
510
+ version = "21.1.0"
511
511
  source = "registry+https://github.com/rust-lang/crates.io-index"
512
- checksum = "36939d4634a283903d76b874eb04c39e84ff3f1adb904c50251b2ce2e85c0d67"
512
+ checksum = "77d5de1c9ca2b72301091674c5f958931d38ecce02e4676b0b5c1130ebb0b8a5"
513
513
  dependencies = [
514
514
  "cc",
515
515
  "tree-sitter-language",
@@ -517,9 +517,9 @@ dependencies = [
517
517
 
518
518
  [[package]]
519
519
  name = "willbooster-tree-sitter-kotlin"
520
- version = "5.0.0"
520
+ version = "19.0.0"
521
521
  source = "registry+https://github.com/rust-lang/crates.io-index"
522
- checksum = "0cfff8b6eb5b11f12ae1efb051e337e1743e47a29a30019c8af1ed1a4ef12d5e"
522
+ checksum = "ff85f6672aa0bd1acc4c51e67d8980cf3ec7c3b9b48f23cb73903e25828ae88c"
523
523
  dependencies = [
524
524
  "cc",
525
525
  "tree-sitter-language",
@@ -527,9 +527,9 @@ dependencies = [
527
527
 
528
528
  [[package]]
529
529
  name = "willbooster-tree-sitter-python"
530
- version = "2.0.0"
530
+ version = "16.0.0"
531
531
  source = "registry+https://github.com/rust-lang/crates.io-index"
532
- checksum = "b1a0fd7773e48257357d4e89bceba458798f1ecf8f1348a475148c3989562167"
532
+ checksum = "3f13a7a406f5f43567214a62258b0b9cd7898b116c73bfdc6596cc597b3034d9"
533
533
  dependencies = [
534
534
  "cc",
535
535
  "tree-sitter-language",
@@ -537,9 +537,9 @@ dependencies = [
537
537
 
538
538
  [[package]]
539
539
  name = "willbooster-tree-sitter-ruby"
540
- version = "2.0.0"
540
+ version = "15.0.0"
541
541
  source = "registry+https://github.com/rust-lang/crates.io-index"
542
- checksum = "53942eea1345d917886d024260232a4d073ac2a8c984cc021e28b077554d8739"
542
+ checksum = "fb771904d747500887426038c7a08b6c791fe1977cd55bf309b38361dcafa356"
543
543
  dependencies = [
544
544
  "cc",
545
545
  "tree-sitter-language",
@@ -547,9 +547,9 @@ dependencies = [
547
547
 
548
548
  [[package]]
549
549
  name = "willbooster-tree-sitter-rust"
550
- version = "3.0.0"
550
+ version = "17.0.0"
551
551
  source = "registry+https://github.com/rust-lang/crates.io-index"
552
- checksum = "136d0e910a15bc03afe5c1aedc9b9409f40aad12bdd48b6b8074ca89af29889b"
552
+ checksum = "989b05ac10ec0f372dcbfbd35a336b4abb65556e56a4f4447929aea208ca6417"
553
553
  dependencies = [
554
554
  "cc",
555
555
  "tree-sitter-language",
@@ -557,9 +557,9 @@ dependencies = [
557
557
 
558
558
  [[package]]
559
559
  name = "willbooster-tree-sitter-typescript"
560
- version = "8.0.0"
560
+ version = "26.0.1"
561
561
  source = "registry+https://github.com/rust-lang/crates.io-index"
562
- checksum = "74c312e563df0bcb5dcc8fb6cbebd47d489423d6860aae75b4caa525b014cad8"
562
+ checksum = "dbef52d0170029359e86ccb526480ca67804e30f09c309c706171e9928299fcc"
563
563
  dependencies = [
564
564
  "cc",
565
565
  "tree-sitter-language",