code-gauge 4.3.1 → 4.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.
- package/README.md +4 -2
- package/dist/metrics.cjs +1 -1
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.js +1 -1
- package/dist/metrics.js.map +1 -1
- package/dist/nativeMetrics.cjs +1 -1
- package/dist/nativeMetrics.cjs.map +1 -1
- package/dist/nativeMetrics.js +1 -1
- package/dist/nativeMetrics.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/native/src/complexity.rs +23 -7
- package/native/src/languages.rs +17 -0
- package/native/src/lib.rs +1 -1
- package/native/src/measure.rs +83 -2
- package/native/src/types.rs +1 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -174,8 +174,10 @@ The `duplication` section tunes how clones are detected:
|
|
|
174
174
|
positions too
|
|
175
175
|
- Per-function and file-level nesting depth
|
|
176
176
|
- Per-function cyclomatic complexity (McCabe, own body only), counted as NIST SP 500-235 defines it:
|
|
177
|
-
every decision and short-circuit operator adds one, one per case-labelled statement
|
|
178
|
-
|
|
177
|
+
every decision and short-circuit operator adds one, one per case-labelled statement, plus the
|
|
178
|
+
file total over McCabe's components (every function, every initializer block, decisions outside
|
|
179
|
+
functions, and the module body of a file that runs top-level code); an API-only metric that
|
|
180
|
+
ranking and the regression gate ignore
|
|
179
181
|
- Per-function parameter counts and locations (name, node type, line span)
|
|
180
182
|
- Within-file duplication: copy-pasted blocks matched on normalized tokens (identifiers anonymized
|
|
181
183
|
consistently, literals by kind, and literal-dense data tables excluded unless their values also
|
package/dist/metrics.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("./languages.cjs"),t=require("./nativeMetrics.cjs");var n=class{registry=e.createLanguageRegistry();getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,n){let i=this.resolveLanguage(n.language),a=n.includeSyntaxTree??!1;return r(t.measureCodeNative(e,i.name,a,n.duplication),a)}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.resolveLanguage(n.language),i=t.collectCrossFileDataNative(e,r.name,n.duplication?.minTokens);return{candidates:i.candidates,tokens:i.tokens,containerStatements:i.containerStatements,codeLineNumbers:new Set(i.codeLineNumbers)}}collectFunctionTokenSequences(e,n){let r=this.resolveLanguage(n.language);return t.collectFunctionTokenSequencesNative(e,r.name)}resolveLanguage(e){let t=this.registry.get(e);if(!t)throw Error(`Unsupported language: ${e}`);return t}};function r(e,t){return{language:e.language,bytes:e.bytes,lines:e.lines,functions:e.functions.map(e=>({name:e.name,nodeType:e.nodeType,startLine:e.startLine,startColumn:e.startColumn,endLine:e.endLine,endColumn:e.endColumn,cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,nestingDepth:e.nestingDepth,ncss:e.ncss,parameterCount:e.parameterCount,halstead:i(e.halsteadCounts),depDegree:e.depDegree})),cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:i(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function i(e){let{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i}=e,a=t+n,o=r+i,s=a===0?0:o*Math.log2(a);return{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i,vocabulary:a,length:o,volume:s,effort:(n===0?0:t/2*(i/n))*s}}const a=new n;function o(e,t){return a.measure(e,t)}function s(e,t){return a.collectDuplicationCandidates(e,t)}function c(e,t){return a.collectFunctionTokenSequences(e,t)}function l(e,t){return a.collectCrossFileDuplicationFileData(e,t)}exports.TreeMeasurer=n,exports.collectCrossFileDuplicationFileData=l,exports.collectDuplicationCandidates=s,exports.collectFunctionTokenSequences=c,exports.defaultMeasurer=a,exports.measureCode=o;
|
|
1
|
+
"use strict";const e=require("./languages.cjs"),t=require("./nativeMetrics.cjs");var n=class{registry=e.createLanguageRegistry();getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,n){let i=this.resolveLanguage(n.language),a=n.includeSyntaxTree??!1;return r(t.measureCodeNative(e,i.name,a,n.duplication),a)}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.resolveLanguage(n.language),i=t.collectCrossFileDataNative(e,r.name,n.duplication?.minTokens);return{candidates:i.candidates,tokens:i.tokens,containerStatements:i.containerStatements,codeLineNumbers:new Set(i.codeLineNumbers)}}collectFunctionTokenSequences(e,n){let r=this.resolveLanguage(n.language);return t.collectFunctionTokenSequencesNative(e,r.name)}resolveLanguage(e){let t=this.registry.get(e);if(!t)throw Error(`Unsupported language: ${e}`);return t}};function r(e,t){return{language:e.language,bytes:e.bytes,lines:e.lines,functions:e.functions.map(e=>({name:e.name,nodeType:e.nodeType,startLine:e.startLine,startColumn:e.startColumn,endLine:e.endLine,endColumn:e.endColumn,cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,nestingDepth:e.nestingDepth,ncss:e.ncss,parameterCount:e.parameterCount,halstead:i(e.halsteadCounts),depDegree:e.depDegree})),cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:i(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function i(e){let{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i}=e,a=t+n,o=r+i,s=a===0?0:o*Math.log2(a);return{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i,vocabulary:a,length:o,volume:s,effort:(n===0?0:t/2*(i/n))*s}}const a=new n;function o(e,t){return a.measure(e,t)}function s(e,t){return a.collectDuplicationCandidates(e,t)}function c(e,t){return a.collectFunctionTokenSequences(e,t)}function l(e,t){return a.collectCrossFileDuplicationFileData(e,t)}exports.TreeMeasurer=n,exports.collectCrossFileDuplicationFileData=l,exports.collectDuplicationCandidates=s,exports.collectFunctionTokenSequences=c,exports.defaultMeasurer=a,exports.measureCode=o;
|
|
2
2
|
//# sourceMappingURL=metrics.cjs.map
|
package/dist/metrics.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.cjs","names":["createLanguageRegistry","measureCodeNative","collectCrossFileDataNative","collectFunctionTokenSequencesNative"],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n const payload = collectCrossFileDataNative(code, language.name, options.duplication?.minTokens);\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim, so the regression gate can re-match renamed or\n * moved functions across two revisions by token-LCS similarity.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n endColumn: fn.endColumn,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"iFAgBA,IAAa,EAAb,KAA0B,CACxB,SAA4BA,EAAAA,uBAAuB,EAEnD,uBAAwC,CACtC,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC,CAAC,CAAC,IAAK,GAAa,EAAS,IAAI,CAAC,CAAC,CAClF,CAEA,QAAQ,EAAc,EAAsC,CAC1D,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAoB,EAAQ,mBAAqB,GAEvD,OAAO,EADSC,EAAAA,kBAAkB,EAAM,EAAS,KAAM,EAAmB,EAAQ,WAC/C,EAAG,CAAiB,CACzD,CAGA,6BAA6B,EAAc,EAAwD,CACjG,OAAO,KAAK,oCAAoC,EAAM,CAAO,CAAC,CAAC,UACjE,CAMA,oCAAoC,EAAc,EAAuD,CACvG,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAUC,EAAAA,2BAA2B,EAAM,EAAS,KAAM,EAAQ,aAAa,SAAS,EAC9F,MAAO,CACL,WAAY,EAAQ,WACpB,OAAQ,EAAQ,OAChB,oBAAqB,EAAQ,oBAE7B,gBAAiB,IAAI,IAAI,EAAQ,eAAe,CAClD,CACF,CAQA,8BAA8B,EAAc,EAAuC,CACjF,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EACtD,OAAOC,EAAAA,oCAAoC,EAAM,EAAS,IAAI,CAChE,CAEA,gBAAwB,EAAwC,CAC9D,IAAM,EAAW,KAAK,SAAS,IAAI,CAAI,EACvC,GAAI,CAAC,EACH,MAAU,MAAM,yBAAyB,GAAM,EAEjD,OAAO,CACT,CACF,EAOA,SAAS,EAAsB,EAA+B,EAAyC,CACrG,MAAO,CACL,SAAU,EAAQ,SAClB,MAAO,EAAQ,MACf,MAAO,EAAQ,MACf,UAAW,EAAQ,UAAU,IAAK,IAAQ,CACxC,KAAM,EAAG,KACT,SAAU,EAAG,SACb,UAAW,EAAG,UACd,YAAa,EAAG,YAChB,QAAS,EAAG,QACZ,UAAW,EAAG,UACd,qBAAsB,EAAG,qBACzB,oBAAqB,EAAG,oBACxB,aAAc,EAAG,aACjB,KAAM,EAAG,KACT,eAAgB,EAAG,eACnB,SAAU,EAAsB,EAAG,cAAc,EACjD,UAAW,EAAG,SAChB,EAAE,EACF,oBAAqB,EAAQ,oBAC7B,uBAAwB,EAAQ,uBAChC,aAAc,EAAQ,aACtB,UAAW,EAAQ,UACnB,YAAa,EAAQ,YACrB,SAAU,EAAsB,EAAQ,cAAc,EACtD,WAAY,EAAoB,EAAQ,WAAa,IAAA,EACvD,CACF,CAEA,SAAS,EAAsB,EAA+C,CAC5E,GAAM,CAAE,oBAAmB,mBAAkB,iBAAgB,iBAAkB,EACzE,EAAa,EAAoB,EACjC,EAAS,EAAiB,EAC1B,EAAS,IAAe,EAAI,EAAI,EAAS,KAAK,KAAK,CAAU,EAInE,MAAO,CACL,oBACA,mBACA,iBACA,gBACA,aACA,SACA,SACA,QAXiB,IAAqB,EAAI,EAAK,EAAoB,GAAM,EAAgB,IAC/D,CAW5B,CACF,CAEA,MAAa,EAAkB,IAAI,EAEnC,SAAgB,EAAY,EAAc,EAAsC,CAC9E,OAAO,EAAgB,QAAQ,EAAM,CAAO,CAC9C,CAGA,SAAgB,EAA6B,EAAc,EAAwD,CACjH,OAAO,EAAgB,6BAA6B,EAAM,CAAO,CACnE,CAGA,SAAgB,EAA8B,EAAc,EAAuC,CACjG,OAAO,EAAgB,8BAA8B,EAAM,CAAO,CACpE,CAGA,SAAgB,EACd,EACA,EAC8B,CAC9B,OAAO,EAAgB,oCAAoC,EAAM,CAAO,CAC1E"}
|
|
1
|
+
{"version":3,"file":"metrics.cjs","names":["createLanguageRegistry","measureCodeNative","collectCrossFileDataNative","collectFunctionTokenSequencesNative"],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n const payload = collectCrossFileDataNative(code, language.name, options.duplication?.minTokens);\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim, so the regression gate can re-match renamed or\n * moved functions across two revisions by token-LCS similarity.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n endColumn: fn.endColumn,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cyclomaticComplexity: payload.cyclomaticComplexity,\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"iFAgBA,IAAa,EAAb,KAA0B,CACxB,SAA4BA,EAAAA,uBAAuB,EAEnD,uBAAwC,CACtC,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC,CAAC,CAAC,IAAK,GAAa,EAAS,IAAI,CAAC,CAAC,CAClF,CAEA,QAAQ,EAAc,EAAsC,CAC1D,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAoB,EAAQ,mBAAqB,GAEvD,OAAO,EADSC,EAAAA,kBAAkB,EAAM,EAAS,KAAM,EAAmB,EAAQ,WAC/C,EAAG,CAAiB,CACzD,CAGA,6BAA6B,EAAc,EAAwD,CACjG,OAAO,KAAK,oCAAoC,EAAM,CAAO,CAAC,CAAC,UACjE,CAMA,oCAAoC,EAAc,EAAuD,CACvG,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAUC,EAAAA,2BAA2B,EAAM,EAAS,KAAM,EAAQ,aAAa,SAAS,EAC9F,MAAO,CACL,WAAY,EAAQ,WACpB,OAAQ,EAAQ,OAChB,oBAAqB,EAAQ,oBAE7B,gBAAiB,IAAI,IAAI,EAAQ,eAAe,CAClD,CACF,CAQA,8BAA8B,EAAc,EAAuC,CACjF,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EACtD,OAAOC,EAAAA,oCAAoC,EAAM,EAAS,IAAI,CAChE,CAEA,gBAAwB,EAAwC,CAC9D,IAAM,EAAW,KAAK,SAAS,IAAI,CAAI,EACvC,GAAI,CAAC,EACH,MAAU,MAAM,yBAAyB,GAAM,EAEjD,OAAO,CACT,CACF,EAOA,SAAS,EAAsB,EAA+B,EAAyC,CACrG,MAAO,CACL,SAAU,EAAQ,SAClB,MAAO,EAAQ,MACf,MAAO,EAAQ,MACf,UAAW,EAAQ,UAAU,IAAK,IAAQ,CACxC,KAAM,EAAG,KACT,SAAU,EAAG,SACb,UAAW,EAAG,UACd,YAAa,EAAG,YAChB,QAAS,EAAG,QACZ,UAAW,EAAG,UACd,qBAAsB,EAAG,qBACzB,oBAAqB,EAAG,oBACxB,aAAc,EAAG,aACjB,KAAM,EAAG,KACT,eAAgB,EAAG,eACnB,SAAU,EAAsB,EAAG,cAAc,EACjD,UAAW,EAAG,SAChB,EAAE,EACF,qBAAsB,EAAQ,qBAC9B,oBAAqB,EAAQ,oBAC7B,uBAAwB,EAAQ,uBAChC,aAAc,EAAQ,aACtB,UAAW,EAAQ,UACnB,YAAa,EAAQ,YACrB,SAAU,EAAsB,EAAQ,cAAc,EACtD,WAAY,EAAoB,EAAQ,WAAa,IAAA,EACvD,CACF,CAEA,SAAS,EAAsB,EAA+C,CAC5E,GAAM,CAAE,oBAAmB,mBAAkB,iBAAgB,iBAAkB,EACzE,EAAa,EAAoB,EACjC,EAAS,EAAiB,EAC1B,EAAS,IAAe,EAAI,EAAI,EAAS,KAAK,KAAK,CAAU,EAInE,MAAO,CACL,oBACA,mBACA,iBACA,gBACA,aACA,SACA,SACA,QAXiB,IAAqB,EAAI,EAAK,EAAoB,GAAM,EAAgB,IAC/D,CAW5B,CACF,CAEA,MAAa,EAAkB,IAAI,EAEnC,SAAgB,EAAY,EAAc,EAAsC,CAC9E,OAAO,EAAgB,QAAQ,EAAM,CAAO,CAC9C,CAGA,SAAgB,EAA6B,EAAc,EAAwD,CACjH,OAAO,EAAgB,6BAA6B,EAAM,CAAO,CACnE,CAGA,SAAgB,EAA8B,EAAc,EAAuC,CACjG,OAAO,EAAgB,8BAA8B,EAAM,CAAO,CACpE,CAGA,SAAgB,EACd,EACA,EAC8B,CAC9B,OAAO,EAAgB,oCAAoC,EAAM,CAAO,CAC1E"}
|
package/dist/metrics.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createLanguageRegistry as e}from"./languages.js";import{collectCrossFileDataNative as t,collectFunctionTokenSequencesNative as n,measureCodeNative as r}from"./nativeMetrics.js";var i=class{registry=e();getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,t){let n=this.resolveLanguage(t.language),i=t.includeSyntaxTree??!1;return a(r(e,n.name,i,t.duplication),i)}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.resolveLanguage(n.language),i=t(e,r.name,n.duplication?.minTokens);return{candidates:i.candidates,tokens:i.tokens,containerStatements:i.containerStatements,codeLineNumbers:new Set(i.codeLineNumbers)}}collectFunctionTokenSequences(e,t){let r=this.resolveLanguage(t.language);return n(e,r.name)}resolveLanguage(e){let t=this.registry.get(e);if(!t)throw Error(`Unsupported language: ${e}`);return t}};function a(e,t){return{language:e.language,bytes:e.bytes,lines:e.lines,functions:e.functions.map(e=>({name:e.name,nodeType:e.nodeType,startLine:e.startLine,startColumn:e.startColumn,endLine:e.endLine,endColumn:e.endColumn,cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,nestingDepth:e.nestingDepth,ncss:e.ncss,parameterCount:e.parameterCount,halstead:o(e.halsteadCounts),depDegree:e.depDegree})),cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:o(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function o(e){let{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i}=e,a=t+n,o=r+i,s=a===0?0:o*Math.log2(a);return{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i,vocabulary:a,length:o,volume:s,effort:(n===0?0:t/2*(i/n))*s}}const s=new i;function c(e,t){return s.measure(e,t)}function l(e,t){return s.collectDuplicationCandidates(e,t)}function u(e,t){return s.collectFunctionTokenSequences(e,t)}function d(e,t){return s.collectCrossFileDuplicationFileData(e,t)}export{i as TreeMeasurer,d as collectCrossFileDuplicationFileData,l as collectDuplicationCandidates,u as collectFunctionTokenSequences,s as defaultMeasurer,c as measureCode};
|
|
1
|
+
import{createLanguageRegistry as e}from"./languages.js";import{collectCrossFileDataNative as t,collectFunctionTokenSequencesNative as n,measureCodeNative as r}from"./nativeMetrics.js";var i=class{registry=e();getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,t){let n=this.resolveLanguage(t.language),i=t.includeSyntaxTree??!1;return a(r(e,n.name,i,t.duplication),i)}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.resolveLanguage(n.language),i=t(e,r.name,n.duplication?.minTokens);return{candidates:i.candidates,tokens:i.tokens,containerStatements:i.containerStatements,codeLineNumbers:new Set(i.codeLineNumbers)}}collectFunctionTokenSequences(e,t){let r=this.resolveLanguage(t.language);return n(e,r.name)}resolveLanguage(e){let t=this.registry.get(e);if(!t)throw Error(`Unsupported language: ${e}`);return t}};function a(e,t){return{language:e.language,bytes:e.bytes,lines:e.lines,functions:e.functions.map(e=>({name:e.name,nodeType:e.nodeType,startLine:e.startLine,startColumn:e.startColumn,endLine:e.endLine,endColumn:e.endColumn,cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,nestingDepth:e.nestingDepth,ncss:e.ncss,parameterCount:e.parameterCount,halstead:o(e.halsteadCounts),depDegree:e.depDegree})),cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:o(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function o(e){let{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i}=e,a=t+n,o=r+i,s=a===0?0:o*Math.log2(a);return{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i,vocabulary:a,length:o,volume:s,effort:(n===0?0:t/2*(i/n))*s}}const s=new i;function c(e,t){return s.measure(e,t)}function l(e,t){return s.collectDuplicationCandidates(e,t)}function u(e,t){return s.collectFunctionTokenSequences(e,t)}function d(e,t){return s.collectCrossFileDuplicationFileData(e,t)}export{i as TreeMeasurer,d as collectCrossFileDuplicationFileData,l as collectDuplicationCandidates,u as collectFunctionTokenSequences,s as defaultMeasurer,c as measureCode};
|
|
2
2
|
//# sourceMappingURL=metrics.js.map
|
package/dist/metrics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n const payload = collectCrossFileDataNative(code, language.name, options.duplication?.minTokens);\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim, so the regression gate can re-match renamed or\n * moved functions across two revisions by token-LCS similarity.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n endColumn: fn.endColumn,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"wLAgBA,IAAa,EAAb,KAA0B,CACxB,SAA4B,EAAuB,EAEnD,uBAAwC,CACtC,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC,CAAC,CAAC,IAAK,GAAa,EAAS,IAAI,CAAC,CAAC,CAClF,CAEA,QAAQ,EAAc,EAAsC,CAC1D,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAoB,EAAQ,mBAAqB,GAEvD,OAAO,EADS,EAAkB,EAAM,EAAS,KAAM,EAAmB,EAAQ,WAC/C,EAAG,CAAiB,CACzD,CAGA,6BAA6B,EAAc,EAAwD,CACjG,OAAO,KAAK,oCAAoC,EAAM,CAAO,CAAC,CAAC,UACjE,CAMA,oCAAoC,EAAc,EAAuD,CACvG,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAU,EAA2B,EAAM,EAAS,KAAM,EAAQ,aAAa,SAAS,EAC9F,MAAO,CACL,WAAY,EAAQ,WACpB,OAAQ,EAAQ,OAChB,oBAAqB,EAAQ,oBAE7B,gBAAiB,IAAI,IAAI,EAAQ,eAAe,CAClD,CACF,CAQA,8BAA8B,EAAc,EAAuC,CACjF,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EACtD,OAAO,EAAoC,EAAM,EAAS,IAAI,CAChE,CAEA,gBAAwB,EAAwC,CAC9D,IAAM,EAAW,KAAK,SAAS,IAAI,CAAI,EACvC,GAAI,CAAC,EACH,MAAU,MAAM,yBAAyB,GAAM,EAEjD,OAAO,CACT,CACF,EAOA,SAAS,EAAsB,EAA+B,EAAyC,CACrG,MAAO,CACL,SAAU,EAAQ,SAClB,MAAO,EAAQ,MACf,MAAO,EAAQ,MACf,UAAW,EAAQ,UAAU,IAAK,IAAQ,CACxC,KAAM,EAAG,KACT,SAAU,EAAG,SACb,UAAW,EAAG,UACd,YAAa,EAAG,YAChB,QAAS,EAAG,QACZ,UAAW,EAAG,UACd,qBAAsB,EAAG,qBACzB,oBAAqB,EAAG,oBACxB,aAAc,EAAG,aACjB,KAAM,EAAG,KACT,eAAgB,EAAG,eACnB,SAAU,EAAsB,EAAG,cAAc,EACjD,UAAW,EAAG,SAChB,EAAE,EACF,oBAAqB,EAAQ,oBAC7B,uBAAwB,EAAQ,uBAChC,aAAc,EAAQ,aACtB,UAAW,EAAQ,UACnB,YAAa,EAAQ,YACrB,SAAU,EAAsB,EAAQ,cAAc,EACtD,WAAY,EAAoB,EAAQ,WAAa,IAAA,EACvD,CACF,CAEA,SAAS,EAAsB,EAA+C,CAC5E,GAAM,CAAE,oBAAmB,mBAAkB,iBAAgB,iBAAkB,EACzE,EAAa,EAAoB,EACjC,EAAS,EAAiB,EAC1B,EAAS,IAAe,EAAI,EAAI,EAAS,KAAK,KAAK,CAAU,EAInE,MAAO,CACL,oBACA,mBACA,iBACA,gBACA,aACA,SACA,SACA,QAXiB,IAAqB,EAAI,EAAK,EAAoB,GAAM,EAAgB,IAC/D,CAW5B,CACF,CAEA,MAAa,EAAkB,IAAI,EAEnC,SAAgB,EAAY,EAAc,EAAsC,CAC9E,OAAO,EAAgB,QAAQ,EAAM,CAAO,CAC9C,CAGA,SAAgB,EAA6B,EAAc,EAAwD,CACjH,OAAO,EAAgB,6BAA6B,EAAM,CAAO,CACnE,CAGA,SAAgB,EAA8B,EAAc,EAAuC,CACjG,OAAO,EAAgB,8BAA8B,EAAM,CAAO,CACpE,CAGA,SAAgB,EACd,EACA,EAC8B,CAC9B,OAAO,EAAgB,oCAAoC,EAAM,CAAO,CAC1E"}
|
|
1
|
+
{"version":3,"file":"metrics.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n const payload = collectCrossFileDataNative(code, language.name, options.duplication?.minTokens);\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim, so the regression gate can re-match renamed or\n * moved functions across two revisions by token-LCS similarity.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n endColumn: fn.endColumn,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cyclomaticComplexity: payload.cyclomaticComplexity,\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"wLAgBA,IAAa,EAAb,KAA0B,CACxB,SAA4B,EAAuB,EAEnD,uBAAwC,CACtC,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC,CAAC,CAAC,IAAK,GAAa,EAAS,IAAI,CAAC,CAAC,CAClF,CAEA,QAAQ,EAAc,EAAsC,CAC1D,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAoB,EAAQ,mBAAqB,GAEvD,OAAO,EADS,EAAkB,EAAM,EAAS,KAAM,EAAmB,EAAQ,WAC/C,EAAG,CAAiB,CACzD,CAGA,6BAA6B,EAAc,EAAwD,CACjG,OAAO,KAAK,oCAAoC,EAAM,CAAO,CAAC,CAAC,UACjE,CAMA,oCAAoC,EAAc,EAAuD,CACvG,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAU,EAA2B,EAAM,EAAS,KAAM,EAAQ,aAAa,SAAS,EAC9F,MAAO,CACL,WAAY,EAAQ,WACpB,OAAQ,EAAQ,OAChB,oBAAqB,EAAQ,oBAE7B,gBAAiB,IAAI,IAAI,EAAQ,eAAe,CAClD,CACF,CAQA,8BAA8B,EAAc,EAAuC,CACjF,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EACtD,OAAO,EAAoC,EAAM,EAAS,IAAI,CAChE,CAEA,gBAAwB,EAAwC,CAC9D,IAAM,EAAW,KAAK,SAAS,IAAI,CAAI,EACvC,GAAI,CAAC,EACH,MAAU,MAAM,yBAAyB,GAAM,EAEjD,OAAO,CACT,CACF,EAOA,SAAS,EAAsB,EAA+B,EAAyC,CACrG,MAAO,CACL,SAAU,EAAQ,SAClB,MAAO,EAAQ,MACf,MAAO,EAAQ,MACf,UAAW,EAAQ,UAAU,IAAK,IAAQ,CACxC,KAAM,EAAG,KACT,SAAU,EAAG,SACb,UAAW,EAAG,UACd,YAAa,EAAG,YAChB,QAAS,EAAG,QACZ,UAAW,EAAG,UACd,qBAAsB,EAAG,qBACzB,oBAAqB,EAAG,oBACxB,aAAc,EAAG,aACjB,KAAM,EAAG,KACT,eAAgB,EAAG,eACnB,SAAU,EAAsB,EAAG,cAAc,EACjD,UAAW,EAAG,SAChB,EAAE,EACF,qBAAsB,EAAQ,qBAC9B,oBAAqB,EAAQ,oBAC7B,uBAAwB,EAAQ,uBAChC,aAAc,EAAQ,aACtB,UAAW,EAAQ,UACnB,YAAa,EAAQ,YACrB,SAAU,EAAsB,EAAQ,cAAc,EACtD,WAAY,EAAoB,EAAQ,WAAa,IAAA,EACvD,CACF,CAEA,SAAS,EAAsB,EAA+C,CAC5E,GAAM,CAAE,oBAAmB,mBAAkB,iBAAgB,iBAAkB,EACzE,EAAa,EAAoB,EACjC,EAAS,EAAiB,EAC1B,EAAS,IAAe,EAAI,EAAI,EAAS,KAAK,KAAK,CAAU,EAInE,MAAO,CACL,oBACA,mBACA,iBACA,gBACA,aACA,SACA,SACA,QAXiB,IAAqB,EAAI,EAAK,EAAoB,GAAM,EAAgB,IAC/D,CAW5B,CACF,CAEA,MAAa,EAAkB,IAAI,EAEnC,SAAgB,EAAY,EAAc,EAAsC,CAC9E,OAAO,EAAgB,QAAQ,EAAM,CAAO,CAC9C,CAGA,SAAgB,EAA6B,EAAc,EAAwD,CACjH,OAAO,EAAgB,6BAA6B,EAAM,CAAO,CACnE,CAGA,SAAgB,EAA8B,EAAc,EAAuC,CACjG,OAAO,EAAgB,8BAA8B,EAAM,CAAO,CACpE,CAGA,SAAgB,EACd,EACA,EAC8B,CAC9B,OAAO,EAAgB,oCAAoC,EAAM,CAAO,CAC1E"}
|
package/dist/nativeMetrics.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use strict";let e=require("node:module");function t(e,t,n,r){return JSON.parse(l().measureCodeNative(i(e),t,n,a(r?.minTokens),a(r?.maxGapTokens),a(r?.minSimilarityPercent)))}function n(e,t,n){return JSON.parse(l().collectCrossFileDataNative(i(e),t,a(n)))}function r(e,t){return JSON.parse(l().collectFunctionTokenSequencesNative(i(e),t)).map(e=>Int32Array.from(e))}function i(e){return e.isWellFormed()?e:e.toWellFormed()}function a(e){return e===void 0||Number.isNaN(e)?void 0:Math.min(Math.max(Math.trunc(e),0),4294967295)}var o=class extends Error{};let s,c;function l(){if(s)return s;if(c)throw c;let t=(0,e.createRequire)(require("url").pathToFileURL(__filename).href),n=[`code-gauge-${u()}`,`../native/code-gauge.node`],r=[];for(let e of n){let n;try{n=t(e)}catch(t){r.push(` ${e}: ${t instanceof Error?t.message.split(`
|
|
2
|
-
`)[0]:String(t)}`);continue}let i=n.payloadVersion?.();if(i!==
|
|
2
|
+
`)[0]:String(t)}`);continue}let i=n.payloadVersion?.();if(i!==6){r.push(` ${e}: payload version ${i??`unknown`} does not match the expected 6; rebuild the addon with \`bun run build-native\``);continue}return s=n,n}throw c=new o(`The code-gauge native addon is not available for ${u()}. Build it with \`node scripts/buildNative.mjs\` in the code-gauge package directory (requires a Rust toolchain); when installing with npm, also allow install scripts for code-gauge so its postinstall build can run.\n${r.join(`
|
|
3
3
|
`)}`),c}function u(){let e=`${process.platform}-${process.arch}`;return process.platform===`win32`?`${e}-msvc`:process.platform===`linux`?(process.report?.getReport())?.header?.glibcVersionRuntime?`${e}-gnu`:`${e}-musl`:e}exports.NativeAddonError=o,exports.collectCrossFileDataNative=n,exports.collectFunctionTokenSequencesNative=r,exports.measureCodeNative=t;
|
|
4
4
|
//# sourceMappingURL=nativeMetrics.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nativeMetrics.cjs","names":["createRequire"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport type { CrossFileDuplicateCandidate, Token, TokenRange } from './duplication.js';\nimport type { CodeMetrics, DuplicationOptions, FunctionMetrics } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, effort, ...) are\n * computed in TypeScript because they involve transcendental functions (log2) whose last-bit\n * results can differ between V8 and Rust's libm, and results must not depend on the Rust side's\n * libm build.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeFunctionMetricsPayload extends Omit<FunctionMetrics, 'halstead'> {\n halsteadCounts: NativeHalsteadCounts;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'functions' | 'syntaxTree'> {\n functions: NativeFunctionMetricsPayload[];\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\n/** One file's cross-file clone-detection contribution as serialized by the native addon. */\nexport interface NativeCrossFileDataPayload {\n candidates: CrossFileDuplicateCandidate[];\n tokens: Token[];\n containerStatements: TokenRange[][];\n /** 1-based lines that are neither blank nor comment-only, sorted ascending. */\n codeLineNumbers: number[];\n}\n\ninterface NativeBinding {\n measureCodeNative(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n minTokens?: number,\n maxGapTokens?: number,\n minSimilarityPercent?: number\n ): string;\n collectCrossFileDataNative(code: string, language: string, minTokens?: number): string;\n collectFunctionTokenSequencesNative(code: string, language: string): string;\n payloadVersion?(): number;\n}\n\n/**\n * Must equal `payload_version` in native/src/lib.rs. A previously built addon survives a\n * `git pull` untouched, so without this handshake it would silently return payloads missing\n * newer fields instead of failing with a clear rebuild message.\n */\nconst expectedPayloadVersion =
|
|
1
|
+
{"version":3,"file":"nativeMetrics.cjs","names":["createRequire"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport type { CrossFileDuplicateCandidate, Token, TokenRange } from './duplication.js';\nimport type { CodeMetrics, DuplicationOptions, FunctionMetrics } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, effort, ...) are\n * computed in TypeScript because they involve transcendental functions (log2) whose last-bit\n * results can differ between V8 and Rust's libm, and results must not depend on the Rust side's\n * libm build.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeFunctionMetricsPayload extends Omit<FunctionMetrics, 'halstead'> {\n halsteadCounts: NativeHalsteadCounts;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'functions' | 'syntaxTree'> {\n functions: NativeFunctionMetricsPayload[];\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\n/** One file's cross-file clone-detection contribution as serialized by the native addon. */\nexport interface NativeCrossFileDataPayload {\n candidates: CrossFileDuplicateCandidate[];\n tokens: Token[];\n containerStatements: TokenRange[][];\n /** 1-based lines that are neither blank nor comment-only, sorted ascending. */\n codeLineNumbers: number[];\n}\n\ninterface NativeBinding {\n measureCodeNative(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n minTokens?: number,\n maxGapTokens?: number,\n minSimilarityPercent?: number\n ): string;\n collectCrossFileDataNative(code: string, language: string, minTokens?: number): string;\n collectFunctionTokenSequencesNative(code: string, language: string): string;\n payloadVersion?(): number;\n}\n\n/**\n * Must equal `payload_version` in native/src/lib.rs. A previously built addon survives a\n * `git pull` untouched, so without this handshake it would silently return payloads missing\n * newer fields instead of failing with a clear rebuild message.\n */\nconst expectedPayloadVersion = 6;\n\n/** Measures one file via the native addon, returning the raw payload for assembly in metrics.ts. */\nexport function measureCodeNative(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication?: DuplicationOptions\n): NativeMetricsPayload {\n return JSON.parse(\n loadBinding().measureCodeNative(\n toWellFormed(code),\n language,\n includeSyntaxTree,\n clampToU32(duplication?.minTokens),\n clampToU32(duplication?.maxGapTokens),\n clampToU32(duplication?.minSimilarityPercent)\n )\n ) as NativeMetricsPayload;\n}\n\n/** Collects one file's cross-file clone-detection contribution via the native addon. */\nexport function collectCrossFileDataNative(\n code: string,\n language: string,\n minTokens?: number\n): NativeCrossFileDataPayload {\n return JSON.parse(\n loadBinding().collectCrossFileDataNative(toWellFormed(code), language, clampToU32(minTokens))\n ) as NativeCrossFileDataPayload;\n}\n\n/** Collects normalized token hash sequences of every function via the native addon. */\nexport function collectFunctionTokenSequencesNative(code: string, language: string): Int32Array[] {\n const sequences = JSON.parse(\n loadBinding().collectFunctionTokenSequencesNative(toWellFormed(code), language)\n ) as number[][];\n return sequences.map((sequence) => Int32Array.from(sequence));\n}\n\n/**\n * Lone surrogates cannot cross the N-API boundary losslessly, so ill-formed strings (invalid\n * UTF-16 occasionally present in real-world files) are measured with U+FFFD replacements — the\n * same code units V8's own UTF-8 conversion would substitute.\n */\nfunction toWellFormed(code: string): string {\n return code.isWellFormed() ? code : code.toWellFormed();\n}\n\n/**\n * The duplication settings cross the boundary as u32, whose JavaScript conversion wraps modulo\n * 2^32 (2 ** 32 would become 0 and match everything). The public API accepts any safe integer, so\n * out-of-range values clamp to [0, u32::MAX] — no source can hold 2^32 tokens, so a clamped\n * threshold behaves identically to the requested one. NaN (e.g. `Number(unsetEnvVariable)`)\n * survives clamping arithmetic and would also convert to 0, so it is treated as an absent setting\n * instead.\n */\nfunction clampToU32(value: number | undefined): number | undefined {\n return value === undefined || Number.isNaN(value)\n ? undefined\n : Math.min(Math.max(Math.trunc(value), 0), 0xFF_FF_FF_FF);\n}\n\n/**\n * Raised when no usable native addon can be loaded. Every measurement fails identically until the\n * addon is built, so callers measuring many files (the CLI scan) treat it as fatal for the whole\n * run instead of recording one \"skipped\" entry per file.\n */\nexport class NativeAddonError extends Error {}\n\nlet cachedBinding: NativeBinding | undefined;\nlet cachedFailure: NativeAddonError | undefined;\n\nfunction loadBinding(): NativeBinding {\n if (cachedBinding) {\n return cachedBinding;\n }\n // The failure is memoized too: resolution (including platformTriplet's diagnostic-report call\n // on Linux) would otherwise repeat for every measured file of an already-failing run.\n if (cachedFailure) {\n throw cachedFailure;\n }\n // Resolved relative to this file, so both src/ (tests) and dist/ (build) find the addon.\n const requireNative = createRequire(import.meta.url);\n const specifiers = [\n // A prebuilt platform package, when published for this platform.\n `code-gauge-${platformTriplet()}`,\n // A locally built addon (`bun run build-native`).\n '../native/code-gauge.node',\n ];\n const failures: string[] = [];\n for (const specifier of specifiers) {\n let binding: NativeBinding;\n try {\n binding = requireNative(specifier) as NativeBinding;\n } catch (error) {\n failures.push(` ${specifier}: ${error instanceof Error ? error.message.split('\\n')[0] : String(error)}`);\n continue;\n }\n const version = binding.payloadVersion?.();\n if (version !== expectedPayloadVersion) {\n failures.push(\n ` ${specifier}: payload version ${version ?? 'unknown'} does not match the expected ` +\n `${expectedPayloadVersion}; rebuild the addon with \\`bun run build-native\\``\n );\n continue;\n }\n cachedBinding = binding;\n return binding;\n }\n cachedFailure = new NativeAddonError(\n `The code-gauge native addon is not available for ${platformTriplet()}. Build it with ` +\n '`node scripts/buildNative.mjs` in the code-gauge package directory (requires a Rust ' +\n 'toolchain); when installing with npm, also allow install scripts for code-gauge so its ' +\n `postinstall build can run.\\n${failures.join('\\n')}`\n );\n throw cachedFailure;\n}\n\n/**\n * The platform-package suffix in the napi-rs naming convention: Linux targets are qualified by\n * libc ABI (`linux-x64-gnu` / `linux-x64-musl`) because a glibc-linked addon cannot load on\n * Alpine/musl, and Windows by toolchain ABI (`win32-x64-msvc`), matching what napi-rs tooling\n * generates. Must match scripts/installNative.mjs and the build-native workflow's target list.\n */\nfunction platformTriplet(): string {\n const base = `${process.platform}-${process.arch}`;\n if (process.platform === 'win32') {\n return `${base}-msvc`;\n }\n if (process.platform !== 'linux') {\n return base;\n }\n // Musl builds of Node report no glibc runtime version; see napi-rs's isMusl detection.\n const report = process.report?.getReport() as { header?: { glibcVersionRuntime?: string } } | undefined;\n return report?.header?.glibcVersionRuntime ? `${base}-gnu` : `${base}-musl`;\n}\n"],"mappings":"0CA0DA,SAAgB,EACd,EACA,EACA,EACA,EACsB,CACtB,OAAO,KAAK,MACV,EAAY,CAAC,CAAC,kBACZ,EAAa,CAAI,EACjB,EACA,EACA,EAAW,GAAa,SAAS,EACjC,EAAW,GAAa,YAAY,EACpC,EAAW,GAAa,oBAAoB,CAC9C,CACF,CACF,CAGA,SAAgB,EACd,EACA,EACA,EAC4B,CAC5B,OAAO,KAAK,MACV,EAAY,CAAC,CAAC,2BAA2B,EAAa,CAAI,EAAG,EAAU,EAAW,CAAS,CAAC,CAC9F,CACF,CAGA,SAAgB,EAAoC,EAAc,EAAgC,CAIhG,OAHkB,KAAK,MACrB,EAAY,CAAC,CAAC,oCAAoC,EAAa,CAAI,EAAG,CAAQ,CAEjE,CAAC,CAAC,IAAK,GAAa,WAAW,KAAK,CAAQ,CAAC,CAC9D,CAOA,SAAS,EAAa,EAAsB,CAC1C,OAAO,EAAK,aAAa,EAAI,EAAO,EAAK,aAAa,CACxD,CAUA,SAAS,EAAW,EAA+C,CACjE,OAAO,IAAU,IAAA,IAAa,OAAO,MAAM,CAAK,EAC5C,IAAA,GACA,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,CAAK,EAAG,CAAC,EAAG,UAAa,CAC5D,CAOA,IAAa,EAAb,cAAsC,KAAM,CAAC,EAE7C,IAAI,EACA,EAEJ,SAAS,GAA6B,CACpC,GAAI,EACF,OAAO,EAIT,GAAI,EACF,MAAM,EAGR,IAAM,GAAA,EAAgBA,EAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IAA6B,EAC7C,EAAa,CAEjB,cAAc,EAAgB,IAE9B,2BACF,EACM,EAAqB,CAAC,EAC5B,IAAK,IAAM,KAAa,EAAY,CAClC,IAAI,EACJ,GAAI,CACF,EAAU,EAAc,CAAS,CACnC,OAAS,EAAO,CACd,EAAS,KAAK,KAAK,EAAU,IAAI,aAAiB,MAAQ,EAAM,QAAQ,MAAM;CAAI,CAAC,CAAC,GAAK,OAAO,CAAK,GAAG,EACxG,QACF,CACA,IAAM,EAAU,EAAQ,iBAAiB,EACzC,GAAI,IAAY,EAAwB,CACtC,EAAS,KACP,KAAK,EAAU,oBAAoB,GAAW,UAAU,gFAE1D,EACA,QACF,CAEA,MADA,GAAgB,EACT,CACT,CAOA,KANA,GAAgB,IAAI,EAClB,oDAAoD,EAAgB,EAAE,2NAGrC,EAAS,KAAK;CAAI,GACrD,EACM,CACR,CAQA,SAAS,GAA0B,CACjC,IAAM,EAAO,GAAG,QAAQ,SAAS,GAAG,QAAQ,OAS5C,OARI,QAAQ,WAAa,QAChB,GAAG,EAAK,OAEb,QAAQ,WAAa,SAIV,QAAQ,QAAQ,UAAU,EAAA,EAC1B,QAAQ,oBAAsB,GAAG,EAAK,MAAQ,GAAG,EAAK,OAJ5D,CAKX"}
|
package/dist/nativeMetrics.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import{createRequire as e}from"node:module";function t(e,t,n,r){return JSON.parse(l().measureCodeNative(i(e),t,n,a(r?.minTokens),a(r?.maxGapTokens),a(r?.minSimilarityPercent)))}function n(e,t,n){return JSON.parse(l().collectCrossFileDataNative(i(e),t,a(n)))}function r(e,t){return JSON.parse(l().collectFunctionTokenSequencesNative(i(e),t)).map(e=>Int32Array.from(e))}function i(e){return e.isWellFormed()?e:e.toWellFormed()}function a(e){return e===void 0||Number.isNaN(e)?void 0:Math.min(Math.max(Math.trunc(e),0),4294967295)}var o=class extends Error{};let s,c;function l(){if(s)return s;if(c)throw c;let t=e(import.meta.url),n=[`code-gauge-${u()}`,`../native/code-gauge.node`],r=[];for(let e of n){let n;try{n=t(e)}catch(t){r.push(` ${e}: ${t instanceof Error?t.message.split(`
|
|
2
|
-
`)[0]:String(t)}`);continue}let i=n.payloadVersion?.();if(i!==
|
|
2
|
+
`)[0]:String(t)}`);continue}let i=n.payloadVersion?.();if(i!==6){r.push(` ${e}: payload version ${i??`unknown`} does not match the expected 6; rebuild the addon with \`bun run build-native\``);continue}return s=n,n}throw c=new o(`The code-gauge native addon is not available for ${u()}. Build it with \`node scripts/buildNative.mjs\` in the code-gauge package directory (requires a Rust toolchain); when installing with npm, also allow install scripts for code-gauge so its postinstall build can run.\n${r.join(`
|
|
3
3
|
`)}`),c}function u(){let e=`${process.platform}-${process.arch}`;return process.platform===`win32`?`${e}-msvc`:process.platform===`linux`?(process.report?.getReport())?.header?.glibcVersionRuntime?`${e}-gnu`:`${e}-musl`:e}export{o as NativeAddonError,n as collectCrossFileDataNative,r as collectFunctionTokenSequencesNative,t as measureCodeNative};
|
|
4
4
|
//# sourceMappingURL=nativeMetrics.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nativeMetrics.js","names":[],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport type { CrossFileDuplicateCandidate, Token, TokenRange } from './duplication.js';\nimport type { CodeMetrics, DuplicationOptions, FunctionMetrics } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, effort, ...) are\n * computed in TypeScript because they involve transcendental functions (log2) whose last-bit\n * results can differ between V8 and Rust's libm, and results must not depend on the Rust side's\n * libm build.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeFunctionMetricsPayload extends Omit<FunctionMetrics, 'halstead'> {\n halsteadCounts: NativeHalsteadCounts;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'functions' | 'syntaxTree'> {\n functions: NativeFunctionMetricsPayload[];\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\n/** One file's cross-file clone-detection contribution as serialized by the native addon. */\nexport interface NativeCrossFileDataPayload {\n candidates: CrossFileDuplicateCandidate[];\n tokens: Token[];\n containerStatements: TokenRange[][];\n /** 1-based lines that are neither blank nor comment-only, sorted ascending. */\n codeLineNumbers: number[];\n}\n\ninterface NativeBinding {\n measureCodeNative(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n minTokens?: number,\n maxGapTokens?: number,\n minSimilarityPercent?: number\n ): string;\n collectCrossFileDataNative(code: string, language: string, minTokens?: number): string;\n collectFunctionTokenSequencesNative(code: string, language: string): string;\n payloadVersion?(): number;\n}\n\n/**\n * Must equal `payload_version` in native/src/lib.rs. A previously built addon survives a\n * `git pull` untouched, so without this handshake it would silently return payloads missing\n * newer fields instead of failing with a clear rebuild message.\n */\nconst expectedPayloadVersion =
|
|
1
|
+
{"version":3,"file":"nativeMetrics.js","names":[],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport type { CrossFileDuplicateCandidate, Token, TokenRange } from './duplication.js';\nimport type { CodeMetrics, DuplicationOptions, FunctionMetrics } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, effort, ...) are\n * computed in TypeScript because they involve transcendental functions (log2) whose last-bit\n * results can differ between V8 and Rust's libm, and results must not depend on the Rust side's\n * libm build.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeFunctionMetricsPayload extends Omit<FunctionMetrics, 'halstead'> {\n halsteadCounts: NativeHalsteadCounts;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'functions' | 'syntaxTree'> {\n functions: NativeFunctionMetricsPayload[];\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\n/** One file's cross-file clone-detection contribution as serialized by the native addon. */\nexport interface NativeCrossFileDataPayload {\n candidates: CrossFileDuplicateCandidate[];\n tokens: Token[];\n containerStatements: TokenRange[][];\n /** 1-based lines that are neither blank nor comment-only, sorted ascending. */\n codeLineNumbers: number[];\n}\n\ninterface NativeBinding {\n measureCodeNative(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n minTokens?: number,\n maxGapTokens?: number,\n minSimilarityPercent?: number\n ): string;\n collectCrossFileDataNative(code: string, language: string, minTokens?: number): string;\n collectFunctionTokenSequencesNative(code: string, language: string): string;\n payloadVersion?(): number;\n}\n\n/**\n * Must equal `payload_version` in native/src/lib.rs. A previously built addon survives a\n * `git pull` untouched, so without this handshake it would silently return payloads missing\n * newer fields instead of failing with a clear rebuild message.\n */\nconst expectedPayloadVersion = 6;\n\n/** Measures one file via the native addon, returning the raw payload for assembly in metrics.ts. */\nexport function measureCodeNative(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication?: DuplicationOptions\n): NativeMetricsPayload {\n return JSON.parse(\n loadBinding().measureCodeNative(\n toWellFormed(code),\n language,\n includeSyntaxTree,\n clampToU32(duplication?.minTokens),\n clampToU32(duplication?.maxGapTokens),\n clampToU32(duplication?.minSimilarityPercent)\n )\n ) as NativeMetricsPayload;\n}\n\n/** Collects one file's cross-file clone-detection contribution via the native addon. */\nexport function collectCrossFileDataNative(\n code: string,\n language: string,\n minTokens?: number\n): NativeCrossFileDataPayload {\n return JSON.parse(\n loadBinding().collectCrossFileDataNative(toWellFormed(code), language, clampToU32(minTokens))\n ) as NativeCrossFileDataPayload;\n}\n\n/** Collects normalized token hash sequences of every function via the native addon. */\nexport function collectFunctionTokenSequencesNative(code: string, language: string): Int32Array[] {\n const sequences = JSON.parse(\n loadBinding().collectFunctionTokenSequencesNative(toWellFormed(code), language)\n ) as number[][];\n return sequences.map((sequence) => Int32Array.from(sequence));\n}\n\n/**\n * Lone surrogates cannot cross the N-API boundary losslessly, so ill-formed strings (invalid\n * UTF-16 occasionally present in real-world files) are measured with U+FFFD replacements — the\n * same code units V8's own UTF-8 conversion would substitute.\n */\nfunction toWellFormed(code: string): string {\n return code.isWellFormed() ? code : code.toWellFormed();\n}\n\n/**\n * The duplication settings cross the boundary as u32, whose JavaScript conversion wraps modulo\n * 2^32 (2 ** 32 would become 0 and match everything). The public API accepts any safe integer, so\n * out-of-range values clamp to [0, u32::MAX] — no source can hold 2^32 tokens, so a clamped\n * threshold behaves identically to the requested one. NaN (e.g. `Number(unsetEnvVariable)`)\n * survives clamping arithmetic and would also convert to 0, so it is treated as an absent setting\n * instead.\n */\nfunction clampToU32(value: number | undefined): number | undefined {\n return value === undefined || Number.isNaN(value)\n ? undefined\n : Math.min(Math.max(Math.trunc(value), 0), 0xFF_FF_FF_FF);\n}\n\n/**\n * Raised when no usable native addon can be loaded. Every measurement fails identically until the\n * addon is built, so callers measuring many files (the CLI scan) treat it as fatal for the whole\n * run instead of recording one \"skipped\" entry per file.\n */\nexport class NativeAddonError extends Error {}\n\nlet cachedBinding: NativeBinding | undefined;\nlet cachedFailure: NativeAddonError | undefined;\n\nfunction loadBinding(): NativeBinding {\n if (cachedBinding) {\n return cachedBinding;\n }\n // The failure is memoized too: resolution (including platformTriplet's diagnostic-report call\n // on Linux) would otherwise repeat for every measured file of an already-failing run.\n if (cachedFailure) {\n throw cachedFailure;\n }\n // Resolved relative to this file, so both src/ (tests) and dist/ (build) find the addon.\n const requireNative = createRequire(import.meta.url);\n const specifiers = [\n // A prebuilt platform package, when published for this platform.\n `code-gauge-${platformTriplet()}`,\n // A locally built addon (`bun run build-native`).\n '../native/code-gauge.node',\n ];\n const failures: string[] = [];\n for (const specifier of specifiers) {\n let binding: NativeBinding;\n try {\n binding = requireNative(specifier) as NativeBinding;\n } catch (error) {\n failures.push(` ${specifier}: ${error instanceof Error ? error.message.split('\\n')[0] : String(error)}`);\n continue;\n }\n const version = binding.payloadVersion?.();\n if (version !== expectedPayloadVersion) {\n failures.push(\n ` ${specifier}: payload version ${version ?? 'unknown'} does not match the expected ` +\n `${expectedPayloadVersion}; rebuild the addon with \\`bun run build-native\\``\n );\n continue;\n }\n cachedBinding = binding;\n return binding;\n }\n cachedFailure = new NativeAddonError(\n `The code-gauge native addon is not available for ${platformTriplet()}. Build it with ` +\n '`node scripts/buildNative.mjs` in the code-gauge package directory (requires a Rust ' +\n 'toolchain); when installing with npm, also allow install scripts for code-gauge so its ' +\n `postinstall build can run.\\n${failures.join('\\n')}`\n );\n throw cachedFailure;\n}\n\n/**\n * The platform-package suffix in the napi-rs naming convention: Linux targets are qualified by\n * libc ABI (`linux-x64-gnu` / `linux-x64-musl`) because a glibc-linked addon cannot load on\n * Alpine/musl, and Windows by toolchain ABI (`win32-x64-msvc`), matching what napi-rs tooling\n * generates. Must match scripts/installNative.mjs and the build-native workflow's target list.\n */\nfunction platformTriplet(): string {\n const base = `${process.platform}-${process.arch}`;\n if (process.platform === 'win32') {\n return `${base}-msvc`;\n }\n if (process.platform !== 'linux') {\n return base;\n }\n // Musl builds of Node report no glibc runtime version; see napi-rs's isMusl detection.\n const report = process.report?.getReport() as { header?: { glibcVersionRuntime?: string } } | undefined;\n return report?.header?.glibcVersionRuntime ? `${base}-gnu` : `${base}-musl`;\n}\n"],"mappings":"4CA0DA,SAAgB,EACd,EACA,EACA,EACA,EACsB,CACtB,OAAO,KAAK,MACV,EAAY,CAAC,CAAC,kBACZ,EAAa,CAAI,EACjB,EACA,EACA,EAAW,GAAa,SAAS,EACjC,EAAW,GAAa,YAAY,EACpC,EAAW,GAAa,oBAAoB,CAC9C,CACF,CACF,CAGA,SAAgB,EACd,EACA,EACA,EAC4B,CAC5B,OAAO,KAAK,MACV,EAAY,CAAC,CAAC,2BAA2B,EAAa,CAAI,EAAG,EAAU,EAAW,CAAS,CAAC,CAC9F,CACF,CAGA,SAAgB,EAAoC,EAAc,EAAgC,CAIhG,OAHkB,KAAK,MACrB,EAAY,CAAC,CAAC,oCAAoC,EAAa,CAAI,EAAG,CAAQ,CAEjE,CAAC,CAAC,IAAK,GAAa,WAAW,KAAK,CAAQ,CAAC,CAC9D,CAOA,SAAS,EAAa,EAAsB,CAC1C,OAAO,EAAK,aAAa,EAAI,EAAO,EAAK,aAAa,CACxD,CAUA,SAAS,EAAW,EAA+C,CACjE,OAAO,IAAU,IAAA,IAAa,OAAO,MAAM,CAAK,EAC5C,IAAA,GACA,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,CAAK,EAAG,CAAC,EAAG,UAAa,CAC5D,CAOA,IAAa,EAAb,cAAsC,KAAM,CAAC,EAE7C,IAAI,EACA,EAEJ,SAAS,GAA6B,CACpC,GAAI,EACF,OAAO,EAIT,GAAI,EACF,MAAM,EAGR,IAAM,EAAgB,EAAc,YAAY,GAAG,EAC7C,EAAa,CAEjB,cAAc,EAAgB,IAE9B,2BACF,EACM,EAAqB,CAAC,EAC5B,IAAK,IAAM,KAAa,EAAY,CAClC,IAAI,EACJ,GAAI,CACF,EAAU,EAAc,CAAS,CACnC,OAAS,EAAO,CACd,EAAS,KAAK,KAAK,EAAU,IAAI,aAAiB,MAAQ,EAAM,QAAQ,MAAM;CAAI,CAAC,CAAC,GAAK,OAAO,CAAK,GAAG,EACxG,QACF,CACA,IAAM,EAAU,EAAQ,iBAAiB,EACzC,GAAI,IAAY,EAAwB,CACtC,EAAS,KACP,KAAK,EAAU,oBAAoB,GAAW,UAAU,gFAE1D,EACA,QACF,CAEA,MADA,GAAgB,EACT,CACT,CAOA,KANA,GAAgB,IAAI,EAClB,oDAAoD,EAAgB,EAAE,2NAGrC,EAAS,KAAK;CAAI,GACrD,EACM,CACR,CAQA,SAAS,GAA0B,CACjC,IAAM,EAAO,GAAG,QAAQ,SAAS,GAAG,QAAQ,OAS5C,OARI,QAAQ,WAAa,QAChB,GAAG,EAAK,OAEb,QAAQ,WAAa,SAIV,QAAQ,QAAQ,UAAU,EAAA,EAC1B,QAAQ,oBAAsB,GAAG,EAAK,MAAQ,GAAG,EAAK,OAJ5D,CAKX"}
|
package/dist/types.d.ts
CHANGED
|
@@ -140,6 +140,16 @@ export interface CodeMetrics {
|
|
|
140
140
|
bytes: number;
|
|
141
141
|
lines: LineMetrics;
|
|
142
142
|
functions: FunctionMetrics[];
|
|
143
|
+
/**
|
|
144
|
+
* The file's cyclomatic complexity as McCabe's v = e - n + 2p over its components: the sum of
|
|
145
|
+
* every function's value, plus 1 per initializer block (Java static/instance initializers, Kotlin
|
|
146
|
+
* `init`, JavaScript/TypeScript class `static` blocks), plus the decisions outside functions,
|
|
147
|
+
* plus 1 for the module body when
|
|
148
|
+
* the file runs top-level code (always for JavaScript, TypeScript, Python, and Ruby; for C# and
|
|
149
|
+
* Kotlin when the file has top-level statements, except a Kotlin file with parse errors, whose top
|
|
150
|
+
* level the grammar cannot be trusted to have read correctly).
|
|
151
|
+
*/
|
|
152
|
+
cyclomaticComplexity: number;
|
|
143
153
|
cognitiveComplexity: number;
|
|
144
154
|
maxCognitiveComplexity: number;
|
|
145
155
|
nestingDepth: number;
|
package/native/src/complexity.rs
CHANGED
|
@@ -136,6 +136,16 @@ struct FunctionBodyPass<'sets, 'code, 'source> {
|
|
|
136
136
|
code: &'code Source<'source>,
|
|
137
137
|
frames: Vec<FunctionBodyFrame>,
|
|
138
138
|
results: HashMap<usize, FunctionBodyMetrics>,
|
|
139
|
+
/// Cyclomatic decisions inside class bodies nested in functions, which no function owns.
|
|
140
|
+
nested_class_decisions: u64,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Per-function body metrics, plus the cyclomatic decisions no function body owns.
|
|
144
|
+
pub struct BodyMetrics {
|
|
145
|
+
pub by_function: HashMap<usize, FunctionBodyMetrics>,
|
|
146
|
+
/// Cyclomatic decisions outside every function body (top-level statements, field initializers,
|
|
147
|
+
/// including those of classes nested in functions).
|
|
148
|
+
pub top_level_decisions: u64,
|
|
139
149
|
}
|
|
140
150
|
|
|
141
151
|
/// Per-function complexity and NCSS for every function boundary, in one post-order pass so each
|
|
@@ -150,16 +160,20 @@ pub fn measure_function_body_metrics(
|
|
|
150
160
|
root: Node<'_>,
|
|
151
161
|
sets: &LanguageSets,
|
|
152
162
|
code: &Source<'_>,
|
|
153
|
-
) ->
|
|
163
|
+
) -> BodyMetrics {
|
|
154
164
|
let mut pass = FunctionBodyPass {
|
|
155
165
|
sets,
|
|
156
166
|
code,
|
|
157
|
-
// frames[0] is a sentinel for top-level code; its
|
|
167
|
+
// frames[0] is a sentinel for top-level code; only its cyclomatic decisions are kept.
|
|
158
168
|
frames: vec![FunctionBodyFrame::new(0, 0)],
|
|
159
169
|
results: HashMap::new(),
|
|
170
|
+
nested_class_decisions: 0,
|
|
160
171
|
};
|
|
161
172
|
pass.visit(root, 0, 0, false, false, false);
|
|
162
|
-
|
|
173
|
+
BodyMetrics {
|
|
174
|
+
by_function: pass.results,
|
|
175
|
+
top_level_decisions: pass.frames[0].cyclomatic_complexity - 1 + pass.nested_class_decisions,
|
|
176
|
+
}
|
|
163
177
|
}
|
|
164
178
|
|
|
165
179
|
impl FunctionBodyPass<'_, '_, '_> {
|
|
@@ -216,10 +230,12 @@ impl FunctionBodyPass<'_, '_, '_> {
|
|
|
216
230
|
|
|
217
231
|
// Each branch, short-circuit operator, and pattern guard adds one path (McCabe; NIST SP
|
|
218
232
|
// 500-235 §4); `else` adds none.
|
|
219
|
-
if
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
233
|
+
if is_decision || is_boolean_operator(current, self.code) || is_pattern_guard(current) {
|
|
234
|
+
if counts_for_own_body {
|
|
235
|
+
self.top_frame().cyclomatic_complexity += 1;
|
|
236
|
+
} else if !opens_frame {
|
|
237
|
+
self.nested_class_decisions += 1;
|
|
238
|
+
}
|
|
223
239
|
}
|
|
224
240
|
if is_decision && !is_case_clause {
|
|
225
241
|
if is_continuation {
|
package/native/src/languages.rs
CHANGED
|
@@ -7,6 +7,10 @@ pub struct LanguageDefinition {
|
|
|
7
7
|
grammar_id: GrammarId,
|
|
8
8
|
pub function_node_types: &'static [&'static str],
|
|
9
9
|
pub decision_node_types: &'static [&'static str],
|
|
10
|
+
/// Whether every file's top-level code runs as a module body (a component of its own in the
|
|
11
|
+
/// file's cyclomatic complexity); C# and Kotlin decide per file by the presence of top-level
|
|
12
|
+
/// statements (never for a Kotlin file with parse errors).
|
|
13
|
+
pub executes_top_level: bool,
|
|
10
14
|
pub nesting_node_types: &'static [&'static str],
|
|
11
15
|
pub ncss_node_types: &'static [&'static str],
|
|
12
16
|
pub ncss_container_node_types: &'static [&'static str],
|
|
@@ -656,6 +660,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
656
660
|
grammar_id: GrammarId::JavaScript,
|
|
657
661
|
function_node_types: COMMON_FUNCTION_NODES,
|
|
658
662
|
decision_node_types: COMMON_DECISION_NODES,
|
|
663
|
+
executes_top_level: true,
|
|
659
664
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
660
665
|
ncss_node_types: JS_NCSS_NODES,
|
|
661
666
|
ncss_container_node_types: &[],
|
|
@@ -666,6 +671,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
666
671
|
grammar_id: GrammarId::JavaScript,
|
|
667
672
|
function_node_types: COMMON_FUNCTION_NODES,
|
|
668
673
|
decision_node_types: COMMON_DECISION_NODES,
|
|
674
|
+
executes_top_level: true,
|
|
669
675
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
670
676
|
ncss_node_types: JS_NCSS_NODES,
|
|
671
677
|
ncss_container_node_types: &[],
|
|
@@ -676,6 +682,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
676
682
|
grammar_id: GrammarId::TypeScript,
|
|
677
683
|
function_node_types: COMMON_FUNCTION_NODES,
|
|
678
684
|
decision_node_types: COMMON_DECISION_NODES,
|
|
685
|
+
executes_top_level: true,
|
|
679
686
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
680
687
|
ncss_node_types: JS_NCSS_NODES,
|
|
681
688
|
ncss_container_node_types: &[],
|
|
@@ -686,6 +693,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
686
693
|
grammar_id: GrammarId::Tsx,
|
|
687
694
|
function_node_types: COMMON_FUNCTION_NODES,
|
|
688
695
|
decision_node_types: COMMON_DECISION_NODES,
|
|
696
|
+
executes_top_level: true,
|
|
689
697
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
690
698
|
ncss_node_types: JS_NCSS_NODES,
|
|
691
699
|
ncss_container_node_types: &[],
|
|
@@ -696,6 +704,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
696
704
|
grammar_id: GrammarId::Python,
|
|
697
705
|
function_node_types: COMMON_FUNCTION_NODES,
|
|
698
706
|
decision_node_types: COMMON_DECISION_NODES,
|
|
707
|
+
executes_top_level: true,
|
|
699
708
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
700
709
|
ncss_node_types: PYTHON_NCSS_NODES,
|
|
701
710
|
ncss_container_node_types: &[],
|
|
@@ -706,6 +715,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
706
715
|
grammar_id: GrammarId::Go,
|
|
707
716
|
function_node_types: COMMON_FUNCTION_NODES,
|
|
708
717
|
decision_node_types: GO_DECISION_NODES,
|
|
718
|
+
executes_top_level: false,
|
|
709
719
|
nesting_node_types: GO_NESTING_NODES,
|
|
710
720
|
ncss_node_types: GO_NCSS_NODES,
|
|
711
721
|
ncss_container_node_types: &[],
|
|
@@ -716,6 +726,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
716
726
|
grammar_id: GrammarId::Rust,
|
|
717
727
|
function_node_types: COMMON_FUNCTION_NODES,
|
|
718
728
|
decision_node_types: COMMON_DECISION_NODES,
|
|
729
|
+
executes_top_level: false,
|
|
719
730
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
720
731
|
ncss_node_types: RUST_NCSS_NODES,
|
|
721
732
|
ncss_container_node_types: RUST_NCSS_CONTAINERS,
|
|
@@ -726,6 +737,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
726
737
|
grammar_id: GrammarId::Java,
|
|
727
738
|
function_node_types: JAVA_FUNCTION_NODES,
|
|
728
739
|
decision_node_types: JAVA_DECISION_NODES,
|
|
740
|
+
executes_top_level: false,
|
|
729
741
|
nesting_node_types: JAVA_DECISION_NODES,
|
|
730
742
|
ncss_node_types: JAVA_NCSS_NODES,
|
|
731
743
|
ncss_container_node_types: &[],
|
|
@@ -736,6 +748,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
736
748
|
grammar_id: GrammarId::Ruby,
|
|
737
749
|
function_node_types: RUBY_FUNCTION_NODES,
|
|
738
750
|
decision_node_types: RUBY_DECISION_NODES,
|
|
751
|
+
executes_top_level: true,
|
|
739
752
|
nesting_node_types: RUBY_DECISION_NODES,
|
|
740
753
|
ncss_node_types: RUBY_NCSS_NODES,
|
|
741
754
|
ncss_container_node_types: RUBY_NCSS_CONTAINERS,
|
|
@@ -746,6 +759,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
746
759
|
grammar_id: GrammarId::C,
|
|
747
760
|
function_node_types: C_FUNCTION_NODES,
|
|
748
761
|
decision_node_types: C_DECISION_NODES,
|
|
762
|
+
executes_top_level: false,
|
|
749
763
|
nesting_node_types: C_DECISION_NODES,
|
|
750
764
|
ncss_node_types: C_NCSS_NODES,
|
|
751
765
|
ncss_container_node_types: &[],
|
|
@@ -756,6 +770,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
756
770
|
grammar_id: GrammarId::Cpp,
|
|
757
771
|
function_node_types: C_FUNCTION_NODES,
|
|
758
772
|
decision_node_types: CPP_DECISION_NODES,
|
|
773
|
+
executes_top_level: false,
|
|
759
774
|
nesting_node_types: CPP_DECISION_NODES,
|
|
760
775
|
ncss_node_types: CPP_NCSS_NODES,
|
|
761
776
|
ncss_container_node_types: &[],
|
|
@@ -766,6 +781,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
766
781
|
grammar_id: GrammarId::CSharp,
|
|
767
782
|
function_node_types: CSHARP_FUNCTION_NODES,
|
|
768
783
|
decision_node_types: CSHARP_DECISION_NODES,
|
|
784
|
+
executes_top_level: false,
|
|
769
785
|
nesting_node_types: CSHARP_DECISION_NODES,
|
|
770
786
|
ncss_node_types: CSHARP_NCSS_NODES,
|
|
771
787
|
ncss_container_node_types: &[],
|
|
@@ -776,6 +792,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
776
792
|
grammar_id: GrammarId::Kotlin,
|
|
777
793
|
function_node_types: KOTLIN_FUNCTION_NODES,
|
|
778
794
|
decision_node_types: KOTLIN_DECISION_NODES,
|
|
795
|
+
executes_top_level: false,
|
|
779
796
|
nesting_node_types: KOTLIN_DECISION_NODES,
|
|
780
797
|
ncss_node_types: KOTLIN_NCSS_NODES,
|
|
781
798
|
ncss_container_node_types: KOTLIN_NCSS_CONTAINERS,
|
package/native/src/lib.rs
CHANGED
package/native/src/measure.rs
CHANGED
|
@@ -38,11 +38,12 @@ pub fn measure(
|
|
|
38
38
|
.filter(|node| !is_lambda_body_block(*node) && is_implemented_function(*node))
|
|
39
39
|
.collect();
|
|
40
40
|
|
|
41
|
-
let
|
|
41
|
+
let body_metrics = measure_function_body_metrics(root, &sets, code);
|
|
42
42
|
let function_metrics: Vec<FunctionMetrics> = functions
|
|
43
43
|
.iter()
|
|
44
44
|
.map(|node| {
|
|
45
|
-
let body_metrics =
|
|
45
|
+
let body_metrics = body_metrics
|
|
46
|
+
.by_function
|
|
46
47
|
.get(&node.id())
|
|
47
48
|
.expect("every collected function node opens a frame in the body-metrics pass");
|
|
48
49
|
FunctionMetrics {
|
|
@@ -76,6 +77,16 @@ pub fn measure(
|
|
|
76
77
|
language: language.name.to_string(),
|
|
77
78
|
bytes: code.code.len(),
|
|
78
79
|
lines,
|
|
80
|
+
// McCabe's v = e - n + 2p over the file's components: every function, every initializer
|
|
81
|
+
// block, and the module body when the file runs top-level code; decisions outside functions
|
|
82
|
+
// belong to the file.
|
|
83
|
+
cyclomatic_complexity: function_metrics
|
|
84
|
+
.iter()
|
|
85
|
+
.map(|function| function.cyclomatic_complexity)
|
|
86
|
+
.sum::<u64>()
|
|
87
|
+
+ body_metrics.top_level_decisions
|
|
88
|
+
+ u64::from(language.executes_top_level || has_top_level_statements(root, language))
|
|
89
|
+
+ count_initializer_blocks(root),
|
|
79
90
|
cognitive_complexity: global_complexity.cognitive_complexity,
|
|
80
91
|
max_cognitive_complexity: function_metrics
|
|
81
92
|
.iter()
|
|
@@ -95,6 +106,76 @@ pub fn measure(
|
|
|
95
106
|
})
|
|
96
107
|
}
|
|
97
108
|
|
|
109
|
+
/// Initializer blocks run code of their own, so each is a component like a function: Java static
|
|
110
|
+
/// and instance initializers, Kotlin `init` blocks, and JavaScript/TypeScript class `static`
|
|
111
|
+
/// blocks. Their decisions already count as decisions outside functions.
|
|
112
|
+
fn count_initializer_blocks(root: Node<'_>) -> u64 {
|
|
113
|
+
let initializer_types: HashSet<&'static str> = [
|
|
114
|
+
"static_initializer",
|
|
115
|
+
"anonymous_initializer",
|
|
116
|
+
"class_static_block",
|
|
117
|
+
"block",
|
|
118
|
+
]
|
|
119
|
+
.into_iter()
|
|
120
|
+
.collect();
|
|
121
|
+
collect_nodes(root, &initializer_types)
|
|
122
|
+
.into_iter()
|
|
123
|
+
.filter(|node| {
|
|
124
|
+
// A bare block is an initializer only as a direct member of a Java class or enum body.
|
|
125
|
+
node.kind() != "block"
|
|
126
|
+
|| node.parent().is_some_and(|parent| {
|
|
127
|
+
matches!(parent.kind(), "class_body" | "enum_body_declarations")
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
.count() as u64
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// A C# top-level statement: a `global_statement`, or a statement inside a top-level `#if` block,
|
|
134
|
+
/// which the grammar does not wrap in `global_statement`; preprocessor blocks are transparent.
|
|
135
|
+
fn is_csharp_top_level_statement(node: Node<'_>) -> bool {
|
|
136
|
+
if node.kind().starts_with("preproc_") {
|
|
137
|
+
return named_children(node)
|
|
138
|
+
.into_iter()
|
|
139
|
+
.any(is_csharp_top_level_statement);
|
|
140
|
+
}
|
|
141
|
+
node.kind() == "global_statement"
|
|
142
|
+
|| node.kind() == "block"
|
|
143
|
+
|| node.kind().ends_with("_statement")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// Whether a C# or Kotlin file runs top-level code: C# top-level statements, or a Kotlin script's
|
|
147
|
+
/// statements beside its declarations.
|
|
148
|
+
fn has_top_level_statements(root: Node<'_>, language: &LanguageDefinition) -> bool {
|
|
149
|
+
const KOTLIN_DECLARATIONS: &[&str] = &[
|
|
150
|
+
"package_header",
|
|
151
|
+
"import_list",
|
|
152
|
+
"class_declaration",
|
|
153
|
+
"object_declaration",
|
|
154
|
+
"function_declaration",
|
|
155
|
+
"property_declaration",
|
|
156
|
+
"type_alias",
|
|
157
|
+
"shebang_line",
|
|
158
|
+
"file_annotation",
|
|
159
|
+
"getter",
|
|
160
|
+
"setter",
|
|
161
|
+
];
|
|
162
|
+
let children = named_children(root);
|
|
163
|
+
match language.name {
|
|
164
|
+
"csharp" => children.into_iter().any(is_csharp_top_level_statement),
|
|
165
|
+
// The grammar mis-parses some valid declarations (non-empty companion objects, `fun
|
|
166
|
+
// interface`) and leaves recovery residue at the top level, so a file with parse errors is
|
|
167
|
+
// never taken for a script.
|
|
168
|
+
"kotlin" => {
|
|
169
|
+
!root.has_error()
|
|
170
|
+
&& children.iter().any(|child| {
|
|
171
|
+
!KOTLIN_DECLARATIONS.contains(&child.kind())
|
|
172
|
+
&& !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind())
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
_ => false,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
98
179
|
/// Collects one file's cross-file clone-detection contribution; see CrossFileFileData.
|
|
99
180
|
pub fn collect_cross_file_data(
|
|
100
181
|
code: &str,
|
package/native/src/types.rs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-gauge",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "Measure code metrics with tree-sitter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -89,12 +89,12 @@
|
|
|
89
89
|
"registry": "https://registry.npmjs.org/"
|
|
90
90
|
},
|
|
91
91
|
"optionalDependencies": {
|
|
92
|
-
"code-gauge-linux-x64-gnu": "4.
|
|
93
|
-
"code-gauge-linux-arm64-gnu": "4.
|
|
94
|
-
"code-gauge-linux-x64-musl": "4.
|
|
95
|
-
"code-gauge-linux-arm64-musl": "4.
|
|
96
|
-
"code-gauge-darwin-x64": "4.
|
|
97
|
-
"code-gauge-darwin-arm64": "4.
|
|
98
|
-
"code-gauge-win32-x64-msvc": "4.
|
|
92
|
+
"code-gauge-linux-x64-gnu": "4.4.0",
|
|
93
|
+
"code-gauge-linux-arm64-gnu": "4.4.0",
|
|
94
|
+
"code-gauge-linux-x64-musl": "4.4.0",
|
|
95
|
+
"code-gauge-linux-arm64-musl": "4.4.0",
|
|
96
|
+
"code-gauge-darwin-x64": "4.4.0",
|
|
97
|
+
"code-gauge-darwin-arm64": "4.4.0",
|
|
98
|
+
"code-gauge-win32-x64-msvc": "4.4.0"
|
|
99
99
|
}
|
|
100
100
|
}
|