code-gauge 4.6.0 → 4.7.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.
@@ -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 crossFileData?: NativeCrossFileDataPayload;\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 nearMissBlocks: 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 includeCrossFileData?: boolean\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 = 7;\n\n/**\n * Measures one file via the native addon, returning the raw payload for assembly in metrics.ts;\n * with `includeCrossFileData`, the payload also carries the file's cross-file contribution.\n */\nexport function measureCodeNative(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication?: DuplicationOptions,\n includeCrossFileData = false\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 includeCrossFileData\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":"4CAgEA,SAAgB,EACd,EACA,EACA,EACA,EACA,EAAuB,GACD,CACtB,OAAO,KAAK,MACV,EAAY,CAAC,CAAC,kBACZ,EAAa,CAAI,EACjB,EACA,EACA,EAAW,GAAa,SAAS,EACjC,EAAW,GAAa,YAAY,EACpC,EAAW,GAAa,oBAAoB,EAC5C,CACF,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"}
1
+ {"version":3,"file":"nativeMetrics.js","names":[],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import 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 crossFileData?: NativeCrossFileDataPayload;\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 nearMissBlocks: TokenRange[];\n /** 1-based lines that are neither blank nor comment-only, sorted ascending. */\n codeLineNumbers: number[];\n}\n\nexport interface NativeBinding {\n measureCodeNative(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n minTokens?: number,\n maxGapTokens?: number,\n minSimilarityPercent?: number,\n includeCrossFileData?: boolean\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 */\nexport const expectedPayloadVersion = 7;\n\n/**\n * Measures one file via the native addon, returning the raw payload for assembly in metrics.ts;\n * with `includeCrossFileData`, the payload also carries the file's cross-file contribution.\n */\nexport function measureCodeNative(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication?: DuplicationOptions,\n includeCrossFileData = false\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 includeCrossFileData\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\n/** Replaces the N-API addon, e.g. with the WebAssembly build on runtimes without N-API. */\nexport function setNativeBinding(binding: NativeBinding): void {\n cachedBinding = binding;\n}\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 // node:module is loaded lazily so that runtimes without it can bundle this module.\n const requireNative = process.getBuiltinModule('node:module').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":"AA+DA,SAAgB,EACd,EACA,EACA,EACA,EACA,EAAuB,GACD,CACtB,OAAO,KAAK,MACV,EAAY,CAAC,CAAC,kBACZ,EAAa,CAAI,EACjB,EACA,EACA,EAAW,GAAa,SAAS,EACjC,EAAW,GAAa,YAAY,EACpC,EAAW,GAAa,oBAAoB,EAC5C,CACF,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,EAGJ,SAAgB,EAAiB,EAA8B,CAC7D,EAAgB,CAClB,CAEA,SAAS,GAA6B,CACpC,GAAI,EACF,OAAO,EAIT,GAAI,EACF,MAAM,EAIR,IAAM,EAAgB,QAAQ,iBAAiB,aAAa,CAAC,CAAC,cAAc,YAAY,GAAG,EACrF,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,IAAA,EAAoC,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/scan.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./crossFileDuplication.cjs"),n=require("./languages.cjs"),r=require("./nativeMetrics.cjs"),i=require("./metrics.cjs");let a=require("node:path");a=e.__toESM(a,1);let o=require("node:fs/promises"),s=require("node:os");s=e.__toESM(s,1);const c=new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`obj`,`target`,`test-fixtures`,`vendor`,`venv`]),l=new Set([`__tests__`,`test`,`tests`,`spec`]),u=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,d=/Tests?\.(?:java|kt|cs)$/u;function f(e){return e===`~`?s.default.homedir():e.startsWith(`~/`)?a.default.join(s.default.homedir(),e.slice(2)):a.default.resolve(e)}async function p(e){try{return(await(0,o.stat)(e)).isDirectory()?e:a.default.dirname(e)}catch{return a.default.dirname(e)}}async function m(e,t){let n=[],r=[],i=[],s=e;try{s=await(0,o.realpath)(e)}catch{}let c=a.default.dirname(s),l;try{l=await(0,o.stat)(s)}catch(e){let t=`${j(s,c)}: ${P(e)}`;return{displayRoot:c,files:n,errors:[t],warnings:i,fatalError:t}}if(l.isFile()){let e=a.default.dirname(s),o=A(s,t,!0);if(!o){let t=`${j(s,e)}: unsupported file type`;return{displayRoot:e,files:n,errors:[t],warnings:i,fatalError:t}}let c=_(t,n,r,i,e);try{await C(s,o,`single-file`,c,s)}catch(t){return g(t,e,n,r,i)}return{displayRoot:e,files:n,errors:r,warnings:i}}try{await b(s,_(t,n,r,i,s))}catch(e){return g(e,s,n,r,i)}return{displayRoot:s,files:n,errors:r,warnings:i}}async function h(e,t,n){let r=[],i=[],s=[],c=_(n,r,i,s,e);for(let l of t){let t=k(l,n)?A(l,n):void 0;if(!t)continue;let u=a.default.join(e,l);if(!(await(0,o.lstat)(u).catch(()=>{}))?.isSymbolicLink())try{await C(u,t,`directory`,c)}catch(t){return g(t,e,r,i,s)}}return{displayRoot:e,files:r,errors:i,warnings:s}}function g(e,t,n,i,a){if(!(e instanceof r.NativeAddonError))throw e;let o=P(e);return{displayRoot:t,files:n,errors:[...i,o],warnings:a,fatalError:o}}function _(e,t,n,r,i){return{options:e,files:t,errors:n,warnings:r,visitedDirectories:new Set,visitedFiles:new Set,rootDirectory:i}}async function v(e,t,n){try{return await e()}catch(e){n.errors.push(`${j(t,n.rootDirectory)}: ${P(e)}`);return}}async function y(e,t){let n=await v(()=>(0,o.realpath)(e),e,t);return n!==void 0&&O(n,t.rootDirectory)?n:void 0}async function b(e,t){let n=await y(e,t);if(n===void 0||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r=await v(()=>(0,o.readdir)(e,{withFileTypes:!0}),e,t);if(r!==void 0)for(let n of r){let r=a.default.join(e,n.name);if(n.isSymbolicLink()){await x(n.name,r,t);continue}if(n.isDirectory()){if(D(n.name,t.options))continue;await b(r,t);continue}n.isFile()&&await S(r,t)}}async function x(e,t,n){let r=await y(t,n);if(r===void 0)return;let i=await v(()=>(0,o.stat)(t),t,n);if(i!==void 0){if(i.isDirectory()){if(D(e,n.options)||D(a.default.basename(r),n.options))return;await b(t,n);return}i.isFile()&&await S(t,n,r,r)}}async function S(e,t,n=e,r){let i=A(n,t.options);i&&await C(e,i,`directory`,t,r)}async function C(e,t,n,a,s){try{let r=s??await(0,o.realpath)(e);if(a.visitedFiles.has(r))return;a.visitedFiles.add(r);let c=await(0,o.readFile)(e,`utf8`),l={language:t,duplication:a.options.duplication};if(n===`single-file`){a.files.push({file:e,metrics:i.measureCode(c,l)});return}let{metrics:u,crossFileData:d,crossFileError:f}=w(c,l);f!==void 0&&a.warnings.push(`${j(e,a.rootDirectory)}: cross-file duplication candidates unavailable: ${f}`),a.files.push({file:e,metrics:u,duplicationCandidates:d})}catch(t){if(t instanceof r.NativeAddonError)throw t;a.errors.push(`${j(e,a.rootDirectory)}: ${P(t)}`)}}function w(e,t){try{return i.measureCodeWithCrossFileData(e,t)}catch(n){if(n instanceof r.NativeAddonError)throw n;return{metrics:i.measureCode(e,t),crossFileError:P(n)}}}function T(e,n){if(e.fatalError||e.files.length<2)return;let r=e.files.flatMap(({file:t,duplicationCandidates:n})=>n?[{file:j(t,e.displayRoot),...n}]:[]);r.length<2||(e.crossFileDuplication=t.measureCrossFileDuplication(r,n.duplication))}function E(e,t,n){let r=new Set(e?.duplication.duplicateLineNumbers),i=t&&Object.hasOwn(t.duplicateLineNumbersByFile,n)?t.duplicateLineNumbersByFile[n]??[]:[];for(let e of i)r.add(e);return r}function D(e,t){return c.has(e)?!0:!t.includeTests&&l.has(e)}function O(e,t){let n=a.default.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${a.default.sep}`)&&!a.default.isAbsolute(n)}function k(e,t){let n=e.split(`/`);for(let e of n.slice(0,-1))if(c.has(e)||!t.includeTests&&l.has(e))return!1;return A(e,t)!==void 0}function A(e,t,r=!1){let i=e.toLowerCase();if(!(!r&&(i.endsWith(`.d.ts`)||i.endsWith(`.d.mts`)||i.endsWith(`.d.cts`)||i.endsWith(`.min.js`)||i.endsWith(`.pnp.cjs`)))&&(r||t.includeTests||!u.test(a.default.basename(e))&&!d.test(a.default.basename(e))))return n.detectLanguage(e)}function j(e,t){return a.default.relative(t,e)||a.default.basename(e)}function M(e){process.stdout.write(e)}function N(e){process.stderr.write(e)}function P(e){return e instanceof Error?e.message:String(e)}exports.addCrossFileDuplication=T,exports.collectDuplicatedLineNumbers=E,exports.configSearchDirectory=p,exports.formatError=P,exports.formatPath=j,exports.getLanguage=A,exports.isScannedPath=k,exports.measureWithCrossFileData=w,exports.resolveTarget=f,exports.scanListedFiles=h,exports.scanTarget=m,exports.writeStderr=N,exports.writeStdout=M;
1
+ "use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./crossFileDuplication.cjs"),n=require("./languages.cjs"),r=require("./nativeMetrics.cjs"),i=require("./metrics.cjs");let a=require("node:fs/promises"),o=require("node:path");o=e.__toESM(o,1);let s=require("node:os");s=e.__toESM(s,1);const c=new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`obj`,`target`,`test-fixtures`,`vendor`,`venv`]),l=new Set([`__tests__`,`test`,`tests`,`spec`]),u=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,d=/Tests?\.(?:java|kt|cs)$/u;function f(e){return e===`~`?s.default.homedir():e.startsWith(`~/`)?o.default.join(s.default.homedir(),e.slice(2)):o.default.resolve(e)}async function p(e){try{return(await(0,a.stat)(e)).isDirectory()?e:o.default.dirname(e)}catch{return o.default.dirname(e)}}async function m(e,t){let n=[],r=[],i=[],s=e;try{s=await(0,a.realpath)(e)}catch{}let c=o.default.dirname(s),l;try{l=await(0,a.stat)(s)}catch(e){let t=`${j(s,c)}: ${P(e)}`;return{displayRoot:c,files:n,errors:[t],warnings:i,fatalError:t}}if(l.isFile()){let e=o.default.dirname(s),a=A(s,t,!0);if(!a){let t=`${j(s,e)}: unsupported file type`;return{displayRoot:e,files:n,errors:[t],warnings:i,fatalError:t}}let c=_(t,n,r,i,e);try{await C(s,a,`single-file`,c,s)}catch(t){return g(t,e,n,r,i)}return{displayRoot:e,files:n,errors:r,warnings:i}}try{await b(s,_(t,n,r,i,s))}catch(e){return g(e,s,n,r,i)}return{displayRoot:s,files:n,errors:r,warnings:i}}async function h(e,t,n){let r=[],i=[],s=[],c=_(n,r,i,s,e);for(let l of t){let t=k(l,n)?A(l,n):void 0;if(!t)continue;let u=o.default.join(e,l);if(!(await(0,a.lstat)(u).catch(()=>{}))?.isSymbolicLink())try{await C(u,t,`directory`,c)}catch(t){return g(t,e,r,i,s)}}return{displayRoot:e,files:r,errors:i,warnings:s}}function g(e,t,n,i,a){if(!(e instanceof r.NativeAddonError))throw e;let o=P(e);return{displayRoot:t,files:n,errors:[...i,o],warnings:a,fatalError:o}}function _(e,t,n,r,i){return{options:e,files:t,errors:n,warnings:r,visitedDirectories:new Set,visitedFiles:new Set,rootDirectory:i}}async function v(e,t,n){try{return await e()}catch(e){n.errors.push(`${j(t,n.rootDirectory)}: ${P(e)}`);return}}async function y(e,t){let n=await v(()=>(0,a.realpath)(e),e,t);return n!==void 0&&O(n,t.rootDirectory)?n:void 0}async function b(e,t){let n=await y(e,t);if(n===void 0||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r=await v(()=>(0,a.readdir)(e,{withFileTypes:!0}),e,t);if(r!==void 0)for(let n of r){let r=o.default.join(e,n.name);if(n.isSymbolicLink()){await x(n.name,r,t);continue}if(n.isDirectory()){if(D(n.name,t.options))continue;await b(r,t);continue}n.isFile()&&await S(r,t)}}async function x(e,t,n){let r=await y(t,n);if(r===void 0)return;let i=await v(()=>(0,a.stat)(t),t,n);if(i!==void 0){if(i.isDirectory()){if(D(e,n.options)||D(o.default.basename(r),n.options))return;await b(t,n);return}i.isFile()&&await S(t,n,r,r)}}async function S(e,t,n=e,r){let i=A(n,t.options);i&&await C(e,i,`directory`,t,r)}async function C(e,t,n,o,s){try{let r=s??await(0,a.realpath)(e);if(o.visitedFiles.has(r))return;o.visitedFiles.add(r);let c=await(0,a.readFile)(e,`utf8`),l={language:t,duplication:o.options.duplication};if(n===`single-file`){o.files.push({file:e,metrics:i.measureCode(c,l)});return}let{metrics:u,crossFileData:d,crossFileError:f}=w(c,l);f!==void 0&&o.warnings.push(`${j(e,o.rootDirectory)}: cross-file duplication candidates unavailable: ${f}`),o.files.push({file:e,metrics:u,duplicationCandidates:d})}catch(t){if(t instanceof r.NativeAddonError)throw t;o.errors.push(`${j(e,o.rootDirectory)}: ${P(t)}`)}}function w(e,t){try{return i.measureCodeWithCrossFileData(e,t)}catch(n){if(n instanceof r.NativeAddonError)throw n;return{metrics:i.measureCode(e,t),crossFileError:P(n)}}}function T(e,n){if(e.fatalError||e.files.length<2)return;let r=e.files.flatMap(({file:t,duplicationCandidates:n})=>n?[{file:j(t,e.displayRoot),...n}]:[]);r.length<2||(e.crossFileDuplication=t.measureCrossFileDuplication(r,n.duplication))}function E(e,t,n){let r=new Set(e?.duplication.duplicateLineNumbers),i=t&&Object.hasOwn(t.duplicateLineNumbersByFile,n)?t.duplicateLineNumbersByFile[n]??[]:[];for(let e of i)r.add(e);return r}function D(e,t){return c.has(e)?!0:!t.includeTests&&l.has(e)}function O(e,t){let n=o.default.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${o.default.sep}`)&&!o.default.isAbsolute(n)}function k(e,t){let n=e.split(`/`);for(let e of n.slice(0,-1))if(c.has(e)||!t.includeTests&&l.has(e))return!1;return A(e,t)!==void 0}function A(e,t,r=!1){let i=e.toLowerCase();if(!(!r&&(i.endsWith(`.d.ts`)||i.endsWith(`.d.mts`)||i.endsWith(`.d.cts`)||i.endsWith(`.min.js`)||i.endsWith(`.pnp.cjs`)))&&(r||t.includeTests||!u.test(o.default.basename(e))&&!d.test(o.default.basename(e))))return n.detectLanguage(e)}function j(e,t){return o.default.relative(t,e)||o.default.basename(e)}function M(e){process.stdout.write(e)}function N(e){process.stderr.write(e)}function P(e){return e instanceof Error?e.message:String(e)}exports.addCrossFileDuplication=T,exports.collectDuplicatedLineNumbers=E,exports.configSearchDirectory=p,exports.formatError=P,exports.formatPath=j,exports.getLanguage=A,exports.isScannedPath=k,exports.measureWithCrossFileData=w,exports.resolveTarget=f,exports.scanListedFiles=h,exports.scanTarget=m,exports.writeStderr=N,exports.writeStdout=M;
2
2
  //# sourceMappingURL=scan.cjs.map
package/dist/scan.js CHANGED
@@ -1,2 +1,2 @@
1
- import{measureCrossFileDuplication as e}from"./crossFileDuplication.js";import{detectLanguage as t}from"./languages.js";import{NativeAddonError as n}from"./nativeMetrics.js";import{measureCode as r,measureCodeWithCrossFileData as i}from"./metrics.js";import a from"node:path";import{lstat as o,readFile as s,readdir as c,realpath as l,stat as u}from"node:fs/promises";import d from"node:os";const f=new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`obj`,`target`,`test-fixtures`,`vendor`,`venv`]),p=new Set([`__tests__`,`test`,`tests`,`spec`]),m=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,h=/Tests?\.(?:java|kt|cs)$/u;function g(e){return e===`~`?d.homedir():e.startsWith(`~/`)?a.join(d.homedir(),e.slice(2)):a.resolve(e)}async function _(e){try{return(await u(e)).isDirectory()?e:a.dirname(e)}catch{return a.dirname(e)}}async function v(e,t){let n=[],r=[],i=[],o=e;try{o=await l(e)}catch{}let s=a.dirname(o),c;try{c=await u(o)}catch(e){let t=`${F(o,s)}: ${R(e)}`;return{displayRoot:s,files:n,errors:[t],warnings:i,fatalError:t}}if(c.isFile()){let e=a.dirname(o),s=P(o,t,!0);if(!s){let t=`${F(o,e)}: unsupported file type`;return{displayRoot:e,files:n,errors:[t],warnings:i,fatalError:t}}let c=x(t,n,r,i,e);try{await D(o,s,`single-file`,c,o)}catch(t){return b(t,e,n,r,i)}return{displayRoot:e,files:n,errors:r,warnings:i}}try{await w(o,x(t,n,r,i,o))}catch(e){return b(e,o,n,r,i)}return{displayRoot:o,files:n,errors:r,warnings:i}}async function y(e,t,n){let r=[],i=[],s=[],c=x(n,r,i,s,e);for(let l of t){let t=N(l,n)?P(l,n):void 0;if(!t)continue;let u=a.join(e,l);if(!(await o(u).catch(()=>{}))?.isSymbolicLink())try{await D(u,t,`directory`,c)}catch(t){return b(t,e,r,i,s)}}return{displayRoot:e,files:r,errors:i,warnings:s}}function b(e,t,r,i,a){if(!(e instanceof n))throw e;let o=R(e);return{displayRoot:t,files:r,errors:[...i,o],warnings:a,fatalError:o}}function x(e,t,n,r,i){return{options:e,files:t,errors:n,warnings:r,visitedDirectories:new Set,visitedFiles:new Set,rootDirectory:i}}async function S(e,t,n){try{return await e()}catch(e){n.errors.push(`${F(t,n.rootDirectory)}: ${R(e)}`);return}}async function C(e,t){let n=await S(()=>l(e),e,t);return n!==void 0&&M(n,t.rootDirectory)?n:void 0}async function w(e,t){let n=await C(e,t);if(n===void 0||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r=await S(()=>c(e,{withFileTypes:!0}),e,t);if(r!==void 0)for(let n of r){let r=a.join(e,n.name);if(n.isSymbolicLink()){await T(n.name,r,t);continue}if(n.isDirectory()){if(j(n.name,t.options))continue;await w(r,t);continue}n.isFile()&&await E(r,t)}}async function T(e,t,n){let r=await C(t,n);if(r===void 0)return;let i=await S(()=>u(t),t,n);if(i!==void 0){if(i.isDirectory()){if(j(e,n.options)||j(a.basename(r),n.options))return;await w(t,n);return}i.isFile()&&await E(t,n,r,r)}}async function E(e,t,n=e,r){let i=P(n,t.options);i&&await D(e,i,`directory`,t,r)}async function D(e,t,i,a,o){try{let n=o??await l(e);if(a.visitedFiles.has(n))return;a.visitedFiles.add(n);let c=await s(e,`utf8`),u={language:t,duplication:a.options.duplication};if(i===`single-file`){a.files.push({file:e,metrics:r(c,u)});return}let{metrics:d,crossFileData:f,crossFileError:p}=O(c,u);p!==void 0&&a.warnings.push(`${F(e,a.rootDirectory)}: cross-file duplication candidates unavailable: ${p}`),a.files.push({file:e,metrics:d,duplicationCandidates:f})}catch(t){if(t instanceof n)throw t;a.errors.push(`${F(e,a.rootDirectory)}: ${R(t)}`)}}function O(e,t){try{return i(e,t)}catch(i){if(i instanceof n)throw i;return{metrics:r(e,t),crossFileError:R(i)}}}function k(t,n){if(t.fatalError||t.files.length<2)return;let r=t.files.flatMap(({file:e,duplicationCandidates:n})=>n?[{file:F(e,t.displayRoot),...n}]:[]);r.length<2||(t.crossFileDuplication=e(r,n.duplication))}function A(e,t,n){let r=new Set(e?.duplication.duplicateLineNumbers),i=t&&Object.hasOwn(t.duplicateLineNumbersByFile,n)?t.duplicateLineNumbersByFile[n]??[]:[];for(let e of i)r.add(e);return r}function j(e,t){return f.has(e)?!0:!t.includeTests&&p.has(e)}function M(e,t){let n=a.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${a.sep}`)&&!a.isAbsolute(n)}function N(e,t){let n=e.split(`/`);for(let e of n.slice(0,-1))if(f.has(e)||!t.includeTests&&p.has(e))return!1;return P(e,t)!==void 0}function P(e,n,r=!1){let i=e.toLowerCase();if(!(!r&&(i.endsWith(`.d.ts`)||i.endsWith(`.d.mts`)||i.endsWith(`.d.cts`)||i.endsWith(`.min.js`)||i.endsWith(`.pnp.cjs`)))&&(r||n.includeTests||!m.test(a.basename(e))&&!h.test(a.basename(e))))return t(e)}function F(e,t){return a.relative(t,e)||a.basename(e)}function I(e){process.stdout.write(e)}function L(e){process.stderr.write(e)}function R(e){return e instanceof Error?e.message:String(e)}export{k as addCrossFileDuplication,A as collectDuplicatedLineNumbers,_ as configSearchDirectory,R as formatError,F as formatPath,P as getLanguage,N as isScannedPath,O as measureWithCrossFileData,g as resolveTarget,y as scanListedFiles,v as scanTarget,L as writeStderr,I as writeStdout};
1
+ import{measureCrossFileDuplication as e}from"./crossFileDuplication.js";import{detectLanguage as t}from"./languages.js";import{NativeAddonError as n}from"./nativeMetrics.js";import{measureCode as r,measureCodeWithCrossFileData as i}from"./metrics.js";import{lstat as a,readFile as o,readdir as s,realpath as c,stat as l}from"node:fs/promises";import u from"node:path";import d from"node:os";const f=new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`obj`,`target`,`test-fixtures`,`vendor`,`venv`]),p=new Set([`__tests__`,`test`,`tests`,`spec`]),m=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,h=/Tests?\.(?:java|kt|cs)$/u;function g(e){return e===`~`?d.homedir():e.startsWith(`~/`)?u.join(d.homedir(),e.slice(2)):u.resolve(e)}async function _(e){try{return(await l(e)).isDirectory()?e:u.dirname(e)}catch{return u.dirname(e)}}async function v(e,t){let n=[],r=[],i=[],a=e;try{a=await c(e)}catch{}let o=u.dirname(a),s;try{s=await l(a)}catch(e){let t=`${F(a,o)}: ${R(e)}`;return{displayRoot:o,files:n,errors:[t],warnings:i,fatalError:t}}if(s.isFile()){let e=u.dirname(a),o=P(a,t,!0);if(!o){let t=`${F(a,e)}: unsupported file type`;return{displayRoot:e,files:n,errors:[t],warnings:i,fatalError:t}}let s=x(t,n,r,i,e);try{await D(a,o,`single-file`,s,a)}catch(t){return b(t,e,n,r,i)}return{displayRoot:e,files:n,errors:r,warnings:i}}try{await w(a,x(t,n,r,i,a))}catch(e){return b(e,a,n,r,i)}return{displayRoot:a,files:n,errors:r,warnings:i}}async function y(e,t,n){let r=[],i=[],o=[],s=x(n,r,i,o,e);for(let c of t){let t=N(c,n)?P(c,n):void 0;if(!t)continue;let l=u.join(e,c);if(!(await a(l).catch(()=>{}))?.isSymbolicLink())try{await D(l,t,`directory`,s)}catch(t){return b(t,e,r,i,o)}}return{displayRoot:e,files:r,errors:i,warnings:o}}function b(e,t,r,i,a){if(!(e instanceof n))throw e;let o=R(e);return{displayRoot:t,files:r,errors:[...i,o],warnings:a,fatalError:o}}function x(e,t,n,r,i){return{options:e,files:t,errors:n,warnings:r,visitedDirectories:new Set,visitedFiles:new Set,rootDirectory:i}}async function S(e,t,n){try{return await e()}catch(e){n.errors.push(`${F(t,n.rootDirectory)}: ${R(e)}`);return}}async function C(e,t){let n=await S(()=>c(e),e,t);return n!==void 0&&M(n,t.rootDirectory)?n:void 0}async function w(e,t){let n=await C(e,t);if(n===void 0||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r=await S(()=>s(e,{withFileTypes:!0}),e,t);if(r!==void 0)for(let n of r){let r=u.join(e,n.name);if(n.isSymbolicLink()){await T(n.name,r,t);continue}if(n.isDirectory()){if(j(n.name,t.options))continue;await w(r,t);continue}n.isFile()&&await E(r,t)}}async function T(e,t,n){let r=await C(t,n);if(r===void 0)return;let i=await S(()=>l(t),t,n);if(i!==void 0){if(i.isDirectory()){if(j(e,n.options)||j(u.basename(r),n.options))return;await w(t,n);return}i.isFile()&&await E(t,n,r,r)}}async function E(e,t,n=e,r){let i=P(n,t.options);i&&await D(e,i,`directory`,t,r)}async function D(e,t,i,a,s){try{let n=s??await c(e);if(a.visitedFiles.has(n))return;a.visitedFiles.add(n);let l=await o(e,`utf8`),u={language:t,duplication:a.options.duplication};if(i===`single-file`){a.files.push({file:e,metrics:r(l,u)});return}let{metrics:d,crossFileData:f,crossFileError:p}=O(l,u);p!==void 0&&a.warnings.push(`${F(e,a.rootDirectory)}: cross-file duplication candidates unavailable: ${p}`),a.files.push({file:e,metrics:d,duplicationCandidates:f})}catch(t){if(t instanceof n)throw t;a.errors.push(`${F(e,a.rootDirectory)}: ${R(t)}`)}}function O(e,t){try{return i(e,t)}catch(i){if(i instanceof n)throw i;return{metrics:r(e,t),crossFileError:R(i)}}}function k(t,n){if(t.fatalError||t.files.length<2)return;let r=t.files.flatMap(({file:e,duplicationCandidates:n})=>n?[{file:F(e,t.displayRoot),...n}]:[]);r.length<2||(t.crossFileDuplication=e(r,n.duplication))}function A(e,t,n){let r=new Set(e?.duplication.duplicateLineNumbers),i=t&&Object.hasOwn(t.duplicateLineNumbersByFile,n)?t.duplicateLineNumbersByFile[n]??[]:[];for(let e of i)r.add(e);return r}function j(e,t){return f.has(e)?!0:!t.includeTests&&p.has(e)}function M(e,t){let n=u.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${u.sep}`)&&!u.isAbsolute(n)}function N(e,t){let n=e.split(`/`);for(let e of n.slice(0,-1))if(f.has(e)||!t.includeTests&&p.has(e))return!1;return P(e,t)!==void 0}function P(e,n,r=!1){let i=e.toLowerCase();if(!(!r&&(i.endsWith(`.d.ts`)||i.endsWith(`.d.mts`)||i.endsWith(`.d.cts`)||i.endsWith(`.min.js`)||i.endsWith(`.pnp.cjs`)))&&(r||n.includeTests||!m.test(u.basename(e))&&!h.test(u.basename(e))))return t(e)}function F(e,t){return u.relative(t,e)||u.basename(e)}function I(e){process.stdout.write(e)}function L(e){process.stderr.write(e)}function R(e){return e instanceof Error?e.message:String(e)}export{k as addCrossFileDuplication,A as collectDuplicatedLineNumbers,_ as configSearchDirectory,R as formatError,F as formatPath,P as getLanguage,N as isScannedPath,O as measureWithCrossFileData,g as resolveTarget,y as scanListedFiles,v as scanTarget,L as writeStderr,I as writeStdout};
2
2
  //# sourceMappingURL=scan.js.map
@@ -0,0 +1,2 @@
1
+ "use strict";const e=require("./nativeMetrics.cjs"),t=new TextEncoder,n=new TextDecoder;function r(e){let t,r,s=a=>{if(!t){if(r)throw r;try{t=i(e)}catch(e){throw r=e,e}}let{exports:o,stderr:s}=t;s.length=0;let c;try{c=a(o)}catch(e){throw t=void 0,Error(`The code-gauge WebAssembly module crashed: ${s.join(``).trim()||String(e)}`,{cause:e})}let l=n.decode(new Uint8Array(o.memory.buffer,o.result_ptr(),o.result_len()));if(c!==0)throw Error(l);return l};return{measureCodeNative:(e,t,n,r,i,c,l)=>s(s=>s.measure_code(...a(s,e),...a(s,t),Number(n),o(r),o(i),o(c),Number(l??!1))),collectCrossFileDataNative:(e,t,n)=>s(r=>r.collect_cross_file_data(...a(r,e),...a(r,t),o(n))),collectFunctionTokenSequencesNative:(e,t)=>s(n=>n.collect_function_token_sequences(...a(n,e),...a(n,t)))}}function i(t){let n=[],r,i=new WebAssembly.Instance(t,{wasi_snapshot_preview1:c(()=>r,n)}).exports;r=i.memory;let a=i.payload_version();if(a!==7)throw new e.NativeAddonError(`The code-gauge WebAssembly module has payload version ${a}, but 7 is expected; reinstall code-gauge, or in a code-gauge repository checkout, rebuild it with \`bun run build-wasm\``);return{exports:i,stderr:n}}function a(e,n){let r=t.encode(n),i=e.alloc(r.length);return new Uint8Array(e.memory.buffer,i,r.length).set(r),[i,r.length]}function o(e){return e??-1}const s=65536;function c(e,t){let r=()=>new DataView(e().buffer),i=()=>8;return{environ_get:()=>0,environ_sizes_get:(e,t)=>(r().setUint32(e,0,!0),r().setUint32(t,0,!0),0),clock_time_get:(e,t,n)=>(r().setBigUint64(n,BigInt(Date.now())*1000000n,!0),0),random_get:(t,n)=>{for(let r=0;r<n;r+=s)crypto.getRandomValues(new Uint8Array(e().buffer,t+r,Math.min(s,n-r)));return 0},fd_write:(i,a,o,s)=>{let c=r(),l=0;for(let r=0;r<o;r++){let o=c.getUint32(a+r*8,!0),s=c.getUint32(a+r*8+4,!0);i===2&&t.push(n.decode(new Uint8Array(e().buffer,o,s))),l+=s}return c.setUint32(s,l,!0),0},fd_close:i,fd_fdstat_get:i,fd_fdstat_set_flags:i,fd_read:i,fd_seek:i,proc_exit:e=>{throw Error(`The code-gauge WebAssembly module exited with code ${e}`)}}}exports.createWasmBinding=r;
2
+ //# sourceMappingURL=wasmBinding.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasmBinding.cjs","names":["NativeAddonError"],"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 return {\n 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 ?? false)\n )\n ),\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 const unavailable = (): number => WASI_ERRNO_BADF;\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"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { type NativeBinding } from './nativeMetrics.js';
2
+ /**
3
+ * Wraps the WebAssembly build of the native addon (native/src/wasm.rs) as a NativeBinding. The
4
+ * module is instantiated synchronously on first use and again after a trap (a panic or stack
5
+ * overflow), because a trap leaves the instance's memory and stack pointer in an undefined state.
6
+ * An instantiation failure (e.g., a payload version mismatch) is memoized like the N-API loader's,
7
+ * since instantiating the same module again would fail again.
8
+ */
9
+ export declare function createWasmBinding(module: WebAssembly.Module): NativeBinding;
@@ -0,0 +1,2 @@
1
+ import{NativeAddonError as e}from"./nativeMetrics.js";const t=new TextEncoder,n=new TextDecoder;function r(e){let t,r,s=a=>{if(!t){if(r)throw r;try{t=i(e)}catch(e){throw r=e,e}}let{exports:o,stderr:s}=t;s.length=0;let c;try{c=a(o)}catch(e){throw t=void 0,Error(`The code-gauge WebAssembly module crashed: ${s.join(``).trim()||String(e)}`,{cause:e})}let l=n.decode(new Uint8Array(o.memory.buffer,o.result_ptr(),o.result_len()));if(c!==0)throw Error(l);return l};return{measureCodeNative:(e,t,n,r,i,c,l)=>s(s=>s.measure_code(...a(s,e),...a(s,t),Number(n),o(r),o(i),o(c),Number(l??!1))),collectCrossFileDataNative:(e,t,n)=>s(r=>r.collect_cross_file_data(...a(r,e),...a(r,t),o(n))),collectFunctionTokenSequencesNative:(e,t)=>s(n=>n.collect_function_token_sequences(...a(n,e),...a(n,t)))}}function i(t){let n=[],r,i=new WebAssembly.Instance(t,{wasi_snapshot_preview1:c(()=>r,n)}).exports;r=i.memory;let a=i.payload_version();if(a!==7)throw new e(`The code-gauge WebAssembly module has payload version ${a}, but 7 is expected; reinstall code-gauge, or in a code-gauge repository checkout, rebuild it with \`bun run build-wasm\``);return{exports:i,stderr:n}}function a(e,n){let r=t.encode(n),i=e.alloc(r.length);return new Uint8Array(e.memory.buffer,i,r.length).set(r),[i,r.length]}function o(e){return e??-1}const s=65536;function c(e,t){let r=()=>new DataView(e().buffer),i=()=>8;return{environ_get:()=>0,environ_sizes_get:(e,t)=>(r().setUint32(e,0,!0),r().setUint32(t,0,!0),0),clock_time_get:(e,t,n)=>(r().setBigUint64(n,BigInt(Date.now())*1000000n,!0),0),random_get:(t,n)=>{for(let r=0;r<n;r+=s)crypto.getRandomValues(new Uint8Array(e().buffer,t+r,Math.min(s,n-r)));return 0},fd_write:(i,a,o,s)=>{let c=r(),l=0;for(let r=0;r<o;r++){let o=c.getUint32(a+r*8,!0),s=c.getUint32(a+r*8+4,!0);i===2&&t.push(n.decode(new Uint8Array(e().buffer,o,s))),l+=s}return c.setUint32(s,l,!0),0},fd_close:i,fd_fdstat_get:i,fd_fdstat_set_flags:i,fd_read:i,fd_seek:i,proc_exit:e=>{throw Error(`The code-gauge WebAssembly module exited with code ${e}`)}}}export{r as createWasmBinding};
2
+ //# sourceMappingURL=wasmBinding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasmBinding.js","names":[],"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 return {\n 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 ?? false)\n )\n ),\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 const unavailable = (): number => WASI_ERRNO_BADF;\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"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./crossFileDuplication.cjs"),n=require("./languages.cjs"),r=require("./nativeMetrics.cjs"),i=require("./metrics.cjs"),a=require("./regressionGate.cjs");require("./index.cjs");const o=require("./wasmBinding.cjs");let s=require("../native/code-gauge.wasm");s=e.__toESM(s,1),r.setNativeBinding(o.createWasmBinding(s.default)),exports.TreeMeasurer=i.TreeMeasurer,exports.collectCrossFileDuplicationFileData=i.collectCrossFileDuplicationFileData,exports.collectDuplicationCandidates=i.collectDuplicationCandidates,exports.collectFunctionTokenSequences=i.collectFunctionTokenSequences,exports.defaultGateOptions=a.defaultGateOptions,exports.defaultLanguages=n.defaultLanguages,exports.defaultMeasurer=i.defaultMeasurer,exports.detectLanguage=n.detectLanguage,exports.evaluateRegressionGate=a.evaluateRegressionGate,exports.measureCode=i.measureCode,exports.measureCrossFileDuplication=t.measureCrossFileDuplication,exports.supportedLanguages=n.supportedLanguages;
2
+ //# sourceMappingURL=worker.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.cjs","names":["setNativeBinding","createWasmBinding","wasmModule"],"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":"+ZAMAA,EAAAA,iBAAiBC,EAAAA,kBAAkBC,EAAAA,OAAU,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './index.js';
package/dist/worker.js ADDED
@@ -0,0 +1,2 @@
1
+ import{measureCrossFileDuplication as e}from"./crossFileDuplication.js";import{defaultLanguages as t,detectLanguage as n,supportedLanguages as r}from"./languages.js";import{setNativeBinding as i}from"./nativeMetrics.js";import{TreeMeasurer as a,collectCrossFileDuplicationFileData as o,collectDuplicationCandidates as s,collectFunctionTokenSequences as c,defaultMeasurer as l,measureCode as u}from"./metrics.js";import{defaultGateOptions as d,evaluateRegressionGate as f}from"./regressionGate.js";import"./index.js";import{createWasmBinding as p}from"./wasmBinding.js";import m from"../native/code-gauge.wasm";i(p(m));export{a as TreeMeasurer,o as collectCrossFileDuplicationFileData,s as collectDuplicationCandidates,c as collectFunctionTokenSequences,d as defaultGateOptions,t as defaultLanguages,l as defaultMeasurer,n as detectLanguage,f as evaluateRegressionGate,u as measureCode,e as measureCrossFileDuplication,r as supportedLanguages};
2
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","names":[],"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":"kmBAMA,EAAiB,EAAkB,CAAU,CAAC"}
package/native/Cargo.toml CHANGED
@@ -9,8 +9,6 @@ publish = false
9
9
  crate-type = ["cdylib"]
10
10
 
11
11
  [dependencies]
12
- napi = { version = "3", default-features = false }
13
- napi-derive = "3"
14
12
  serde = { version = "1", features = ["derive"] }
15
13
  serde_json = "1"
16
14
  indexmap = "2"
@@ -39,6 +37,11 @@ tree-sitter-ruby = "=0.21.0"
39
37
  tree-sitter-rust = "=0.21.2"
40
38
  tree-sitter-typescript = "=0.21.2"
41
39
 
40
+ # The WebAssembly build (src/wasm.rs) exports a plain C ABI instead of N-API.
41
+ [target.'cfg(not(target_family = "wasm"))'.dependencies]
42
+ napi = { version = "3", default-features = false }
43
+ napi-derive = "3"
44
+
42
45
  [build-dependencies]
43
46
  napi-build = "2"
44
47
 
package/native/build.rs CHANGED
@@ -1,3 +1,6 @@
1
1
  fn main() {
2
- napi_build::setup();
2
+ let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap_or_default();
3
+ if !target_family.split(',').any(|family| family == "wasm") {
4
+ napi_build::setup();
5
+ }
3
6
  }
Binary file
package/native/src/lib.rs CHANGED
@@ -1,8 +1,5 @@
1
1
  #![deny(clippy::all)]
2
2
 
3
- use napi::bindgen_prelude::*;
4
- use napi_derive::napi;
5
-
6
3
  use crate::duplication::DuplicationSettings;
7
4
 
8
5
  mod complexity;
@@ -11,16 +8,20 @@ mod duplication;
11
8
  mod functions;
12
9
  mod languages;
13
10
  mod measure;
11
+ #[cfg(not(target_family = "wasm"))]
12
+ mod napi;
14
13
  mod ncss;
15
14
  mod near_miss;
16
15
  mod types;
17
16
  mod util;
17
+ #[cfg(target_family = "wasm")]
18
+ mod wasm;
18
19
 
19
20
  /// Version of the native payload schema. The TypeScript wrapper refuses a binding whose version
20
21
  /// differs from the one it expects, so a stale prebuilt addon fails with a clear rebuild message
21
22
  /// instead of silently returning an incompatible payload. Bump on every payload-shape change,
22
23
  /// together with `expectedPayloadVersion` in src/nativeMetrics.ts.
23
- #[napi]
24
+ /// scripts/installNative.mjs parses the literal from this function's source.
24
25
  pub fn payload_version() -> u32 {
25
26
  7
26
27
  }
@@ -31,59 +32,52 @@ pub fn payload_version() -> u32 {
31
32
  /// The TypeScript wrapper derives the remaining float metrics (Halstead volume/effort/...): they
32
33
  /// involve transcendental functions whose last-bit results can differ between V8 and Rust's libm,
33
34
  /// and results must not depend on which side computes them.
34
- #[napi]
35
- pub fn measure_code_native(
36
- code: String,
37
- language: String,
38
- include_syntax_tree: Option<bool>,
35
+ fn measure_code(
36
+ code: &str,
37
+ language: &str,
38
+ include_syntax_tree: bool,
39
39
  min_tokens: Option<u32>,
40
40
  max_gap_tokens: Option<u32>,
41
41
  min_similarity_percent: Option<u32>,
42
- include_cross_file_data: Option<bool>,
43
- ) -> Result<String> {
44
- let definition = find_language(&language)?;
42
+ include_cross_file_data: bool,
43
+ ) -> Result<String, String> {
44
+ let definition = find_language(language)?;
45
45
  let settings = to_duplication_settings(min_tokens, max_gap_tokens, min_similarity_percent);
46
46
  let metrics = measure::measure(
47
- &code,
47
+ code,
48
48
  definition,
49
- include_syntax_tree.unwrap_or(false),
50
- include_cross_file_data.unwrap_or(false),
49
+ include_syntax_tree,
50
+ include_cross_file_data,
51
51
  &settings,
52
- )
53
- .map_err(Error::from_reason)?;
54
- serde_json::to_string(&metrics).map_err(|error| Error::from_reason(error.to_string()))
52
+ )?;
53
+ serde_json::to_string(&metrics).map_err(|error| error.to_string())
55
54
  }
56
55
 
57
56
  /// Collects one file's cross-file clone-detection contribution (candidates, normalized token
58
57
  /// stream, statement structure, and code line numbers) as JSON; see CrossFileFileData.
59
- #[napi]
60
- pub fn collect_cross_file_data_native(
61
- code: String,
62
- language: String,
58
+ fn collect_cross_file_data(
59
+ code: &str,
60
+ language: &str,
63
61
  min_tokens: Option<u32>,
64
- ) -> Result<String> {
65
- let definition = find_language(&language)?;
62
+ ) -> Result<String, String> {
63
+ let definition = find_language(language)?;
66
64
  let min_tokens = min_tokens
67
65
  .map(|value| value as usize)
68
66
  .unwrap_or(DuplicationSettings::default().min_tokens);
69
- let data = measure::collect_cross_file_data(&code, definition, min_tokens)
70
- .map_err(Error::from_reason)?;
71
- serde_json::to_string(&data).map_err(|error| Error::from_reason(error.to_string()))
67
+ let data = measure::collect_cross_file_data(code, definition, min_tokens)?;
68
+ serde_json::to_string(&data).map_err(|error| error.to_string())
72
69
  }
73
70
 
74
71
  /// Collects normalized token hash sequences of every function as JSON (number[][]),
75
- /// index-parallel to the functions array of measure_code_native.
76
- #[napi]
77
- pub fn collect_function_token_sequences_native(code: String, language: String) -> Result<String> {
78
- let definition = find_language(&language)?;
79
- let sequences =
80
- measure::collect_function_token_sequences(&code, definition).map_err(Error::from_reason)?;
81
- serde_json::to_string(&sequences).map_err(|error| Error::from_reason(error.to_string()))
72
+ /// index-parallel to the functions array of measure_code.
73
+ fn collect_function_token_sequences(code: &str, language: &str) -> Result<String, String> {
74
+ let definition = find_language(language)?;
75
+ let sequences = measure::collect_function_token_sequences(code, definition)?;
76
+ serde_json::to_string(&sequences).map_err(|error| error.to_string())
82
77
  }
83
78
 
84
- fn find_language(language: &str) -> Result<&'static languages::LanguageDefinition> {
85
- languages::find_language(language)
86
- .ok_or_else(|| Error::from_reason(format!("Unsupported language: {language}")))
79
+ fn find_language(language: &str) -> Result<&'static languages::LanguageDefinition, String> {
80
+ languages::find_language(language).ok_or_else(|| format!("Unsupported language: {language}"))
87
81
  }
88
82
 
89
83
  fn to_duplication_settings(
@@ -0,0 +1,45 @@
1
+ //! The N-API binding loaded by Node.js (src/nativeMetrics.ts).
2
+
3
+ use napi::bindgen_prelude::*;
4
+ use napi_derive::napi;
5
+
6
+ #[napi]
7
+ pub fn payload_version() -> u32 {
8
+ crate::payload_version()
9
+ }
10
+
11
+ #[napi]
12
+ pub fn measure_code_native(
13
+ code: String,
14
+ language: String,
15
+ include_syntax_tree: Option<bool>,
16
+ min_tokens: Option<u32>,
17
+ max_gap_tokens: Option<u32>,
18
+ min_similarity_percent: Option<u32>,
19
+ include_cross_file_data: Option<bool>,
20
+ ) -> Result<String> {
21
+ crate::measure_code(
22
+ &code,
23
+ &language,
24
+ include_syntax_tree.unwrap_or(false),
25
+ min_tokens,
26
+ max_gap_tokens,
27
+ min_similarity_percent,
28
+ include_cross_file_data.unwrap_or(false),
29
+ )
30
+ .map_err(Error::from_reason)
31
+ }
32
+
33
+ #[napi]
34
+ pub fn collect_cross_file_data_native(
35
+ code: String,
36
+ language: String,
37
+ min_tokens: Option<u32>,
38
+ ) -> Result<String> {
39
+ crate::collect_cross_file_data(&code, &language, min_tokens).map_err(Error::from_reason)
40
+ }
41
+
42
+ #[napi]
43
+ pub fn collect_function_token_sequences_native(code: String, language: String) -> Result<String> {
44
+ crate::collect_function_token_sequences(&code, &language).map_err(Error::from_reason)
45
+ }
@@ -0,0 +1,128 @@
1
+ //! The WebAssembly binding for runtimes without N-API, such as Cloudflare Workers
2
+ //! (src/wasmBinding.ts). Strings cross linear memory as UTF-8: the host copies each input into a
3
+ //! buffer from `alloc`, whose ownership passes to the called function, and reads the JSON result
4
+ //! (status 0) or error message (status 1) through `result_ptr`/`result_len` before the next call.
5
+
6
+ use std::cell::RefCell;
7
+
8
+ thread_local! {
9
+ static RESULT: RefCell<String> = const { RefCell::new(String::new()) };
10
+ }
11
+
12
+ #[no_mangle]
13
+ pub extern "C" fn payload_version() -> u32 {
14
+ crate::payload_version()
15
+ }
16
+
17
+ /// Allocates with an exact layout: `Vec::with_capacity` may over-allocate, so rebuilding a `Vec`
18
+ /// from the pointer and `len` would not be sound.
19
+ #[no_mangle]
20
+ pub extern "C" fn alloc(len: usize) -> *mut u8 {
21
+ if len == 0 {
22
+ return std::ptr::NonNull::dangling().as_ptr();
23
+ }
24
+ let pointer = unsafe { std::alloc::alloc(byte_layout(len)) };
25
+ if pointer.is_null() {
26
+ std::alloc::handle_alloc_error(byte_layout(len));
27
+ }
28
+ pointer
29
+ }
30
+
31
+ #[no_mangle]
32
+ pub extern "C" fn result_ptr() -> *const u8 {
33
+ RESULT.with_borrow(|result| result.as_ptr())
34
+ }
35
+
36
+ #[no_mangle]
37
+ pub extern "C" fn result_len() -> usize {
38
+ RESULT.with_borrow(|result| result.len())
39
+ }
40
+
41
+ /// Optional settings arrive as f64 (exact for every u32) with a negative value for "absent", so
42
+ /// the host needs no BigInt or presence flags.
43
+ #[no_mangle]
44
+ #[allow(clippy::too_many_arguments)]
45
+ pub unsafe extern "C" fn measure_code(
46
+ code_ptr: *mut u8,
47
+ code_len: usize,
48
+ language_ptr: *mut u8,
49
+ language_len: usize,
50
+ include_syntax_tree: u32,
51
+ min_tokens: f64,
52
+ max_gap_tokens: f64,
53
+ min_similarity_percent: f64,
54
+ include_cross_file_data: u32,
55
+ ) -> u32 {
56
+ // Both buffers are taken before `?` so that neither leaks when the other is invalid.
57
+ let code = take_string(code_ptr, code_len);
58
+ let language = take_string(language_ptr, language_len);
59
+ store_result((|| {
60
+ crate::measure_code(
61
+ &code?,
62
+ &language?,
63
+ include_syntax_tree != 0,
64
+ to_option(min_tokens),
65
+ to_option(max_gap_tokens),
66
+ to_option(min_similarity_percent),
67
+ include_cross_file_data != 0,
68
+ )
69
+ })())
70
+ }
71
+
72
+ #[no_mangle]
73
+ pub unsafe extern "C" fn collect_cross_file_data(
74
+ code_ptr: *mut u8,
75
+ code_len: usize,
76
+ language_ptr: *mut u8,
77
+ language_len: usize,
78
+ min_tokens: f64,
79
+ ) -> u32 {
80
+ let code = take_string(code_ptr, code_len);
81
+ let language = take_string(language_ptr, language_len);
82
+ store_result((|| {
83
+ crate::collect_cross_file_data(&code?, &language?, to_option(min_tokens))
84
+ })())
85
+ }
86
+
87
+ #[no_mangle]
88
+ pub unsafe extern "C" fn collect_function_token_sequences(
89
+ code_ptr: *mut u8,
90
+ code_len: usize,
91
+ language_ptr: *mut u8,
92
+ language_len: usize,
93
+ ) -> u32 {
94
+ let code = take_string(code_ptr, code_len);
95
+ let language = take_string(language_ptr, language_len);
96
+ store_result((|| {
97
+ crate::collect_function_token_sequences(&code?, &language?)
98
+ })())
99
+ }
100
+
101
+ /// Takes ownership of a buffer returned by `alloc(len)` and filled by the host.
102
+ unsafe fn take_string(ptr: *mut u8, len: usize) -> Result<String, String> {
103
+ if len == 0 {
104
+ return Ok(String::new());
105
+ }
106
+ let text = std::str::from_utf8(std::slice::from_raw_parts(ptr, len))
107
+ .map(str::to_owned)
108
+ .map_err(|error| error.to_string());
109
+ std::alloc::dealloc(ptr, byte_layout(len));
110
+ text
111
+ }
112
+
113
+ fn byte_layout(len: usize) -> std::alloc::Layout {
114
+ std::alloc::Layout::array::<u8>(len).expect("buffer size overflows isize")
115
+ }
116
+
117
+ fn to_option(value: f64) -> Option<u32> {
118
+ (value >= 0.0).then_some(value as u32)
119
+ }
120
+
121
+ fn store_result(result: Result<String, String>) -> u32 {
122
+ let (status, text) = match result {
123
+ Ok(json) => (0, json),
124
+ Err(message) => (1, message),
125
+ };
126
+ RESULT.set(text);
127
+ status
128
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-gauge",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "Measure code metrics with tree-sitter.",
5
5
  "keywords": [
6
6
  "cli",
@@ -24,6 +24,7 @@
24
24
  "exports": {
25
25
  ".": {
26
26
  "types": "./dist/index.d.ts",
27
+ "workerd": "./dist/worker.js",
27
28
  "require": "./dist/index.cjs",
28
29
  "import": "./dist/index.js"
29
30
  }
@@ -37,14 +38,17 @@
37
38
  "native/build.rs",
38
39
  "native/Cargo.lock",
39
40
  "native/Cargo.toml",
41
+ "native/code-gauge.wasm",
40
42
  "native/src/",
41
43
  "scripts/buildNative.mjs",
42
- "scripts/installNative.mjs"
44
+ "scripts/installNative.mjs",
45
+ "THIRD-PARTY-NOTICES.txt"
43
46
  ],
44
47
  "scripts": {
45
48
  "benchmark": "node scripts/benchmark.mjs",
46
- "build": "build-ts lib -i src/index.ts -i src/cli.ts",
49
+ "build": "build-ts lib -i src/index.ts -i src/cli.ts -i src/worker.ts --external ../native/code-gauge.wasm",
47
50
  "build-native": "node scripts/buildNative.mjs",
51
+ "build-wasm": "node scripts/buildWasm.mjs",
48
52
  "cleanup": "bun wb lint --fix --format",
49
53
  "format": "bun wb lint --format",
50
54
  "postinstall": "node scripts/installNative.mjs",
@@ -79,7 +83,8 @@
79
83
  "semantic-release": "25.0.5",
80
84
  "sort-package-json": "4.0.0",
81
85
  "typescript": "7.0.2",
82
- "vitest": "4.1.9"
86
+ "vitest": "4.1.9",
87
+ "wrangler": "4.135.0"
83
88
  },
84
89
  "engines": {
85
90
  "node": ">=24"
@@ -89,12 +94,12 @@
89
94
  "registry": "https://registry.npmjs.org/"
90
95
  },
91
96
  "optionalDependencies": {
92
- "code-gauge-linux-x64-gnu": "4.6.0",
93
- "code-gauge-linux-arm64-gnu": "4.6.0",
94
- "code-gauge-linux-x64-musl": "4.6.0",
95
- "code-gauge-linux-arm64-musl": "4.6.0",
96
- "code-gauge-darwin-x64": "4.6.0",
97
- "code-gauge-darwin-arm64": "4.6.0",
98
- "code-gauge-win32-x64-msvc": "4.6.0"
97
+ "code-gauge-linux-x64-gnu": "4.7.0",
98
+ "code-gauge-linux-arm64-gnu": "4.7.0",
99
+ "code-gauge-linux-x64-musl": "4.7.0",
100
+ "code-gauge-linux-arm64-musl": "4.7.0",
101
+ "code-gauge-darwin-x64": "4.7.0",
102
+ "code-gauge-darwin-arm64": "4.7.0",
103
+ "code-gauge-win32-x64-msvc": "4.7.0"
99
104
  }
100
105
  }